Repository navigation
feat(console): register the mobile shell push token after sign-in and unregister it on sign-out - #30534
Conversation
…it on removal Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L6bhgWPe8iKWPE5j7CLjdF
…e-sign-out steps Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L6bhgWPe8iKWPE5j7CLjdF
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L6bhgWPe8iKWPE5j7CLjdF
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L6bhgWPe8iKWPE5j7CLjdF
…n still exists Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L6bhgWPe8iKWPE5j7CLjdF
|
domain:ui#1 · os-sales · session_01L6bhgWn6zhNa4LL7VB1rtL Contract-review-claim: session_01L6bhgWn6zhNa4LL7VB1rtL · head 9d7ef8b · 2026-11-05T15:09Z |
|
domain:ui#1 · os-sales · session_01L6bhgWn6zhNa4LL7VB1rtL Contract reviewServed-tier: Owed by the ① Derived judgments
② Semver levelAll three packages are ③ Boundary flags
Implemented-by: VERDICT: PASS |
|
domain:ui#1 · os-sales · session_01L6bhgWn6zhNa4LL7VB1rtL 维护者速读
席位核对 — ACCEPT · head 9d7ef8b
|
Requested by Sales · project thread
Fixes #29042
Clause-②: yes (widening)
Metadata-protocol: none
维护者速读
ui/apps/console/src/nativePushRegistration.ts(注册时机、去重、注销),ui/packages/auth/src/beforeSignOut.ts(登出前 seam),ui/apps/console/src/App.tsx(接线)。pnpm --dir ui exec vitest run apps/console/src/__tests__/nativePushRegistration-29042.test.tsx,预期 17 个用例全过。What changes
ui/apps/console) — newuseNativePushRegistration(pathname), called byNativeShellRoutingbeforeuseNativeHostRouting, whose return value is that call'sonConnectionRemoved:getPushToken()) once per page load, only when a signed-in person is on Home or an app and has stayed there forNATIVE_PUSH_PROMPT_DELAY_MS(1.5 s). The first call shows the OS permission prompt, so it never runs on a sign-in surface. Anullanswer sends nothing.client.notifications.registerDevice(POST /api/v1/notifications/devices) withplatform(iosorandroid; any other shell registers nothing),vendor,deviceId= the shell'sinstallIdwhen it is a string (else the id the server answered last time, else none), andoriginonly whenwindow.location.protocolishttp:orhttps:(an opaque origin reads"null", which the request grammar refuses for the whole body).sessionStorage; an equal token for the same person is not sent again. A rotated token (objectstack-native:pushTokenChanged, announced ashas('pushTokenChanged')) is registered again; a rotation that arrives before sign-in is registered at sign-in without asking the shell. Another person signing in on the page registers the device for them, and the registry's own token move takes it from the previous person.session.impersonatedBy): that would move the impersonated person's pushes onto the administrator's device.DELETE /api/v1/notifications/devices/:deviceId) runs under the Console's session for the shell'sconnectionRemovedevent and before sign-out. It resolves when nothing is left registered (a 404 counts), and rejects when the device may still be registered (no session left, or the request failed), so the shell records the truth.@object-ui/mobile—ObjectStackNativegains the optionalinstallId(string | null); the entry exportsNATIVE_PUSH_TOKEN_CHANGED_EVENTandNativePushTokenChangedEvent. Both feature-detected. README updated.@object-ui/auth— newregisterBeforeSignOut(step)(andBEFORE_SIGN_OUT_TIMEOUT_MS): stepssignOut()awaits while the session still exists, run together, bounded at 3 s, a rejection reported and dropped. Sign-out is never held back.Why
The shell holds no API credential, so only the Console can register the device under its session (card ask 1, 3, 4). The card's comments are part of the work: the shell's
installIdandpushTokenChanged(record 6577618893), theonConnectionRemovedunregister seam (record 6588256589) andoriginonly onhttp:/https:(record 6595330380). Unregister on sign-out (ask 2) needs a step that runs before the session ends; no such hook existed, hence the@object-ui/authseam (seat amendment 6659877507).Tests
ui/apps/console/src/__tests__/nativePushRegistration-29042.test.tsx(new, 17): sign-in on Home registers after the delay, once, withdeviceIdandorigin; noinstallIdsends nodeviceIdand keeps the minted id; a sign-in surface and no session never ask the shell; leaving Home before the delay asks nothing;nullsends no request; an impersonation registers nothing; a non-iOS/Android shell registers nothing; a rotated token re-registers and a repeat is not sent twice; a rotation before sign-in registers at sign-in; another person registers for themselves; connection removed deletes the device and forgets it; sign-out (runBeforeSignOut) runs the delete; 404 counts as unregistered; a failed delete rejects; no session rejects; nothing registered resolves with no request;originfor http, https and opaque origins.ui/apps/console/src/__tests__/App.nativeShellMode-29039.test.tsx(shared harness, new top-level describe, 2): through the realApprouter, Home asks for the token and registers it; the shell'sconnectionRemovedwaits for the DELETE of the registered device.ui/packages/auth/src/__tests__/beforeSignOut-29042.test.ts(new, 4): every step runs and is awaited; a rejection does not reject the run; a step that never settles is cut off at the timeout; a removed step does not run.Acceptance notes
@object-ui/mobile:ObjectStackNative.installId?(optional member),NATIVE_PUSH_TOKEN_CHANGED_EVENT,type NativePushTokenChangedEvent.@object-ui/auth:registerBeforeSignOut,BEFORE_SIGN_OUT_TIMEOUT_MS. Nothing removed or narrowed.AuthProvider.signOut()call torunBeforeSignOut()(beforeclient.signOut()) waited for the sign-out PR that ownedui/packages/auth/src/AuthProvider.tsx(fix(console): after a sign-out, the next person to sign in lands on their own home, not the previous person's page #30490). That PR has merged (c3224219); the call landed on this branch after a merge ofmain, at head9d7ef8b1(seat amendment 6659877507).route) is a manual run: no device lab in this container.claude/project-thread-t8v3to(claim amendment 6659783719), notclaude/issue-N-slug.localeis not sent on registration (the card does not ask for it); the registry renders a push in the device locale only when one is stored.sessionStorage, so a cold app start re-registers once the person reaches Home (an idempotent refresh); with no record and noinstallId, a connection removal has no device id to delete and resolves.content/docs/frontend/documents the native bridge today; the package README carries the new members.Verification
Targeted tests (head 03b3034)
Type check
Derived gates (dispatch-gates --commands, tree a257694)
Re-measured on the final head
9d7ef8b1(os-dev-report 6661373319): targeted tests 58 files, 604 passed; the three packages' type-check exit 0; 36 derived gates, all exit 0, 0 NOT-MEASURED.🤖 Generated with Claude Code
https://claude.ai/code/session_01L6bhgWPe8iKWPE5j7CLjdF
Generated by Claude Code