| ... | @@ -2,6 +2,7 @@ import { addPushToStartTokenListener, type LiveActivity } from "expo-widgets"; | ... | @@ -2,6 +2,7 @@ import { addPushToStartTokenListener, type LiveActivity } from "expo-widgets"; |
| 2 | import Constants from "expo-constants"; | 2 | import Constants from "expo-constants"; |
| 3 | import * as Notifications from "expo-notifications"; | 3 | import * as Notifications from "expo-notifications"; |
| 4 | import * as Effect from "effect/Effect"; | 4 | import * as Effect from "effect/Effect"; |
| | 5 | import * as Schema from "effect/Schema"; |
| 5 | import { Platform } from "react-native"; | 6 | import { Platform } from "react-native"; |
| 6 | import type { EnvironmentId } from "@t3tools/contracts"; | 7 | import type { EnvironmentId } from "@t3tools/contracts"; |
| 7 | import { | 8 | import { |
| ... | @@ -28,6 +29,33 @@ import { resolveCloudPublicConfig } from "../cloud/publicConfig"; | ... | @@ -28,6 +29,33 @@ import { resolveCloudPublicConfig } from "../cloud/publicConfig"; |
| 28 | import { makeRelayDeviceRegistrationRequest } from "./registrationPayload"; | 29 | import { makeRelayDeviceRegistrationRequest } from "./registrationPayload"; |
| 29 | | 30 | |
| 30 | const REMOTE_ACTIVITY_REGISTRATION_RETRY_MS = 15_000; | 31 | const REMOTE_ACTIVITY_REGISTRATION_RETRY_MS = 15_000; |
| | 32 | |
| | 33 | const AgentAwarenessOperation = Schema.Literals([ |
| | 34 | "read-notification-permissions", |
| | 35 | "read-native-push-token", |
| | 36 | "read-device-registration-relay-token", |
| | 37 | "read-device-unregistration-relay-token", |
| | 38 | "read-live-activity-registration-relay-token", |
| | 39 | "load-device-registration-identifier", |
| | 40 | "load-device-registration-preferences", |
| | 41 | "load-device-unregistration-identifier", |
| | 42 | "read-live-activity-push-token", |
| | 43 | "load-live-activity-registration-identifier", |
| | 44 | "list-active-live-activities", |
| | 45 | ]); |
| | 46 | |
| | 47 | export class AgentAwarenessOperationError extends Schema.TaggedErrorClass<AgentAwarenessOperationError>()( |
| | 48 | "AgentAwarenessOperationError", |
| | 49 | { |
| | 50 | operation: AgentAwarenessOperation, |
| | 51 | cause: Schema.Defect(), |
| | 52 | }, |
| | 53 | ) { |
| | 54 | override get message(): string { |
| | 55 | return `Agent awareness operation ${this.operation} failed.`; |
| | 56 | } |
| | 57 | } |
| | 58 | |
| 31 | const environmentConnections = new Map<EnvironmentId, SavedRemoteConnection>(); | 59 | const environmentConnections = new Map<EnvironmentId, SavedRemoteConnection>(); |
| 32 | const activityPushTokenListeners = new WeakSet<LiveActivity<AgentActivityProps>>(); | 60 | const activityPushTokenListeners = new WeakSet<LiveActivity<AgentActivityProps>>(); |
| 33 | let pushToStartSubscription: { remove: () => void } | null = null; | 61 | let pushToStartSubscription: { remove: () => void } | null = null; |
| ... | @@ -137,14 +165,22 @@ function nativePushTokenRegistration(observedPushToken?: string) { | ... | @@ -137,14 +165,22 @@ function nativePushTokenRegistration(observedPushToken?: string) { |
| 137 | } | 165 | } |
| 138 | const permissions = yield* Effect.tryPromise({ | 166 | const permissions = yield* Effect.tryPromise({ |
| 139 | try: () => Notifications.getPermissionsAsync(), | 167 | try: () => Notifications.getPermissionsAsync(), |
| 140 | catch: (error) => error, | 168 | catch: (cause) => |
| | 169 | new AgentAwarenessOperationError({ |
| | 170 | operation: "read-notification-permissions", |
| | 171 | cause, |
| | 172 | }), |
| 141 | }); | 173 | }); |
| 142 | if (!permissions.granted) { | 174 | if (!permissions.granted) { |
| 143 | return { notificationsEnabled: false, pushToken: null }; | 175 | return { notificationsEnabled: false, pushToken: null }; |
| 144 | } | 176 | } |
| 145 | const token = yield* Effect.tryPromise({ | 177 | const token = yield* Effect.tryPromise({ |
| 146 | try: () => Notifications.getDevicePushTokenAsync(), | 178 | try: () => Notifications.getDevicePushTokenAsync(), |
| 147 | catch: (error) => error, | 179 | catch: (cause) => |
| | 180 | new AgentAwarenessOperationError({ |
| | 181 | operation: "read-native-push-token", |
| | 182 | cause, |
| | 183 | }), |
| 148 | }).pipe( | 184 | }).pipe( |
| 149 | Effect.tapError((error) => | 185 | Effect.tapError((error) => |
| 150 | Effect.sync(() => { | 186 | Effect.sync(() => { |
| ... | @@ -161,16 +197,19 @@ function nativePushTokenRegistration(observedPushToken?: string) { | ... | @@ -161,16 +197,19 @@ function nativePushTokenRegistration(observedPushToken?: string) { |
| 161 | }); | 197 | }); |
| 162 | } | 198 | } |
| 163 | | 199 | |
| 164 | const relayToken = Effect.gen(function* () { | 200 | const relayToken = ( |
| 165 | const provider = relayTokenProvider; | 201 | operation: "read-device-registration-relay-token" | "read-live-activity-registration-relay-token", |
| 166 | if (!provider) { | 202 | ) => |
| 167 | return null; | 203 | Effect.gen(function* () { |
| 168 | } | 204 | const provider = relayTokenProvider; |
| 169 | return yield* Effect.tryPromise({ | 205 | if (!provider) { |
| 170 | try: provider, | 206 | return null; |
| 171 | catch: (error) => error, | 207 | } |
| | 208 | return yield* Effect.tryPromise({ |
| | 209 | try: provider, |
| | 210 | catch: (cause) => new AgentAwarenessOperationError({ operation, cause }), |
| | 211 | }); |
| 172 | }); | 212 | }); |
| 173 | }); | | |
| 174 | | 213 | |
| 175 | function registerDeviceWithRelay( | 214 | function registerDeviceWithRelay( |
| 176 | body: RelayDeviceRegistrationRequest, | 215 | body: RelayDeviceRegistrationRequest, |
| ... | @@ -185,7 +224,7 @@ function registerDeviceWithRelay( | ... | @@ -185,7 +224,7 @@ function registerDeviceWithRelay( |
| 185 | return; | 224 | return; |
| 186 | } | 225 | } |
| 187 | if (!readRelayConfig()) return; | 226 | if (!readRelayConfig()) return; |
| 188 | const token = yield* relayToken; | 227 | const token = yield* relayToken("read-device-registration-relay-token"); |
| 189 | if (expectedGeneration !== deviceRegistrationGeneration) { | 228 | if (expectedGeneration !== deviceRegistrationGeneration) { |
| 190 | logRegistrationDebug("device registration cancelled after auth lookup", { | 229 | logRegistrationDebug("device registration cancelled after auth lookup", { |
| 191 | expectedGeneration, | 230 | expectedGeneration, |
| ... | @@ -220,7 +259,11 @@ function unregisterDeviceWithRelay(input: { | ... | @@ -220,7 +259,11 @@ function unregisterDeviceWithRelay(input: { |
| 220 | if (!readRelayConfig()) return; | 259 | if (!readRelayConfig()) return; |
| 221 | const token = yield* Effect.tryPromise({ | 260 | const token = yield* Effect.tryPromise({ |
| 222 | try: input.tokenProvider, | 261 | try: input.tokenProvider, |
| 223 | catch: (error) => error, | 262 | catch: (cause) => |
| | 263 | new AgentAwarenessOperationError({ |
| | 264 | operation: "read-device-unregistration-relay-token", |
| | 265 | cause, |
| | 266 | }), |
| 224 | }); | 267 | }); |
| 225 | if (!token) { | 268 | if (!token) { |
| 226 | logRegistrationDebug("relay device unregistration skipped; user is not signed in"); | 269 | logRegistrationDebug("relay device unregistration skipped; user is not signed in"); |
| ... | @@ -240,7 +283,7 @@ function registerLiveActivityWithRelay( | ... | @@ -240,7 +283,7 @@ function registerLiveActivityWithRelay( |
| 240 | ): Effect.Effect<boolean, unknown, ManagedRelay.ManagedRelayClient> { | 283 | ): Effect.Effect<boolean, unknown, ManagedRelay.ManagedRelayClient> { |
| 241 | return Effect.gen(function* () { | 284 | return Effect.gen(function* () { |
| 242 | if (!readRelayConfig()) return false; | 285 | if (!readRelayConfig()) return false; |
| 243 | const token = yield* relayToken; | 286 | const token = yield* relayToken("read-live-activity-registration-relay-token"); |
| 244 | if (!token) { | 287 | if (!token) { |
| 245 | logRegistrationDebug("relay live activity registration skipped; user is not signed in"); | 288 | logRegistrationDebug("relay live activity registration skipped; user is not signed in"); |
| 246 | return false; | 289 | return false; |
| ... | @@ -381,11 +424,19 @@ function registerDevice( | ... | @@ -381,11 +424,19 @@ function registerDevice( |
| 381 | const [deviceId, preferences] = yield* Effect.all([ | 424 | const [deviceId, preferences] = yield* Effect.all([ |
| 382 | Effect.tryPromise({ | 425 | Effect.tryPromise({ |
| 383 | try: () => loadOrCreateAgentAwarenessDeviceId(), | 426 | try: () => loadOrCreateAgentAwarenessDeviceId(), |
| 384 | catch: (error) => error, | 427 | catch: (cause) => |
| | 428 | new AgentAwarenessOperationError({ |
| | 429 | operation: "load-device-registration-identifier", |
| | 430 | cause, |
| | 431 | }), |
| 385 | }), | 432 | }), |
| 386 | Effect.tryPromise({ | 433 | Effect.tryPromise({ |
| 387 | try: () => loadPreferences(), | 434 | try: () => loadPreferences(), |
| 388 | catch: (error) => error, | 435 | catch: (cause) => |
| | 436 | new AgentAwarenessOperationError({ |
| | 437 | operation: "load-device-registration-preferences", |
| | 438 | cause, |
| | 439 | }), |
| 389 | }), | 440 | }), |
| 390 | ]); | 441 | ]); |
| 391 | const pushTokenRegistration = yield* nativePushTokenRegistration(input?.observedPushToken); | 442 | const pushTokenRegistration = yield* nativePushTokenRegistration(input?.observedPushToken); |
| ... | @@ -519,7 +570,11 @@ export function unregisterAgentAwarenessDeviceForCurrentUser( | ... | @@ -519,7 +570,11 @@ export function unregisterAgentAwarenessDeviceForCurrentUser( |
| 519 | return Effect.gen(function* () { | 570 | return Effect.gen(function* () { |
| 520 | const deviceId = yield* Effect.tryPromise({ | 571 | const deviceId = yield* Effect.tryPromise({ |
| 521 | try: () => loadAgentAwarenessDeviceId(), | 572 | try: () => loadAgentAwarenessDeviceId(), |
| 522 | catch: (error) => error, | 573 | catch: (cause) => |
| | 574 | new AgentAwarenessOperationError({ |
| | 575 | operation: "load-device-unregistration-identifier", |
| | 576 | cause, |
| | 577 | }), |
| 523 | }); | 578 | }); |
| 524 | if (!deviceId) { | 579 | if (!deviceId) { |
| 525 | return; | 580 | return; |
| ... | @@ -544,7 +599,11 @@ export function registerLiveActivityPushToken(input: { | ... | @@ -544,7 +599,11 @@ export function registerLiveActivityPushToken(input: { |
| 544 | | 599 | |
| 545 | const activityPushToken = yield* Effect.tryPromise({ | 600 | const activityPushToken = yield* Effect.tryPromise({ |
| 546 | try: () => input.activity.getPushToken(), | 601 | try: () => input.activity.getPushToken(), |
| 547 | catch: (error) => error, | 602 | catch: (cause) => |
| | 603 | new AgentAwarenessOperationError({ |
| | 604 | operation: "read-live-activity-push-token", |
| | 605 | cause, |
| | 606 | }), |
| 548 | }); | 607 | }); |
| 549 | if (!activityPushToken) { | 608 | if (!activityPushToken) { |
| 550 | if (activityPushTokenListeners.has(input.activity)) { | 609 | if (activityPushTokenListeners.has(input.activity)) { |
| ... | @@ -592,7 +651,11 @@ function registerLiveActivityPushTokenValue(input: { | ... | @@ -592,7 +651,11 @@ function registerLiveActivityPushTokenValue(input: { |
| 592 | return Effect.gen(function* () { | 651 | return Effect.gen(function* () { |
| 593 | const deviceId = yield* Effect.tryPromise({ | 652 | const deviceId = yield* Effect.tryPromise({ |
| 594 | try: () => loadOrCreateAgentAwarenessDeviceId(), | 653 | try: () => loadOrCreateAgentAwarenessDeviceId(), |
| 595 | catch: (error) => error, | 654 | catch: (cause) => |
| | 655 | new AgentAwarenessOperationError({ |
| | 656 | operation: "load-live-activity-registration-identifier", |
| | 657 | cause, |
| | 658 | }), |
| 596 | }); | 659 | }); |
| 597 | const registered = yield* registerLiveActivityWithRelay({ | 660 | const registered = yield* registerLiveActivityWithRelay({ |
| 598 | deviceId, | 661 | deviceId, |
| ... | @@ -633,7 +696,11 @@ export function refreshActiveLiveActivityRemoteRegistration(): Effect.Effect< | ... | @@ -633,7 +696,11 @@ export function refreshActiveLiveActivityRemoteRegistration(): Effect.Effect< |
| 633 | | 696 | |
| 634 | const activities = yield* Effect.try({ | 697 | const activities = yield* Effect.try({ |
| 635 | try: () => AgentActivity.getInstances(), | 698 | try: () => AgentActivity.getInstances(), |
| 636 | catch: (error) => error, | 699 | catch: (cause) => |
| | 700 | new AgentAwarenessOperationError({ |
| | 701 | operation: "list-active-live-activities", |
| | 702 | cause, |
| | 703 | }), |
| 637 | }).pipe( | 704 | }).pipe( |
| 638 | Effect.catch((error) => | 705 | Effect.catch((error) => |
| 639 | Effect.sync(() => { | 706 | Effect.sync(() => { |