Repository navigation
Commit a4c3e73
feat(console): register the mobile shell push token after sign-in and unregister it on sign-out (#30534)
<!-- claude-projects-attribution: {"github_login":"os-sales"} -->
_Requested by **Sales** · [project
thread](https://claude.ai/code/project/chan_01D5bWFY3rFzgJcytD5gD4Uu?thread=cmsg_01D5bWFY3rFzgJcytD5gD4Uu3UEbecgCQf9xiCeSoCrKC1)_
Fixes #29042
Clause-②: yes (widening)
Metadata-protocol: none
## 维护者速读
- 改了什么:在移动端原生壳里,Console 用自己的会话把壳的推送 token 注册到设备表;换 token
时重注册;登出前和壳移除连接时注销。
- 为什么改:壳本身没有 API 凭证,只能由 Console 注册;不注册就收不到推送,登出不注销则已登出的设备还会收到这个人的推送。
- 风险与回滚:只在原生壳里生效,浏览器不变;冒充会话下不注册;登出前的步骤有 3 秒上限,失败或超时都不挡登出。回滚即还原本 PR。
-
先看:`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
- **Console (`ui/apps/console`)** — new
`useNativePushRegistration(pathname)`, called by `NativeShellRouting`
before `useNativeHostRouting`, whose return value is that call's
`onConnectionRemoved`:
- Asks the shell for the token (`getPushToken()`) once per page load,
only when a signed-in person is on Home or an app and has stayed there
for `NATIVE_PUSH_PROMPT_DELAY_MS` (1.5 s). The first call shows the OS
permission prompt, so it never runs on a sign-in surface. A `null`
answer sends nothing.
- Registers through `client.notifications.registerDevice` (`POST
/api/v1/notifications/devices`) with `platform` (`ios` or `android`; any
other shell registers nothing), `vendor`, `deviceId` = the shell's
`installId` when it is a string (else the id the server answered last
time, else none), and `origin` only when `window.location.protocol` is
`http:` or `https:` (an opaque origin reads `"null"`, which the request
grammar refuses for the whole body).
- The last registration (person, token, vendor, device id) is kept in
`sessionStorage`; an equal token for the same person is not sent again.
A rotated token (`objectstack-native:pushTokenChanged`, announced as
`has('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.
- Never registers while the session is an impersonation
(`session.impersonatedBy`): that would move the impersonated person's
pushes onto the administrator's device.
- Unregister (`DELETE /api/v1/notifications/devices/:deviceId`) runs
under the Console's session for the shell's `connectionRemoved` event
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`** — `ObjectStackNative` gains the optional
`installId` (`string | null`); the entry exports
`NATIVE_PUSH_TOKEN_CHANGED_EVENT` and `NativePushTokenChangedEvent`.
Both feature-detected. README updated.
- **`@object-ui/auth`** — new `registerBeforeSignOut(step)` (and
`BEFORE_SIGN_OUT_TIMEOUT_MS`): steps `signOut()` 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 `installId` and `pushTokenChanged` (record
6577618893), the `onConnectionRemoved` unregister seam (record
6588256589) and `origin` only on `http:`/`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/auth` seam (seat
amendment 6659877507).
## Tests
- `ui/apps/console/src/__tests__/nativePushRegistration-29042.test.tsx`
(new, 17): sign-in on Home registers after the delay, once, with
`deviceId` and `origin`; no `installId` sends no `deviceId` and keeps
the minted id; a sign-in surface and no session never ask the shell;
leaving Home before the delay asks nothing; `null` sends 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; `origin` for http, https and opaque
origins.
- `ui/apps/console/src/__tests__/App.nativeShellMode-29039.test.tsx`
(shared harness, new top-level describe, 2): through the real `App`
router, Home asks for the token and registers it; the shell's
`connectionRemoved` waits 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
- **Public surface added (Clause-② yes, widening):**
`@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.
- **Sequencing:** the one-line `AuthProvider.signOut()` call to
`runBeforeSignOut()` (before `client.signOut()`) waited for the sign-out
PR that owned `ui/packages/auth/src/AuthProvider.tsx` (#30490). That PR
has merged (`c3224219`); the call landed on this branch after a merge of
`main`, at head `9d7ef8b1` (seat amendment 6659877507).
- Device acceptance (a device build receives a push and opens its
`route`) is a manual run: no device lab in this container.
- Branch is the harness-designated `claude/project-thread-t8v3to` (claim
amendment 6659783719), not `claude/issue-N-slug`.
- `locale` is 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.
- The registration record lives in `sessionStorage`, so a cold app start
re-registers once the person reaches Home (an idempotent refresh); with
no record and no `installId`, a connection removal has no device id to
delete and resolves.
- No page under `content/docs/frontend/` documents the native bridge
today; the package README carries the new members.
## Verification
<details><summary>Targeted tests (head 03b3034)</summary>
```
pnpm --dir ui exec vitest run apps/console/src/__tests__/nativePushRegistration-29042.test.tsx apps/console/src/__tests__/App.nativeShellMode-29039.test.tsx apps/console/src/__tests__/App.nativeShellOpenSet-29330.test.tsx apps/console/src/__tests__/main.nativeBridgeVersion-29330.test.tsx packages/mobile/src/ packages/auth/src/__tests__/beforeSignOut-29042.test.ts
Test Files 14 passed (14)
Tests 210 passed (210)
```
</details>
<details><summary>Type check</summary>
```
pnpm --filter @object-ui/mobile --filter @object-ui/auth --filter @object-ui/console --workspace-concurrency=1 run type-check
os-verify-lock: VERDICT command-exit 0
```
</details>
<details><summary>Derived gates (dispatch-gates --commands, tree
a257694)</summary>
```
36 derived, each recorded as command :: exit N, all exit 0; plus pnpm --dir ui run check:spec-symbols :: exit 0
dispatch-gates --ran: 36 derived famil(ies) accounted for, 36 run, 0 NOT-MEASURED (a DERIVED zero)
pnpm --dir ui run check:new-line-citations: 0 new citation(s)
```
</details>
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.com/claude-code)
https://claude.ai/code/session_01L6bhgWPe8iKWPE5j7CLjdF
---
_Generated by [Claude
Code](https://claude.ai/code/session_01L6bhgWPe8iKWPE5j7CLjdF)_
<!-- signed: domain:ui#1 · os-sales · session_01L6bhgWn6zhNa4LL7VB1rtL
-->
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent ab159a9 commit a4c3e73
13 files changed
Lines changed: 1036 additions & 3 deletions
File tree
- .changeset
- ui
- apps/console/src
- __tests__
- packages
- auth/src
- __tests__
- mobile
- src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
49 | 49 | | |
50 | 50 | | |
51 | 51 | | |
| 52 | + | |
52 | 53 | | |
53 | 54 | | |
54 | 55 | | |
| |||
160 | 161 | | |
161 | 162 | | |
162 | 163 | | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
163 | 168 | | |
164 | 169 | | |
165 | 170 | | |
| |||
174 | 179 | | |
175 | 180 | | |
176 | 181 | | |
| 182 | + | |
177 | 183 | | |
178 | 184 | | |
179 | 185 | | |
| 186 | + | |
180 | 187 | | |
181 | 188 | | |
182 | 189 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
26 | 26 | | |
27 | 27 | | |
28 | 28 | | |
| 29 | + | |
29 | 30 | | |
30 | 31 | | |
31 | 32 | | |
32 | 33 | | |
33 | 34 | | |
34 | 35 | | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
35 | 41 | | |
36 | | - | |
| 42 | + | |
37 | 43 | | |
38 | 44 | | |
39 | 45 | | |
| |||
45 | 51 | | |
46 | 52 | | |
47 | 53 | | |
48 | | - | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
49 | 58 | | |
50 | 59 | | |
51 | 60 | | |
| |||
90 | 99 | | |
91 | 100 | | |
92 | 101 | | |
93 | | - | |
| 102 | + | |
94 | 103 | | |
95 | 104 | | |
96 | 105 | | |
| |||
166 | 175 | | |
167 | 176 | | |
168 | 177 | | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
169 | 181 | | |
170 | 182 | | |
171 | 183 | | |
| |||
358 | 370 | | |
359 | 371 | | |
360 | 372 | | |
| 373 | + | |
| 374 | + | |
| 375 | + | |
| 376 | + | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
| 385 | + | |
| 386 | + | |
| 387 | + | |
| 388 | + | |
| 389 | + | |
| 390 | + | |
| 391 | + | |
| 392 | + | |
| 393 | + | |
| 394 | + | |
| 395 | + | |
| 396 | + | |
| 397 | + | |
| 398 | + | |
| 399 | + | |
| 400 | + | |
| 401 | + | |
| 402 | + | |
| 403 | + | |
| 404 | + | |
| 405 | + | |
| 406 | + | |
| 407 | + | |
| 408 | + | |
| 409 | + | |
| 410 | + | |
| 411 | + | |
| 412 | + | |
| 413 | + | |
| 414 | + | |
| 415 | + | |
| 416 | + | |
| 417 | + | |
| 418 | + | |
| 419 | + | |
| 420 | + | |
| 421 | + | |
| 422 | + | |
| 423 | + | |
| 424 | + | |
| 425 | + | |
| 426 | + | |
| 427 | + | |
| 428 | + | |
| 429 | + | |
| 430 | + | |
| 431 | + | |
| 432 | + | |
| 433 | + | |
| 434 | + | |
| 435 | + | |
| 436 | + | |
| 437 | + | |
| 438 | + | |
| 439 | + | |
| 440 | + | |
| 441 | + | |
| 442 | + | |
| 443 | + | |
| 444 | + | |
| 445 | + | |
0 commit comments