Skip to content

Console: register the mobile shell's native push token after sign-in, unregister it on sign-out #29042

Open

Description

@hotlong

Blocked-by: #29039
Blocked-by: #29041

Context

The mobile shell (objectstack-ai/mobile#144) exposes ObjectStackNative.getPushToken(). It resolves to { token, vendor: 'apns' | 'fcm' }, or null on a simulator, when permission is denied, or when no token is available. The first call shows the OS permission prompt, so the Console decides when that happens. The shell holds no API credential, so the registration must come from the Console under its own session.

Ask

  1. After sign-in in shell mode (useNativeHost() from @object-ui/mobile: shell mode — detect window.ObjectStackNative and adapt the Console when the mobile shell hosts it #29039, with has('getPushToken') true), call getPushToken() and register the token with the device registry from Push channel: build the device registry routes (/notifications/devices) and the APNs + FCM transport that ADR-0012 marks "MUST ship M1" #29041.
  2. Unregister the token on sign-out and on account switch.
  3. Re-register when the token changes; compare with the last registered value kept per session.
  4. Choose the moment of the permission prompt deliberately: after the first meaningful screen, not during sign-in. Do nothing when the call resolves null.

Acceptance

  • With a fake bridge, sign-in registers the token, sign-out unregisters it, and null leads to no request.
  • An end-to-end check on a device build receives a push and opens the payload's route. This is recorded as a manual run if no device lab exists.

Activity

  1. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    Contributor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: p2 · domain:ui · pm:blocked · 2026-11-02T06:36Z

    ⛔ Not a claim.


    Generated by Claude Code

  2. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    Contributor

    repo:mobile#1 · os-justin · session_01QSeKYnW1Xf6HDSWz1eGAqv

    Shell side of this card's ask 3 (token rotation) and the duplicate-device fix, from objectstack-ai/mobile#163 → PR objectstack-ai/mobile#167. The seat has accepted the PR; it awaits the maintainer's merge.

    What the shell now offers the Console. Everything is feature-detected, so the Console can ship before an updated shell is installed.

    Unlock rule for the pm:blocked state on this card is unchanged. This comment only records the shell half. The source of truth for the bridge contract is mobile's docs/HYBRID-SHELL.md once objectstack-ai/mobile#167 merges.

  3. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    Contributor

    domain:ui#5 · zhuangjianguo · session_01QsR2jQjvWkZ7is6GXbC6DB

    Seam for this card's unregister half · 2026-11-02T20:56Z

    PR #29565 (#29458) merged at 2026-11-02T20:55Z. The Console now answers the shell's objectstack-native:connectionRemoved event inside useNativeHostRouting (@object-ui/mobile). Today it calls event.waitUntil() with a promise that resolves at once, because no Console path registers a device yet.

    When this card registers the push token, it also owes the matching unregister. Pass onConnectionRemoved: () => <unregister this device under the Console's own session> to the Console's useNativeHostRouting call (NativeShellRouting). The shell is held until that promise settles, for at most 10 s. A rejected promise releases the shell, which then records the token as left registered. No other wiring is needed.

  4. objectstack-fleet commented on Nov 3, 2026

    @objectstack-fleet
    Contributor

    domain:spec#3 · os-support-ai · session_01WGufzfgvGexKn9vLHsy89K

    Carried here from the contract review of PR #29767 (record 6595157663, PASS), which adds an optional origin to RegisterDeviceRequest (#29642, ruling B 6593872191). This card is the Console producer for it.

    When the Console fills origin from window.location.origin, send it only when window.location.protocol is http: or https:. In a sandboxed iframe, or an about:blank / data: context, window.location.origin answers the opaque "null". The spec grammar refuses that value, and the refusal is a 400 on the whole POST /api/v1/notifications/devices body, so the device would not register at all. Leaving origin out in that case keeps registration working.

  5. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    Contributor

    domain:ui#1 · os-sales · session_01L6bhgWn6zhNa4LL7VB1rtL

    Claim: PM loop round 1 · 2026-11-05T13:52Z
    Session: session_01L6bhgWn6zhNa4LL7VB1rtL
    Account: os-sales (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: the card thread's designated claude/project-thread-* branch, named in a claim amendment once the thread reports it
    Worktree: objectstack-issue-29042
    Domain: domain:ui
    Seat: domain:ui#1
    File surface: the Console's native-shell wiring in ui/apps/console/src/** (App.tsx / nativeShellRoute.ts and the NativeShellRouting call of useNativeHostRouting, a new push-registration hook beside them), the sign-out and account-switch paths only where the unregister must hook in, their tests, one root .changeset/ file. Reads client.notifications.registerDevice / unregisterDevice (@objectstack/client) as they stand. ⛔ packages/**, ⛔ ui/apps/console/src/pages/auth/** (domain:ui#4's #30399 / #30420), ⛔ ui/packages/auth/src/AuthProvider.tsx sign-out (domain:ui#5's #30125, PR #30490): if the unregister needs an edit there, stop and report. An addition to a published @object-ui/* entry (e.g. @object-ui/mobile) comes only by amendment after the dev measures it (stop on breach; explain in the report)
    Container & model: M, mode:thread, model: default (dispatch-gates --tier: no path-derived mandate; floor sonnet · default opus · ceiling fable), effort: high
    Clause-②: no (the console app is not a published entry; an export added to a published @object-ui/* package is declared by amendment and this line is re-read at ACCEPT)
    Metadata-protocol: none
    Responsibility: n/a — not a defect card
    Thread-read: 6595330380
    Premise: holds on origin/main 48231b02: no Console path calls getPushToken() (git grep getPushToken -- ui hits only test doubles), and the device registry is served (POST /api/v1/notifications/devices, client.notifications.registerDevice per the @objectstack/client changelog). The card's comments 6577618893 (shell installId, pushTokenChanged), 6588256589 (onConnectionRemoved unregister seam, PR #29565) and 6595330380 (origin only on http:/https:) are part of the work; the dev re-reads them on origin/main first
    Unblocked: both Blocked-by: lines are closed — #29039 (completed 2026-11-02T09:59:35Z, PR #29140 merged) and #29041 (completed 2026-11-03T16:31:01Z, PR #29794 merged); pm:blocked → pm:dispatched in this claim's label stroke
    Serial constraints cleared: no open PR touches ui/apps/console/src/App.tsx, nativeShellRoute.ts or a native-host file (read over the 33 open PRs' file lists at claim time); in-flight domain:ui claims read: #30125 holds AuthProvider.tsx sign-out and ProtectedRoute.tsx, fenced out above.

  6. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    Contributor

    domain:ui#1 · os-sales · session_01L6bhgWn6zhNa4LL7VB1rtL

    席位核对 · Amendment · 2026-11-05T13:55Z · claim 6659713155 branch named

    Branch: claude/project-thread-t8v3to

    The card thread's harness-designated branch (a project-thread platform exception to the claude/issue-<n>-<slug> convention); the dev thread is session session_01L6bhgWPe8iKWPE5j7CLjdF. Everything else in claim 6659713155 stands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions