@fishonfire/bubbles-expo 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,789 @@
1
+ import {
2
+ ANDROID_CHANNEL_IMPORTANCE_VALUES,
3
+ BUBBLES_DELIVERY_STATUSES,
4
+ BUBBLES_NOTIFICATIONS_EXPO_EXTRA_KEY,
5
+ BubblesAppAuthenticationError,
6
+ buildBubblesDeliveryStatusPayload,
7
+ buildCreateDevicePayload,
8
+ buildStoredDeliveryStatusEventKey,
9
+ buildUpdateDevicePayload,
10
+ createBubblesDevice,
11
+ createBubblesDeviceClient,
12
+ createStoredDeliveryStatusEvent,
13
+ describeNotificationPermissionStatus,
14
+ enqueueStoredDeliveryStatusEvent,
15
+ ensureDefaultNotificationChannel,
16
+ ensureNotificationPermissions,
17
+ extractDeviceIdFromDeviceResponse,
18
+ failWithBubblesError,
19
+ flushStoredBubblesDeliveryStatuses,
20
+ getAndroidNotificationChannelImportance,
21
+ getBubblesNotificationDataFromNotification,
22
+ getBubblesNotificationPayloadFromRemoteMessage,
23
+ getBubblesNotificationsRuntimeConfig,
24
+ getDefaultNotificationChannelConfig,
25
+ getErrorMessage,
26
+ getNotificationPermissions,
27
+ getRequiredNonEmptyString,
28
+ getRequiredNonEmptyStringFromSource,
29
+ getSupportedPlatform,
30
+ hasFirebaseDisplayNotification,
31
+ isNotificationPermissionGranted,
32
+ markStoredDeliveryStatusEventPosted,
33
+ normalizeBubblesNotificationPayload,
34
+ patchStoredDeviceState,
35
+ postBubblesDeliveryStatus,
36
+ postStoredBubblesDeliveryStatus,
37
+ readStoredApiBaseUrl,
38
+ readStoredAppKey,
39
+ readStoredDeliveryStatusLedger,
40
+ readStoredDeviceId,
41
+ readStoredDeviceState,
42
+ registerBubblesBackgroundHandlers,
43
+ scheduleBubblesLocalNotification,
44
+ storeApiBaseUrl,
45
+ storeAppKey,
46
+ storeDeliveryStatusLedger,
47
+ storeDeviceId,
48
+ storeDeviceState,
49
+ syncBubblesDevice,
50
+ updateBubblesDevice
51
+ } from "./chunk-6NPXFGQX.mjs";
52
+
53
+ // src/runtime/tokens.ts
54
+ import {
55
+ getMessaging,
56
+ getToken,
57
+ isDeviceRegisteredForRemoteMessages,
58
+ registerDeviceForRemoteMessages
59
+ } from "@react-native-firebase/messaging";
60
+ import { Platform } from "react-native";
61
+ async function getFirebaseMessagingToken(platform) {
62
+ try {
63
+ const messagingInstance = getMessaging();
64
+ if (!isDeviceRegisteredForRemoteMessages(messagingInstance)) {
65
+ await registerDeviceForRemoteMessages(messagingInstance);
66
+ }
67
+ const token = await getToken(messagingInstance);
68
+ return getRequiredNonEmptyStringFromSource(
69
+ token,
70
+ `${platform} FCM token from Firebase Messaging`
71
+ );
72
+ } catch (error) {
73
+ const reason = getErrorMessage(error);
74
+ const platformSpecificSetupHint = platform === "ios" ? " On iOS, also ensure Firebase APNs setup is complete and `expo.ios.googleServicesFile` is configured." : " On Android, ensure `google-services.json` is configured and the native app has been rebuilt.";
75
+ failWithBubblesError(
76
+ `Failed to get a ${platform} FCM token from Firebase Messaging. Ensure "@react-native-firebase/app" and "@react-native-firebase/messaging" are installed, the Expo app includes the "@react-native-firebase/messaging" config plugin, and Firebase is configured for this platform.${platformSpecificSetupHint} Original error: ${reason}`
77
+ );
78
+ }
79
+ }
80
+ async function getDeviceRegistrationState(options) {
81
+ const platform = getSupportedPlatform(Platform.OS);
82
+ const permissions = await getNotificationPermissions(options);
83
+ const permissionStatus = describeNotificationPermissionStatus(permissions);
84
+ const notificationsEnabled = isNotificationPermissionGranted(permissions);
85
+ if (!notificationsEnabled) {
86
+ return {
87
+ platform,
88
+ tokenType: null,
89
+ token: null,
90
+ permissionStatus,
91
+ notificationsEnabled: false
92
+ };
93
+ }
94
+ const token = await getFirebaseMessagingToken(platform);
95
+ return {
96
+ platform,
97
+ tokenType: "fcm",
98
+ token,
99
+ permissionStatus,
100
+ notificationsEnabled: true
101
+ };
102
+ }
103
+ async function getDeviceToken(options) {
104
+ const registrationState = await getDeviceRegistrationState(options);
105
+ if (!registrationState.notificationsEnabled || !registrationState.token) {
106
+ failWithBubblesError("Push notification permission was not granted.");
107
+ }
108
+ return {
109
+ platform: registrationState.platform,
110
+ tokenType: "fcm",
111
+ token: registrationState.token
112
+ };
113
+ }
114
+ async function getFCMToken(options) {
115
+ const tokenResult = await getDeviceToken(options);
116
+ return tokenResult.token;
117
+ }
118
+
119
+ // src/runtime/BubblesNotificationsProvider.tsx
120
+ import { useCallback as useCallback2, useEffect, useRef as useRef2, useState } from "react";
121
+
122
+ // src/runtime/context.ts
123
+ import { createContext } from "react";
124
+ var BubblesNotificationsContext = createContext(null);
125
+
126
+ // src/runtime/notification-handler.ts
127
+ import * as Notifications from "expo-notifications";
128
+ var DEFAULT_FOREGROUND_PRESENTATION = {
129
+ shouldShowBanner: true,
130
+ shouldShowList: true,
131
+ shouldPlaySound: false,
132
+ shouldSetBadge: false
133
+ };
134
+ function applyBubblesNotificationHandler(options) {
135
+ Notifications.setNotificationHandler({
136
+ handleNotification: async (notification) => {
137
+ const presentation = {
138
+ shouldShowBanner: options?.shouldShowBanner ?? DEFAULT_FOREGROUND_PRESENTATION.shouldShowBanner,
139
+ shouldShowList: options?.shouldShowList ?? DEFAULT_FOREGROUND_PRESENTATION.shouldShowList,
140
+ shouldPlaySound: options?.shouldPlaySound ?? DEFAULT_FOREGROUND_PRESENTATION.shouldPlaySound,
141
+ shouldSetBadge: options?.shouldSetBadge ?? DEFAULT_FOREGROUND_PRESENTATION.shouldSetBadge
142
+ };
143
+ return presentation;
144
+ }
145
+ });
146
+ }
147
+ function clearBubblesNotificationHandler() {
148
+ Notifications.setNotificationHandler(null);
149
+ }
150
+
151
+ // src/runtime/response-observer.ts
152
+ import * as Notifications2 from "expo-notifications";
153
+ function buildNotificationResponseKey(response) {
154
+ const payload = normalizeBubblesNotificationPayload(
155
+ getBubblesNotificationDataFromNotification(response.notification)
156
+ );
157
+ if (payload.notificationId) {
158
+ return `notification:${payload.notificationId}:action:${response.actionIdentifier}`;
159
+ }
160
+ return `request:${response.notification.request.identifier}:action:${response.actionIdentifier}`;
161
+ }
162
+ function logNotificationResponseError(description, error) {
163
+ console.error(
164
+ `[@fishonfire/bubbles-expo] Failed during notification response ${description}.`,
165
+ error
166
+ );
167
+ }
168
+ function buildObservedNotificationResponse(response, storedDeviceState) {
169
+ const payload = normalizeBubblesNotificationPayload(
170
+ getBubblesNotificationDataFromNotification(response.notification)
171
+ );
172
+ return {
173
+ notificationId: payload.notificationId,
174
+ event: {
175
+ notification: response.notification,
176
+ url: payload.url,
177
+ notificationId: payload.notificationId,
178
+ deviceId: storedDeviceState.deviceId,
179
+ data: payload.data
180
+ }
181
+ };
182
+ }
183
+ function postNotificationClickedDeliveryStatus(storedDeviceState, notificationId, actionId) {
184
+ void postStoredBubblesDeliveryStatus({
185
+ source: "notification response",
186
+ storedDeviceState,
187
+ notificationId,
188
+ status: BUBBLES_DELIVERY_STATUSES.notificationClicked,
189
+ actionId
190
+ }).catch((error) => {
191
+ logNotificationResponseError("delivery-status update", error);
192
+ });
193
+ }
194
+ function handleNotificationResponse(response, options, source) {
195
+ const storedDeviceState = readStoredDeviceState();
196
+ const observedResponse = buildObservedNotificationResponse(
197
+ response,
198
+ storedDeviceState
199
+ );
200
+ postNotificationClickedDeliveryStatus(
201
+ storedDeviceState,
202
+ observedResponse.notificationId,
203
+ response.actionIdentifier
204
+ );
205
+ options.onNotificationResponse?.(observedResponse.event);
206
+ }
207
+ function processStartupNotificationResponse(processNotificationResponse) {
208
+ try {
209
+ const lastResponse = Notifications2.getLastNotificationResponse();
210
+ if (!lastResponse) {
211
+ return;
212
+ }
213
+ processNotificationResponse(lastResponse);
214
+ Notifications2.clearLastNotificationResponse();
215
+ } catch (error) {
216
+ logNotificationResponseError("initial response handling", error);
217
+ }
218
+ }
219
+ function observeBubblesNotificationResponses(options) {
220
+ let lastHandledResponseKey = null;
221
+ const processNotificationResponse = (response) => {
222
+ const responseKey = buildNotificationResponseKey(response);
223
+ if (lastHandledResponseKey === responseKey) {
224
+ return;
225
+ }
226
+ lastHandledResponseKey = responseKey;
227
+ handleNotificationResponse(response, options, "listener");
228
+ };
229
+ processStartupNotificationResponse((response) => {
230
+ const responseKey = buildNotificationResponseKey(response);
231
+ lastHandledResponseKey = responseKey;
232
+ handleNotificationResponse(response, options, "startup");
233
+ });
234
+ const subscription = Notifications2.addNotificationResponseReceivedListener(
235
+ processNotificationResponse
236
+ );
237
+ return () => {
238
+ subscription.remove();
239
+ };
240
+ }
241
+
242
+ // src/runtime/presentation.ts
243
+ function applyBubblesForegroundPresentation(options) {
244
+ applyBubblesNotificationHandler(options);
245
+ }
246
+ function clearBubblesForegroundPresentation() {
247
+ clearBubblesNotificationHandler();
248
+ }
249
+ function observeBubblesNotificationOpenEvents(options) {
250
+ return observeBubblesNotificationResponses(options);
251
+ }
252
+ async function prepareBubblesNotificationPresentation(options) {
253
+ return ensureDefaultNotificationChannel(options);
254
+ }
255
+
256
+ // src/runtime/foreground-message-observer.ts
257
+ import {
258
+ getMessaging as getMessaging2,
259
+ onMessage
260
+ } from "@react-native-firebase/messaging";
261
+ import { Platform as Platform2 } from "react-native";
262
+ function supportsFirebaseForegroundMessages() {
263
+ return Platform2.OS === "android" || Platform2.OS === "ios";
264
+ }
265
+ async function handleFirebaseForegroundMessage(remoteMessage) {
266
+ const payload = getBubblesNotificationPayloadFromRemoteMessage(remoteMessage);
267
+ if (!hasFirebaseDisplayNotification(remoteMessage)) {
268
+ return;
269
+ }
270
+ await scheduleBubblesLocalNotification({
271
+ source: "Firebase foreground message",
272
+ payload
273
+ });
274
+ }
275
+ function observeBubblesForegroundMessages() {
276
+ if (!supportsFirebaseForegroundMessages()) {
277
+ return () => {
278
+ };
279
+ }
280
+ const unsubscribe = onMessage(
281
+ getMessaging2(),
282
+ (remoteMessage) => {
283
+ void handleFirebaseForegroundMessage(remoteMessage).catch(
284
+ (error) => {
285
+ console.error(
286
+ "[@fishonfire/bubbles-expo] Failed while handling a Firebase foreground message.",
287
+ error
288
+ );
289
+ }
290
+ );
291
+ }
292
+ );
293
+ return () => {
294
+ unsubscribe();
295
+ };
296
+ }
297
+
298
+ // src/runtime/installations.ts
299
+ import { getId, getInstallations } from "@react-native-firebase/installations";
300
+ async function getFirebaseInstallationId(platform) {
301
+ try {
302
+ const installationsInstance = getInstallations();
303
+ const installationId = await getId(installationsInstance);
304
+ return getRequiredNonEmptyStringFromSource(
305
+ installationId,
306
+ `${platform} Firebase installation id from Firebase Installations`
307
+ );
308
+ } catch (error) {
309
+ const reason = getErrorMessage(error);
310
+ const platformSpecificSetupHint = platform === "ios" ? " On iOS, ensure `expo.ios.googleServicesFile` is configured and the native app has been rebuilt." : " On Android, ensure `expo.android.googleServicesFile` is configured and the native app has been rebuilt.";
311
+ failWithBubblesError(
312
+ `Failed to get a ${platform} Firebase installation id from Firebase Installations. Ensure "@react-native-firebase/app" and "@react-native-firebase/installations" are installed and Firebase is configured for this platform.${platformSpecificSetupHint} Original error: ${reason}`
313
+ );
314
+ }
315
+ }
316
+
317
+ // src/runtime/transport.ts
318
+ function observeBubblesForegroundRemoteMessages() {
319
+ return observeBubblesForegroundMessages();
320
+ }
321
+ async function getBubblesDeviceRegistrationState(options) {
322
+ return getDeviceRegistrationState(options);
323
+ }
324
+ async function getBubblesInstallationId(platform) {
325
+ return getFirebaseInstallationId(platform);
326
+ }
327
+
328
+ // src/runtime/sync-device.ts
329
+ var BubblesNotificationsSyncError = class extends Error {
330
+ constructor(message, snapshot, cause) {
331
+ super(message);
332
+ this.name = "BubblesNotificationsSyncError";
333
+ this.snapshot = snapshot;
334
+ this.cause = cause;
335
+ }
336
+ };
337
+ function fail(message) {
338
+ throw new Error(`[@fishonfire/bubbles-expo] ${message}`);
339
+ }
340
+ function toError(error) {
341
+ if (error instanceof Error) {
342
+ return error;
343
+ }
344
+ return new Error(String(error));
345
+ }
346
+ function getRequiredNonEmptyString2(value, fieldName) {
347
+ if (typeof value !== "string") {
348
+ fail(`"${fieldName}" must be a string.`);
349
+ }
350
+ const trimmedValue = value.trim();
351
+ if (!trimmedValue) {
352
+ fail(`"${fieldName}" must not be empty.`);
353
+ }
354
+ return trimmedValue;
355
+ }
356
+ function buildRuntimeSnapshot(deviceId, registrationState) {
357
+ return {
358
+ deviceId,
359
+ pushToken: registrationState.token,
360
+ tokenType: registrationState.tokenType,
361
+ permissionStatus: registrationState.permissionStatus,
362
+ notificationsEnabled: registrationState.notificationsEnabled
363
+ };
364
+ }
365
+ async function syncDeviceRegistrationState(options) {
366
+ const apiBaseUrl = getRequiredNonEmptyString2(options.apiBaseUrl, "apiBaseUrl");
367
+ const appKey = getRequiredNonEmptyString2(options.appKey, "appKey");
368
+ storeApiBaseUrl(apiBaseUrl);
369
+ storeAppKey(appKey);
370
+ const snapshot = buildRuntimeSnapshot(
371
+ options.deviceId,
372
+ options.registrationState
373
+ );
374
+ try {
375
+ const installationId = options.registrationState.notificationsEnabled ? await getBubblesInstallationId(options.registrationState.platform) : null;
376
+ const syncResult = await syncBubblesDevice({
377
+ apiBaseUrl,
378
+ appId: options.appId,
379
+ appKey,
380
+ userId: options.userId,
381
+ aliasing: options.aliasing,
382
+ appVersion: options.appVersion,
383
+ deviceId: options.deviceId,
384
+ platform: options.registrationState.platform,
385
+ pushToken: options.registrationState.token,
386
+ fid: installationId,
387
+ notificationsEnabled: options.registrationState.notificationsEnabled
388
+ });
389
+ const nextDeviceId = syncResult.deviceId ?? options.deviceId;
390
+ storeDeviceId(nextDeviceId);
391
+ return {
392
+ ...snapshot,
393
+ action: syncResult.action,
394
+ deviceId: nextDeviceId
395
+ };
396
+ } catch (error) {
397
+ const normalizedError = toError(error);
398
+ throw new BubblesNotificationsSyncError(
399
+ normalizedError.message,
400
+ snapshot,
401
+ error
402
+ );
403
+ }
404
+ }
405
+ async function syncExistingBubblesDevice(options) {
406
+ const registrationState = await getBubblesDeviceRegistrationState({
407
+ requestPermissions: false
408
+ });
409
+ return syncDeviceRegistrationState({
410
+ ...options,
411
+ deviceId: options.deviceId,
412
+ registrationState
413
+ });
414
+ }
415
+
416
+ // src/runtime/register-device.ts
417
+ var DEFAULT_PERMISSION_REQUEST_OPTIONS = {
418
+ ios: {
419
+ allowAlert: true,
420
+ allowBadge: true,
421
+ allowSound: true
422
+ }
423
+ };
424
+ function buildRegistrationOptions(options) {
425
+ if (options?.requestPermissions === false) {
426
+ return {
427
+ requestPermissions: false,
428
+ permissionRequestOptions: options.permissionRequestOptions
429
+ };
430
+ }
431
+ return {
432
+ requestPermissions: options?.requestPermissions,
433
+ permissionRequestOptions: options?.permissionRequestOptions ?? DEFAULT_PERMISSION_REQUEST_OPTIONS
434
+ };
435
+ }
436
+ async function registerBubblesDevice(options) {
437
+ const registrationState = await getBubblesDeviceRegistrationState(
438
+ buildRegistrationOptions(options.registrationOptions)
439
+ );
440
+ return syncDeviceRegistrationState({
441
+ ...options,
442
+ deviceId: options.deviceId,
443
+ registrationState
444
+ });
445
+ }
446
+
447
+ // src/runtime/provider-operation-queue.ts
448
+ import {
449
+ useCallback,
450
+ useRef
451
+ } from "react";
452
+
453
+ // src/runtime/provider-state.ts
454
+ function createInitialRuntimeState(deviceId, error) {
455
+ return {
456
+ deviceId,
457
+ pushToken: null,
458
+ tokenType: null,
459
+ permissionStatus: "unknown",
460
+ notificationsEnabled: false,
461
+ isSyncing: false,
462
+ error
463
+ };
464
+ }
465
+ function normalizeProviderError(error) {
466
+ if (error instanceof Error) {
467
+ return error;
468
+ }
469
+ return new Error(String(error));
470
+ }
471
+ function normalizeUserId(value) {
472
+ if (value === null) {
473
+ return null;
474
+ }
475
+ const trimmedValue = value.trim();
476
+ return trimmedValue.length > 0 ? trimmedValue : null;
477
+ }
478
+ function getAutomaticSyncSignature(input) {
479
+ return JSON.stringify({
480
+ appId: input.appId,
481
+ appKey: input.appKey,
482
+ apiBaseUrl: input.apiBaseUrl,
483
+ userId: input.userId,
484
+ aliasing: input.aliasing ?? [],
485
+ appVersion: input.appVersion ?? null,
486
+ deviceId: input.deviceId
487
+ });
488
+ }
489
+ function getRuntimeStateUpdate(snapshot) {
490
+ return {
491
+ deviceId: snapshot.deviceId,
492
+ pushToken: snapshot.pushToken,
493
+ tokenType: snapshot.tokenType,
494
+ permissionStatus: snapshot.permissionStatus,
495
+ notificationsEnabled: snapshot.notificationsEnabled
496
+ };
497
+ }
498
+ function getInitialRuntimeState() {
499
+ try {
500
+ return createInitialRuntimeState(readStoredDeviceId(), null);
501
+ } catch (error) {
502
+ return createInitialRuntimeState(
503
+ null,
504
+ normalizeProviderError(error)
505
+ );
506
+ }
507
+ }
508
+
509
+ // src/runtime/provider-operation-queue.ts
510
+ function useBubblesRuntimeOperationQueue(setState) {
511
+ const isMountedRef = useRef(true);
512
+ const pendingOperationsRef = useRef(0);
513
+ const operationQueueRef = useRef(Promise.resolve());
514
+ const enqueueOperation = useCallback((operation) => {
515
+ pendingOperationsRef.current += 1;
516
+ setState((currentState) => ({
517
+ ...currentState,
518
+ isSyncing: true,
519
+ error: null
520
+ }));
521
+ const queuedOperation = operationQueueRef.current.catch(() => void 0).then(operation);
522
+ operationQueueRef.current = queuedOperation.then(
523
+ () => void 0,
524
+ () => void 0
525
+ );
526
+ return queuedOperation.then(
527
+ (result) => {
528
+ pendingOperationsRef.current -= 1;
529
+ if (!isMountedRef.current) {
530
+ return;
531
+ }
532
+ setState((currentState) => ({
533
+ ...currentState,
534
+ ...result ?? {},
535
+ isSyncing: pendingOperationsRef.current > 0,
536
+ error: null
537
+ }));
538
+ },
539
+ (error) => {
540
+ pendingOperationsRef.current -= 1;
541
+ const normalizedError = normalizeProviderError(error);
542
+ const snapshot = error instanceof BubblesNotificationsSyncError ? error.snapshot : void 0;
543
+ if (isMountedRef.current) {
544
+ setState((currentState) => ({
545
+ ...currentState,
546
+ ...snapshot ? getRuntimeStateUpdate(snapshot) : {},
547
+ isSyncing: pendingOperationsRef.current > 0,
548
+ error: normalizedError
549
+ }));
550
+ }
551
+ throw normalizedError;
552
+ }
553
+ );
554
+ }, [setState]);
555
+ return {
556
+ enqueueOperation,
557
+ isMountedRef
558
+ };
559
+ }
560
+
561
+ // src/runtime/BubblesNotificationsProvider.tsx
562
+ import { jsx } from "react/jsx-runtime";
563
+ function BubblesNotificationsProvider(props) {
564
+ const {
565
+ appId,
566
+ appKey,
567
+ apiBaseUrl,
568
+ ready,
569
+ userId,
570
+ aliasing,
571
+ appVersion,
572
+ onLogout,
573
+ foregroundPresentation,
574
+ onNotificationResponse,
575
+ children
576
+ } = props;
577
+ const normalizedUserId = normalizeUserId(userId);
578
+ const [state, setState] = useState(getInitialRuntimeState);
579
+ const { enqueueOperation, isMountedRef } = useBubblesRuntimeOperationQueue(setState);
580
+ const lastAutomaticSyncSignatureRef = useRef2(null);
581
+ const previousUserIdRef = useRef2(normalizedUserId);
582
+ const buildAutomaticSyncSignature = useCallback2((deviceId) => {
583
+ return getAutomaticSyncSignature({
584
+ appId,
585
+ appKey,
586
+ apiBaseUrl,
587
+ userId: normalizedUserId,
588
+ aliasing,
589
+ appVersion,
590
+ deviceId
591
+ });
592
+ }, [
593
+ appId,
594
+ appKey,
595
+ apiBaseUrl,
596
+ normalizedUserId,
597
+ aliasing,
598
+ appVersion
599
+ ]);
600
+ useEffect(() => {
601
+ return () => {
602
+ isMountedRef.current = false;
603
+ clearBubblesForegroundPresentation();
604
+ };
605
+ }, [isMountedRef]);
606
+ useEffect(() => {
607
+ let isCancelled = false;
608
+ void (async () => {
609
+ try {
610
+ storeApiBaseUrl(getRequiredNonEmptyString(apiBaseUrl, "apiBaseUrl"));
611
+ storeAppKey(getRequiredNonEmptyString(appKey, "appKey"));
612
+ await prepareBubblesNotificationPresentation();
613
+ } catch (error) {
614
+ if (!isCancelled && isMountedRef.current) {
615
+ setState((currentState) => ({
616
+ ...currentState,
617
+ error: normalizeProviderError(error)
618
+ }));
619
+ }
620
+ }
621
+ })();
622
+ return () => {
623
+ isCancelled = true;
624
+ };
625
+ }, [apiBaseUrl, appKey, isMountedRef]);
626
+ useEffect(() => {
627
+ applyBubblesForegroundPresentation(foregroundPresentation);
628
+ }, [foregroundPresentation]);
629
+ useEffect(() => {
630
+ return observeBubblesForegroundRemoteMessages();
631
+ }, []);
632
+ useEffect(() => {
633
+ return observeBubblesNotificationOpenEvents({
634
+ onNotificationResponse
635
+ });
636
+ }, [onNotificationResponse]);
637
+ useEffect(() => {
638
+ const previousUserId = previousUserIdRef.current;
639
+ if (previousUserId !== null && normalizedUserId === null) {
640
+ lastAutomaticSyncSignatureRef.current = null;
641
+ if (onLogout) {
642
+ void enqueueOperation(async () => {
643
+ await onLogout({
644
+ previousUserId,
645
+ deviceId: state.deviceId
646
+ });
647
+ });
648
+ }
649
+ }
650
+ previousUserIdRef.current = normalizedUserId;
651
+ }, [enqueueOperation, normalizedUserId, onLogout, state.deviceId]);
652
+ useEffect(() => {
653
+ if (!ready || normalizedUserId === null || state.deviceId === null) {
654
+ lastAutomaticSyncSignatureRef.current = null;
655
+ return;
656
+ }
657
+ const syncSignature = buildAutomaticSyncSignature(state.deviceId);
658
+ if (lastAutomaticSyncSignatureRef.current === syncSignature) {
659
+ return;
660
+ }
661
+ lastAutomaticSyncSignatureRef.current = syncSignature;
662
+ const storedDeviceId = state.deviceId;
663
+ void enqueueOperation(async () => {
664
+ const result = await syncExistingBubblesDevice({
665
+ appId,
666
+ appKey,
667
+ apiBaseUrl,
668
+ userId: normalizedUserId,
669
+ aliasing,
670
+ appVersion,
671
+ deviceId: storedDeviceId
672
+ });
673
+ return getRuntimeStateUpdate(result);
674
+ });
675
+ }, [
676
+ ready,
677
+ normalizedUserId,
678
+ state.deviceId,
679
+ appId,
680
+ appKey,
681
+ apiBaseUrl,
682
+ aliasing,
683
+ appVersion,
684
+ buildAutomaticSyncSignature,
685
+ enqueueOperation
686
+ ]);
687
+ function registerDevice(options) {
688
+ return enqueueOperation(async () => {
689
+ if (!ready) {
690
+ failWithBubblesError(
691
+ '"registerDevice()" requires "ready" to be true before the device can be synced.'
692
+ );
693
+ }
694
+ if (normalizedUserId === null) {
695
+ failWithBubblesError(
696
+ '"registerDevice()" requires a non-null "userId" before the device can be synced.'
697
+ );
698
+ }
699
+ const result = await registerBubblesDevice({
700
+ appId,
701
+ appKey,
702
+ apiBaseUrl,
703
+ userId: normalizedUserId,
704
+ aliasing,
705
+ appVersion,
706
+ deviceId: state.deviceId,
707
+ registrationOptions: options
708
+ });
709
+ if (result.deviceId !== null) {
710
+ lastAutomaticSyncSignatureRef.current = buildAutomaticSyncSignature(
711
+ result.deviceId
712
+ );
713
+ }
714
+ return getRuntimeStateUpdate(result);
715
+ });
716
+ }
717
+ const contextValue = {
718
+ registerDevice,
719
+ deviceId: state.deviceId,
720
+ pushToken: state.pushToken,
721
+ tokenType: state.tokenType,
722
+ permissionStatus: state.permissionStatus,
723
+ notificationsEnabled: state.notificationsEnabled,
724
+ isSyncing: state.isSyncing,
725
+ error: state.error
726
+ };
727
+ return /* @__PURE__ */ jsx(BubblesNotificationsContext.Provider, { value: contextValue, children });
728
+ }
729
+
730
+ // src/runtime/useBubblesNotifications.ts
731
+ import { useContext } from "react";
732
+ function fail2(message) {
733
+ throw new Error(`[@fishonfire/bubbles-expo] ${message}`);
734
+ }
735
+ function useBubblesNotifications() {
736
+ const context = useContext(BubblesNotificationsContext);
737
+ if (context === null) {
738
+ fail2(
739
+ '"useBubblesNotifications()" must be used inside "BubblesNotificationsProvider".'
740
+ );
741
+ }
742
+ return context;
743
+ }
744
+ export {
745
+ ANDROID_CHANNEL_IMPORTANCE_VALUES,
746
+ BUBBLES_DELIVERY_STATUSES,
747
+ BUBBLES_NOTIFICATIONS_EXPO_EXTRA_KEY,
748
+ BubblesAppAuthenticationError,
749
+ BubblesNotificationsProvider,
750
+ buildBubblesDeliveryStatusPayload,
751
+ buildCreateDevicePayload,
752
+ buildStoredDeliveryStatusEventKey,
753
+ buildUpdateDevicePayload,
754
+ createBubblesDevice,
755
+ createBubblesDeviceClient,
756
+ createStoredDeliveryStatusEvent,
757
+ describeNotificationPermissionStatus,
758
+ enqueueStoredDeliveryStatusEvent,
759
+ ensureDefaultNotificationChannel,
760
+ ensureNotificationPermissions,
761
+ extractDeviceIdFromDeviceResponse,
762
+ flushStoredBubblesDeliveryStatuses,
763
+ getAndroidNotificationChannelImportance,
764
+ getBubblesNotificationsRuntimeConfig,
765
+ getDefaultNotificationChannelConfig,
766
+ getDeviceRegistrationState,
767
+ getDeviceToken,
768
+ getFCMToken,
769
+ getNotificationPermissions,
770
+ isNotificationPermissionGranted,
771
+ markStoredDeliveryStatusEventPosted,
772
+ patchStoredDeviceState,
773
+ postBubblesDeliveryStatus,
774
+ postStoredBubblesDeliveryStatus,
775
+ readStoredApiBaseUrl,
776
+ readStoredAppKey,
777
+ readStoredDeliveryStatusLedger,
778
+ readStoredDeviceId,
779
+ readStoredDeviceState,
780
+ registerBubblesBackgroundHandlers,
781
+ storeApiBaseUrl,
782
+ storeAppKey,
783
+ storeDeliveryStatusLedger,
784
+ storeDeviceId,
785
+ storeDeviceState,
786
+ syncBubblesDevice,
787
+ updateBubblesDevice,
788
+ useBubblesNotifications
789
+ };