authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-09-08 16:39:49-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-08 16:39:49-07:00
log9d6c43f32ed2b4d0f21f1d15b2c671b0c07be991
tree6255f735008099531a4cb796437484998bafa655
parent3dfc134e6c1e2359400c56ea415e74fecf9d5c12
signature Signed by PGP key B5690EEEBB952194

fix(mobile): respect notification permission when tokens rotate (#10850)


2 files changed, 46 insertions(+), 3 deletions(-)

apps/mobile/src/features/agent-awareness/remoteRegistration.test.ts+43
......@@ -924,4 +924,47 @@ describe("makeRelayDeviceRegistrationRequest", () => {
924924 await new Promise((resolve) => setTimeout(resolve, 0));
925925 expect(widgetMocks.start).toHaveBeenCalledTimes(1);
926926 });
927 it.effect(
928 "does not enable notifications when a token rotates after permission is revoked",
929 () => {
930 const registrations: unknown[] = [];
931 vi.stubGlobal("fetch", async (input: RequestInfo | URL, init?: RequestInit) => {
932 const request = new Request(input, init);
933 if (request.url.endsWith("/v1/client/dpop-token")) {
934 return Response.json({
935 access_token: "dpop",
936 issued_token_type: "urn:ietf:params:oauth:token-type:access_token",
937 token_type: "DPoP",
938 expires_in: 300,
939 scope: "mobile:registration",
940 });
941 }
942 registrations.push(await request.json());
943 return Response.json({ ok: true });
944 });
945 Constants.expoConfig!.extra = { relay: { url: "https://permission-relay.example.test" } };
946 setAgentAwarenessRelayTokenProvider(() => Promise.resolve("clerk"), "user-a");
947 return Effect.gen(function* () {
948 yield* runBackgroundOperations();
949 expect(registrations.at(-1)).toMatchObject({ preferences: { notificationsEnabled: true } });
950 vi.mocked(Notifications.getPermissionsAsync).mockResolvedValueOnce({
951 granted: false,
952 } as Awaited<ReturnType<typeof Notifications.getPermissionsAsync>>);
953 const listener = vi.mocked(Notifications.addPushTokenListener).mock.calls.at(-1)![0];
954 listener({ type: "ios", data: "rotated" });
955 yield* runBackgroundOperations();
956 expect(registrations.at(-1)).toMatchObject({
957 preferences: { notificationsEnabled: false },
958 });
959 expect(registrations.at(-1)).not.toHaveProperty("pushToken");
960 }).pipe(
961 Effect.provideService(FetchHttpClient.Fetch, globalThis.fetch),
962 Effect.provide(
963 managedRelayClientLayer("https://permission-relay.example.test").pipe(
964 Layer.provide(Layer.mergeAll(FetchHttpClient.layer, cryptoLayer)),
965 ),
966 ),
967 );
968 },
969 );
927970});
apps/mobile/src/features/agent-awareness/remoteRegistration.ts+3-3
......@@ -245,9 +245,6 @@ function nativePushTokenRegistration(observedPushToken?: string) {
245245 if (!canRegisterRemoteLiveActivities() || !supportsAgentAwarenessPush()) {
246246 return { notificationsEnabled: false, pushToken: null };
247247 }
248 if (observedPushToken) {
249 return { notificationsEnabled: true, pushToken: observedPushToken };
250 }
251248 const permissions = yield* Effect.tryPromise({
252249 try: () => Notifications.getPermissionsAsync(),
253250 catch: (cause) =>
......@@ -259,6 +256,9 @@ function nativePushTokenRegistration(observedPushToken?: string) {
259256 if (!permissions.granted) {
260257 return { notificationsEnabled: false, pushToken: null };
261258 }
259 if (observedPushToken) {
260 return { notificationsEnabled: true, pushToken: observedPushToken };
261 }
262262 const token = yield* Effect.tryPromise({
263263 try: () => Notifications.getDevicePushTokenAsync(),
264264 catch: (cause) =>