@fishonfire/bubbles-expo 0.1.0 → 0.1.3
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/README.md +19 -2
- package/dist/{chunk-6NPXFGQX.mjs → chunk-3BAGWXHY.mjs} +85 -0
- package/dist/index.d.mts +15 -2
- package/dist/index.d.ts +15 -2
- package/dist/index.js +227 -41
- package/dist/index.mjs +143 -47
- package/dist/register-task.mjs +1 -1
- package/package.json +3 -1
- package/plugin/build/index.js +2 -0
- package/plugin/build/with-firebase-messaging-manifest.js +36 -0
- package/plugin/build/with-firebase-messaging-registration.d.ts +4 -0
- package/plugin/build/with-firebase-messaging-registration.js +118 -0
package/README.md
CHANGED
|
@@ -70,6 +70,8 @@ Plugin options:
|
|
|
70
70
|
|
|
71
71
|
For Android remote display, backend payloads must use the same `android.notification.channel_id` as `defaultChannelId`. The plugin writes Firebase's default-channel manifest metadata, and the package creates the actual Android notification channel when `BubblesNotificationsProvider`, `getNotificationPermissions()`, or `ensureDefaultNotificationChannel()` runs. On a fresh install, the app must be opened at least once before remote notifications can reliably use this configured channel; otherwise Android/Firebase may fall back to its own default behavior before JavaScript has created the channel.
|
|
72
72
|
|
|
73
|
+
Device registration uses the Firebase Installation ID (FID) as the push registration identifier. The package does not call Firebase Messaging's deprecated `getToken()` path for Bubbles device sync; the same FID value is sent as both `push_token` and `fid`. On Android, the config plugin also opts into FID-based Firebase Messaging registration and installs native startup code that calls `FirebaseMessaging.getInstance().register()` automatically.
|
|
74
|
+
|
|
73
75
|
## Background registration
|
|
74
76
|
|
|
75
77
|
Register the package-owned background notification handlers once from your app entrypoint:
|
|
@@ -162,10 +164,25 @@ export function EnableNotificationsButton() {
|
|
|
162
164
|
}
|
|
163
165
|
```
|
|
164
166
|
|
|
165
|
-
`useBubblesNotifications()` also exposes `deviceId`, `pushToken`, `permissionStatus`, `notificationsEnabled`, `tokenType`, and `error`.
|
|
167
|
+
`useBubblesNotifications()` also exposes `addDeviceAttribute`, `deviceId`, `pushToken`, `permissionStatus`, `notificationsEnabled`, `tokenType`, and `error`. When notifications are enabled, `pushToken` is the Firebase Installation ID and `tokenType` is `"fid"`.
|
|
166
168
|
|
|
167
169
|
If your app already requested notification permissions, call `registerDevice({ requestPermissions: false })`.
|
|
168
170
|
|
|
171
|
+
## Add custom device attributes
|
|
172
|
+
|
|
173
|
+
After the provider has a backend `deviceId`, apps can add their own device attributes:
|
|
174
|
+
|
|
175
|
+
```tsx
|
|
176
|
+
const { addDeviceAttribute, deviceId } = useBubblesNotifications();
|
|
177
|
+
|
|
178
|
+
if (deviceId) {
|
|
179
|
+
void addDeviceAttribute('plan', {
|
|
180
|
+
tier: 'pro',
|
|
181
|
+
seats: 4,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
169
186
|
## Example application
|
|
170
187
|
A example application can be found at: https://github.com/fishonfire/bubbles-notifications-expo-example
|
|
171
188
|
|
|
@@ -177,4 +194,4 @@ A example application can be found at: https://github.com/fishonfire/bubbles-not
|
|
|
177
194
|
## Copyright and Licence
|
|
178
195
|
Copyright (c) 2026, Fish on Fire.
|
|
179
196
|
|
|
180
|
-
Source code is licensed under the [`GPL License`](https://github.com/fishonfire/bubbles-notifications-expo/blob/develop/LICENSE).
|
|
197
|
+
Source code is licensed under the [`GPL License`](https://github.com/fishonfire/bubbles-notifications-expo/blob/develop/LICENSE).
|
|
@@ -458,6 +458,78 @@ function throwAppAuthenticationError(error) {
|
|
|
458
458
|
}
|
|
459
459
|
throw error;
|
|
460
460
|
}
|
|
461
|
+
function normalizeDeviceAttributes(value) {
|
|
462
|
+
return normalizeDeviceAttributeObject(value, "attributes", /* @__PURE__ */ new WeakSet());
|
|
463
|
+
}
|
|
464
|
+
function normalizeDeviceAttributeValue(value, sourceDescription, seenValues) {
|
|
465
|
+
if (value === null) {
|
|
466
|
+
return null;
|
|
467
|
+
}
|
|
468
|
+
if (typeof value === "string" || typeof value === "boolean") {
|
|
469
|
+
return value;
|
|
470
|
+
}
|
|
471
|
+
if (typeof value === "number") {
|
|
472
|
+
if (!Number.isFinite(value)) {
|
|
473
|
+
failWithBubblesError(`${sourceDescription} must be a finite number.`);
|
|
474
|
+
}
|
|
475
|
+
return value;
|
|
476
|
+
}
|
|
477
|
+
if (Array.isArray(value)) {
|
|
478
|
+
if (seenValues.has(value)) {
|
|
479
|
+
failWithBubblesError(`${sourceDescription} must not contain cycles.`);
|
|
480
|
+
}
|
|
481
|
+
seenValues.add(value);
|
|
482
|
+
const normalizedValue = value.map(
|
|
483
|
+
(entry, index) => normalizeDeviceAttributeValue(
|
|
484
|
+
entry,
|
|
485
|
+
`${sourceDescription}[${index}]`,
|
|
486
|
+
seenValues
|
|
487
|
+
)
|
|
488
|
+
);
|
|
489
|
+
seenValues.delete(value);
|
|
490
|
+
return normalizedValue;
|
|
491
|
+
}
|
|
492
|
+
if (isPlainObject(value)) {
|
|
493
|
+
return normalizeDeviceAttributeObject(
|
|
494
|
+
value,
|
|
495
|
+
sourceDescription,
|
|
496
|
+
seenValues
|
|
497
|
+
);
|
|
498
|
+
}
|
|
499
|
+
failWithBubblesError(
|
|
500
|
+
`${sourceDescription} must be a JSON-compatible value.`
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
function normalizeDeviceAttributeObject(value, sourceDescription, seenValues) {
|
|
504
|
+
if (!isPlainObject(value)) {
|
|
505
|
+
failWithBubblesError('"attributes" must be an object.');
|
|
506
|
+
}
|
|
507
|
+
const valuePrototype = Object.getPrototypeOf(value);
|
|
508
|
+
if (valuePrototype !== Object.prototype && valuePrototype !== null) {
|
|
509
|
+
failWithBubblesError(
|
|
510
|
+
`${sourceDescription} must be a JSON-compatible object.`
|
|
511
|
+
);
|
|
512
|
+
}
|
|
513
|
+
if (Object.getOwnPropertySymbols(value).length > 0) {
|
|
514
|
+
failWithBubblesError(
|
|
515
|
+
`${sourceDescription} must not contain symbol keys.`
|
|
516
|
+
);
|
|
517
|
+
}
|
|
518
|
+
if (seenValues.has(value)) {
|
|
519
|
+
failWithBubblesError(`${sourceDescription} must not contain cycles.`);
|
|
520
|
+
}
|
|
521
|
+
seenValues.add(value);
|
|
522
|
+
const normalizedValue = {};
|
|
523
|
+
for (const [attributeName, attributeValue] of Object.entries(value)) {
|
|
524
|
+
normalizedValue[attributeName] = normalizeDeviceAttributeValue(
|
|
525
|
+
attributeValue,
|
|
526
|
+
`${sourceDescription}.${attributeName}`,
|
|
527
|
+
seenValues
|
|
528
|
+
);
|
|
529
|
+
}
|
|
530
|
+
seenValues.delete(value);
|
|
531
|
+
return normalizedValue;
|
|
532
|
+
}
|
|
461
533
|
function extractDeviceIdFromDeviceResponse(value) {
|
|
462
534
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
463
535
|
return null;
|
|
@@ -497,6 +569,18 @@ async function updateBubblesDevice(options) {
|
|
|
497
569
|
throwAppAuthenticationError(error);
|
|
498
570
|
}
|
|
499
571
|
}
|
|
572
|
+
async function updateBubblesDeviceAttributes(options) {
|
|
573
|
+
const client = resolveDeviceClient(options);
|
|
574
|
+
try {
|
|
575
|
+
return await client.updateDeviceAttributes(
|
|
576
|
+
normalizeIdentifier(options.deviceId, "deviceId"),
|
|
577
|
+
normalizeDeviceAttributes(options.attributes),
|
|
578
|
+
withAppKeyHeader(options.appKey, options.requestOptions)
|
|
579
|
+
);
|
|
580
|
+
} catch (error) {
|
|
581
|
+
throwAppAuthenticationError(error);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
500
584
|
async function syncBubblesDevice(options) {
|
|
501
585
|
const client = resolveDeviceClient(options);
|
|
502
586
|
if (options.deviceId !== null) {
|
|
@@ -1279,6 +1363,7 @@ export {
|
|
|
1279
1363
|
extractDeviceIdFromDeviceResponse,
|
|
1280
1364
|
createBubblesDevice,
|
|
1281
1365
|
updateBubblesDevice,
|
|
1366
|
+
updateBubblesDeviceAttributes,
|
|
1282
1367
|
syncBubblesDevice,
|
|
1283
1368
|
BUBBLES_DELIVERY_STATUSES,
|
|
1284
1369
|
buildBubblesDeliveryStatusPayload,
|
package/dist/index.d.mts
CHANGED
|
@@ -93,10 +93,20 @@ interface SyncBubblesDeviceResult<TResponse = unknown> {
|
|
|
93
93
|
deviceId: string | null;
|
|
94
94
|
response: TResponse;
|
|
95
95
|
}
|
|
96
|
+
type BubblesDeviceAttributeValue = string | number | boolean | null | BubblesDeviceAttributeValue[] | {
|
|
97
|
+
[key: string]: BubblesDeviceAttributeValue;
|
|
98
|
+
};
|
|
99
|
+
type BubblesDeviceAttributes = Record<string, BubblesDeviceAttributeValue>;
|
|
100
|
+
interface UpdateBubblesDeviceAttributesOptions extends BubblesDeviceClientInput {
|
|
101
|
+
deviceId: string | number;
|
|
102
|
+
attributes: BubblesDeviceAttributes;
|
|
103
|
+
requestOptions?: RequestOptions;
|
|
104
|
+
}
|
|
96
105
|
declare function createBubblesDeviceClient(options: BubblesDeviceClientOptions): DeviceClient;
|
|
97
106
|
declare function extractDeviceIdFromDeviceResponse(value: unknown): string | null;
|
|
98
107
|
declare function createBubblesDevice<TResponse = unknown>(options: CreateBubblesDeviceOptions): Promise<TResponse>;
|
|
99
108
|
declare function updateBubblesDevice<TResponse = unknown>(options: UpdateBubblesDeviceOptions): Promise<TResponse>;
|
|
109
|
+
declare function updateBubblesDeviceAttributes<TResponse = unknown>(options: UpdateBubblesDeviceAttributesOptions): Promise<TResponse>;
|
|
100
110
|
declare function syncBubblesDevice<TResponse = unknown>(options: SyncBubblesDeviceOptions): Promise<SyncBubblesDeviceResult<TResponse>>;
|
|
101
111
|
|
|
102
112
|
type DeviceClientFactoryOptions = Omit<DeviceClientOptions, 'baseUrl'>;
|
|
@@ -202,7 +212,7 @@ declare function ensureNotificationPermissions(options?: GetNotificationPermissi
|
|
|
202
212
|
|
|
203
213
|
type SupportedPlatform = 'android' | 'ios';
|
|
204
214
|
|
|
205
|
-
type NativeTokenType = '
|
|
215
|
+
type NativeTokenType = 'fid';
|
|
206
216
|
type GetDeviceTokenOptions = GetNotificationPermissionsOptions;
|
|
207
217
|
interface DeviceTokenResult {
|
|
208
218
|
platform: SupportedPlatform;
|
|
@@ -220,6 +230,8 @@ declare function getDeviceRegistrationState(options?: GetDeviceTokenOptions): Pr
|
|
|
220
230
|
declare function getDeviceToken(options?: GetDeviceTokenOptions): Promise<DeviceTokenResult>;
|
|
221
231
|
declare function getFCMToken(options?: GetDeviceTokenOptions): Promise<string>;
|
|
222
232
|
|
|
233
|
+
declare function collectBubblesDeviceAttributes(): Promise<BubblesDeviceAttributes>;
|
|
234
|
+
|
|
223
235
|
interface RegisterDeviceOptions {
|
|
224
236
|
requestPermissions?: boolean;
|
|
225
237
|
permissionRequestOptions?: PermissionRequestOptions;
|
|
@@ -256,6 +268,7 @@ interface BubblesNotificationsProviderProps {
|
|
|
256
268
|
}
|
|
257
269
|
interface BubblesNotificationsContextValue {
|
|
258
270
|
registerDevice: (options?: RegisterDeviceOptions) => Promise<void>;
|
|
271
|
+
addDeviceAttribute: (name: string, value: BubblesDeviceAttributeValue) => Promise<void>;
|
|
259
272
|
deviceId: string | null;
|
|
260
273
|
pushToken: string | null;
|
|
261
274
|
tokenType: NativeTokenType | null;
|
|
@@ -303,4 +316,4 @@ interface BubblesNotificationPayload {
|
|
|
303
316
|
url: string | null;
|
|
304
317
|
}
|
|
305
318
|
|
|
306
|
-
export { ANDROID_CHANNEL_IMPORTANCE_VALUES, type AndroidChannelImportance, BUBBLES_DELIVERY_STATUSES, BUBBLES_NOTIFICATIONS_EXPO_EXTRA_KEY, BubblesAppAuthenticationError, type BubblesCreateDevicePayload, type BubblesDeliveryStatus, type BubblesDeliveryStatusPayload, type BubblesDevicePayload, type BubblesDevicePlatform, type BubblesForegroundNotificationPayloadContract, type BubblesNotificationDataContract, type BubblesNotificationPayload, type BubblesNotificationResponseEvent, type BubblesNotificationsContextValue, type BubblesNotificationsLogoutEvent, BubblesNotificationsProvider, type BubblesNotificationsProviderProps, type BubblesNotificationsRuntimeConfig, type BubblesSilentNotificationPayloadContract, type BubblesUpdateDevicePayload, type BubblesVisibleNotificationPayloadContract, type BuildBubblesCreateDevicePayloadOptions, type BuildBubblesDeliveryStatusPayloadOptions, type BuildBubblesDevicePayloadOptions, type CreateBubblesDeviceOptions, type CreateStoredDeliveryStatusEventOptions, type DefaultNotificationChannelConfig, type DeviceRegistrationState, type DeviceTokenResult, type EnsureDefaultNotificationChannelOptions, type ForegroundPresentationOptions, type GetDeviceTokenOptions, type GetNotificationPermissionsOptions, type NativeTokenType, type PermissionRequestOptions, type PostBubblesDeliveryStatusOptions, type PostStoredBubblesDeliveryStatusOptions, type RegisterDeviceOptions, type StoredDeliveryStatusEvent, type StoredDeliveryStatusLedger, type StoredDeviceState, type StoredDeviceStateUpdate, type SupportedPlatform, type SyncBubblesDeviceOptions, type SyncBubblesDeviceResult, type UpdateBubblesDeviceOptions, buildBubblesDeliveryStatusPayload, buildCreateDevicePayload, buildStoredDeliveryStatusEventKey, buildUpdateDevicePayload, createBubblesDevice, createBubblesDeviceClient, createStoredDeliveryStatusEvent, describeNotificationPermissionStatus, enqueueStoredDeliveryStatusEvent, ensureDefaultNotificationChannel, ensureNotificationPermissions, extractDeviceIdFromDeviceResponse, flushStoredBubblesDeliveryStatuses, getAndroidNotificationChannelImportance, getBubblesNotificationsRuntimeConfig, getDefaultNotificationChannelConfig, getDeviceRegistrationState, getDeviceToken, getFCMToken, getNotificationPermissions, isNotificationPermissionGranted, markStoredDeliveryStatusEventPosted, patchStoredDeviceState, postBubblesDeliveryStatus, postStoredBubblesDeliveryStatus, readStoredApiBaseUrl, readStoredAppKey, readStoredDeliveryStatusLedger, readStoredDeviceId, readStoredDeviceState, registerBubblesBackgroundHandlers, storeApiBaseUrl, storeAppKey, storeDeliveryStatusLedger, storeDeviceId, storeDeviceState, syncBubblesDevice, updateBubblesDevice, useBubblesNotifications };
|
|
319
|
+
export { ANDROID_CHANNEL_IMPORTANCE_VALUES, type AndroidChannelImportance, BUBBLES_DELIVERY_STATUSES, BUBBLES_NOTIFICATIONS_EXPO_EXTRA_KEY, BubblesAppAuthenticationError, type BubblesCreateDevicePayload, type BubblesDeliveryStatus, type BubblesDeliveryStatusPayload, type BubblesDeviceAttributeValue, type BubblesDeviceAttributes, type BubblesDevicePayload, type BubblesDevicePlatform, type BubblesForegroundNotificationPayloadContract, type BubblesNotificationDataContract, type BubblesNotificationPayload, type BubblesNotificationResponseEvent, type BubblesNotificationsContextValue, type BubblesNotificationsLogoutEvent, BubblesNotificationsProvider, type BubblesNotificationsProviderProps, type BubblesNotificationsRuntimeConfig, type BubblesSilentNotificationPayloadContract, type BubblesUpdateDevicePayload, type BubblesVisibleNotificationPayloadContract, type BuildBubblesCreateDevicePayloadOptions, type BuildBubblesDeliveryStatusPayloadOptions, type BuildBubblesDevicePayloadOptions, type CreateBubblesDeviceOptions, type CreateStoredDeliveryStatusEventOptions, type DefaultNotificationChannelConfig, type DeviceRegistrationState, type DeviceTokenResult, type EnsureDefaultNotificationChannelOptions, type ForegroundPresentationOptions, type GetDeviceTokenOptions, type GetNotificationPermissionsOptions, type NativeTokenType, type PermissionRequestOptions, type PostBubblesDeliveryStatusOptions, type PostStoredBubblesDeliveryStatusOptions, type RegisterDeviceOptions, type StoredDeliveryStatusEvent, type StoredDeliveryStatusLedger, type StoredDeviceState, type StoredDeviceStateUpdate, type SupportedPlatform, type SyncBubblesDeviceOptions, type SyncBubblesDeviceResult, type UpdateBubblesDeviceAttributesOptions, type UpdateBubblesDeviceOptions, buildBubblesDeliveryStatusPayload, buildCreateDevicePayload, buildStoredDeliveryStatusEventKey, buildUpdateDevicePayload, collectBubblesDeviceAttributes, createBubblesDevice, createBubblesDeviceClient, createStoredDeliveryStatusEvent, describeNotificationPermissionStatus, enqueueStoredDeliveryStatusEvent, ensureDefaultNotificationChannel, ensureNotificationPermissions, extractDeviceIdFromDeviceResponse, flushStoredBubblesDeliveryStatuses, getAndroidNotificationChannelImportance, getBubblesNotificationsRuntimeConfig, getDefaultNotificationChannelConfig, getDeviceRegistrationState, getDeviceToken, getFCMToken, getNotificationPermissions, isNotificationPermissionGranted, markStoredDeliveryStatusEventPosted, patchStoredDeviceState, postBubblesDeliveryStatus, postStoredBubblesDeliveryStatus, readStoredApiBaseUrl, readStoredAppKey, readStoredDeliveryStatusLedger, readStoredDeviceId, readStoredDeviceState, registerBubblesBackgroundHandlers, storeApiBaseUrl, storeAppKey, storeDeliveryStatusLedger, storeDeviceId, storeDeviceState, syncBubblesDevice, updateBubblesDevice, updateBubblesDeviceAttributes, useBubblesNotifications };
|
package/dist/index.d.ts
CHANGED
|
@@ -93,10 +93,20 @@ interface SyncBubblesDeviceResult<TResponse = unknown> {
|
|
|
93
93
|
deviceId: string | null;
|
|
94
94
|
response: TResponse;
|
|
95
95
|
}
|
|
96
|
+
type BubblesDeviceAttributeValue = string | number | boolean | null | BubblesDeviceAttributeValue[] | {
|
|
97
|
+
[key: string]: BubblesDeviceAttributeValue;
|
|
98
|
+
};
|
|
99
|
+
type BubblesDeviceAttributes = Record<string, BubblesDeviceAttributeValue>;
|
|
100
|
+
interface UpdateBubblesDeviceAttributesOptions extends BubblesDeviceClientInput {
|
|
101
|
+
deviceId: string | number;
|
|
102
|
+
attributes: BubblesDeviceAttributes;
|
|
103
|
+
requestOptions?: RequestOptions;
|
|
104
|
+
}
|
|
96
105
|
declare function createBubblesDeviceClient(options: BubblesDeviceClientOptions): DeviceClient;
|
|
97
106
|
declare function extractDeviceIdFromDeviceResponse(value: unknown): string | null;
|
|
98
107
|
declare function createBubblesDevice<TResponse = unknown>(options: CreateBubblesDeviceOptions): Promise<TResponse>;
|
|
99
108
|
declare function updateBubblesDevice<TResponse = unknown>(options: UpdateBubblesDeviceOptions): Promise<TResponse>;
|
|
109
|
+
declare function updateBubblesDeviceAttributes<TResponse = unknown>(options: UpdateBubblesDeviceAttributesOptions): Promise<TResponse>;
|
|
100
110
|
declare function syncBubblesDevice<TResponse = unknown>(options: SyncBubblesDeviceOptions): Promise<SyncBubblesDeviceResult<TResponse>>;
|
|
101
111
|
|
|
102
112
|
type DeviceClientFactoryOptions = Omit<DeviceClientOptions, 'baseUrl'>;
|
|
@@ -202,7 +212,7 @@ declare function ensureNotificationPermissions(options?: GetNotificationPermissi
|
|
|
202
212
|
|
|
203
213
|
type SupportedPlatform = 'android' | 'ios';
|
|
204
214
|
|
|
205
|
-
type NativeTokenType = '
|
|
215
|
+
type NativeTokenType = 'fid';
|
|
206
216
|
type GetDeviceTokenOptions = GetNotificationPermissionsOptions;
|
|
207
217
|
interface DeviceTokenResult {
|
|
208
218
|
platform: SupportedPlatform;
|
|
@@ -220,6 +230,8 @@ declare function getDeviceRegistrationState(options?: GetDeviceTokenOptions): Pr
|
|
|
220
230
|
declare function getDeviceToken(options?: GetDeviceTokenOptions): Promise<DeviceTokenResult>;
|
|
221
231
|
declare function getFCMToken(options?: GetDeviceTokenOptions): Promise<string>;
|
|
222
232
|
|
|
233
|
+
declare function collectBubblesDeviceAttributes(): Promise<BubblesDeviceAttributes>;
|
|
234
|
+
|
|
223
235
|
interface RegisterDeviceOptions {
|
|
224
236
|
requestPermissions?: boolean;
|
|
225
237
|
permissionRequestOptions?: PermissionRequestOptions;
|
|
@@ -256,6 +268,7 @@ interface BubblesNotificationsProviderProps {
|
|
|
256
268
|
}
|
|
257
269
|
interface BubblesNotificationsContextValue {
|
|
258
270
|
registerDevice: (options?: RegisterDeviceOptions) => Promise<void>;
|
|
271
|
+
addDeviceAttribute: (name: string, value: BubblesDeviceAttributeValue) => Promise<void>;
|
|
259
272
|
deviceId: string | null;
|
|
260
273
|
pushToken: string | null;
|
|
261
274
|
tokenType: NativeTokenType | null;
|
|
@@ -303,4 +316,4 @@ interface BubblesNotificationPayload {
|
|
|
303
316
|
url: string | null;
|
|
304
317
|
}
|
|
305
318
|
|
|
306
|
-
export { ANDROID_CHANNEL_IMPORTANCE_VALUES, type AndroidChannelImportance, BUBBLES_DELIVERY_STATUSES, BUBBLES_NOTIFICATIONS_EXPO_EXTRA_KEY, BubblesAppAuthenticationError, type BubblesCreateDevicePayload, type BubblesDeliveryStatus, type BubblesDeliveryStatusPayload, type BubblesDevicePayload, type BubblesDevicePlatform, type BubblesForegroundNotificationPayloadContract, type BubblesNotificationDataContract, type BubblesNotificationPayload, type BubblesNotificationResponseEvent, type BubblesNotificationsContextValue, type BubblesNotificationsLogoutEvent, BubblesNotificationsProvider, type BubblesNotificationsProviderProps, type BubblesNotificationsRuntimeConfig, type BubblesSilentNotificationPayloadContract, type BubblesUpdateDevicePayload, type BubblesVisibleNotificationPayloadContract, type BuildBubblesCreateDevicePayloadOptions, type BuildBubblesDeliveryStatusPayloadOptions, type BuildBubblesDevicePayloadOptions, type CreateBubblesDeviceOptions, type CreateStoredDeliveryStatusEventOptions, type DefaultNotificationChannelConfig, type DeviceRegistrationState, type DeviceTokenResult, type EnsureDefaultNotificationChannelOptions, type ForegroundPresentationOptions, type GetDeviceTokenOptions, type GetNotificationPermissionsOptions, type NativeTokenType, type PermissionRequestOptions, type PostBubblesDeliveryStatusOptions, type PostStoredBubblesDeliveryStatusOptions, type RegisterDeviceOptions, type StoredDeliveryStatusEvent, type StoredDeliveryStatusLedger, type StoredDeviceState, type StoredDeviceStateUpdate, type SupportedPlatform, type SyncBubblesDeviceOptions, type SyncBubblesDeviceResult, type UpdateBubblesDeviceOptions, buildBubblesDeliveryStatusPayload, buildCreateDevicePayload, buildStoredDeliveryStatusEventKey, buildUpdateDevicePayload, createBubblesDevice, createBubblesDeviceClient, createStoredDeliveryStatusEvent, describeNotificationPermissionStatus, enqueueStoredDeliveryStatusEvent, ensureDefaultNotificationChannel, ensureNotificationPermissions, extractDeviceIdFromDeviceResponse, flushStoredBubblesDeliveryStatuses, getAndroidNotificationChannelImportance, getBubblesNotificationsRuntimeConfig, getDefaultNotificationChannelConfig, getDeviceRegistrationState, getDeviceToken, getFCMToken, getNotificationPermissions, isNotificationPermissionGranted, markStoredDeliveryStatusEventPosted, patchStoredDeviceState, postBubblesDeliveryStatus, postStoredBubblesDeliveryStatus, readStoredApiBaseUrl, readStoredAppKey, readStoredDeliveryStatusLedger, readStoredDeviceId, readStoredDeviceState, registerBubblesBackgroundHandlers, storeApiBaseUrl, storeAppKey, storeDeliveryStatusLedger, storeDeviceId, storeDeviceState, syncBubblesDevice, updateBubblesDevice, useBubblesNotifications };
|
|
319
|
+
export { ANDROID_CHANNEL_IMPORTANCE_VALUES, type AndroidChannelImportance, BUBBLES_DELIVERY_STATUSES, BUBBLES_NOTIFICATIONS_EXPO_EXTRA_KEY, BubblesAppAuthenticationError, type BubblesCreateDevicePayload, type BubblesDeliveryStatus, type BubblesDeliveryStatusPayload, type BubblesDeviceAttributeValue, type BubblesDeviceAttributes, type BubblesDevicePayload, type BubblesDevicePlatform, type BubblesForegroundNotificationPayloadContract, type BubblesNotificationDataContract, type BubblesNotificationPayload, type BubblesNotificationResponseEvent, type BubblesNotificationsContextValue, type BubblesNotificationsLogoutEvent, BubblesNotificationsProvider, type BubblesNotificationsProviderProps, type BubblesNotificationsRuntimeConfig, type BubblesSilentNotificationPayloadContract, type BubblesUpdateDevicePayload, type BubblesVisibleNotificationPayloadContract, type BuildBubblesCreateDevicePayloadOptions, type BuildBubblesDeliveryStatusPayloadOptions, type BuildBubblesDevicePayloadOptions, type CreateBubblesDeviceOptions, type CreateStoredDeliveryStatusEventOptions, type DefaultNotificationChannelConfig, type DeviceRegistrationState, type DeviceTokenResult, type EnsureDefaultNotificationChannelOptions, type ForegroundPresentationOptions, type GetDeviceTokenOptions, type GetNotificationPermissionsOptions, type NativeTokenType, type PermissionRequestOptions, type PostBubblesDeliveryStatusOptions, type PostStoredBubblesDeliveryStatusOptions, type RegisterDeviceOptions, type StoredDeliveryStatusEvent, type StoredDeliveryStatusLedger, type StoredDeviceState, type StoredDeviceStateUpdate, type SupportedPlatform, type SyncBubblesDeviceOptions, type SyncBubblesDeviceResult, type UpdateBubblesDeviceAttributesOptions, type UpdateBubblesDeviceOptions, buildBubblesDeliveryStatusPayload, buildCreateDevicePayload, buildStoredDeliveryStatusEventKey, buildUpdateDevicePayload, collectBubblesDeviceAttributes, createBubblesDevice, createBubblesDeviceClient, createStoredDeliveryStatusEvent, describeNotificationPermissionStatus, enqueueStoredDeliveryStatusEvent, ensureDefaultNotificationChannel, ensureNotificationPermissions, extractDeviceIdFromDeviceResponse, flushStoredBubblesDeliveryStatuses, getAndroidNotificationChannelImportance, getBubblesNotificationsRuntimeConfig, getDefaultNotificationChannelConfig, getDeviceRegistrationState, getDeviceToken, getFCMToken, getNotificationPermissions, isNotificationPermissionGranted, markStoredDeliveryStatusEventPosted, patchStoredDeviceState, postBubblesDeliveryStatus, postStoredBubblesDeliveryStatus, readStoredApiBaseUrl, readStoredAppKey, readStoredDeliveryStatusLedger, readStoredDeviceId, readStoredDeviceState, registerBubblesBackgroundHandlers, storeApiBaseUrl, storeAppKey, storeDeliveryStatusLedger, storeDeviceId, storeDeviceState, syncBubblesDevice, updateBubblesDevice, updateBubblesDeviceAttributes, useBubblesNotifications };
|
package/dist/index.js
CHANGED
|
@@ -39,6 +39,7 @@ __export(index_exports, {
|
|
|
39
39
|
buildCreateDevicePayload: () => buildCreateDevicePayload,
|
|
40
40
|
buildStoredDeliveryStatusEventKey: () => buildStoredDeliveryStatusEventKey,
|
|
41
41
|
buildUpdateDevicePayload: () => buildUpdateDevicePayload,
|
|
42
|
+
collectBubblesDeviceAttributes: () => collectBubblesDeviceAttributes,
|
|
42
43
|
createBubblesDevice: () => createBubblesDevice,
|
|
43
44
|
createBubblesDeviceClient: () => createBubblesDeviceClient,
|
|
44
45
|
createStoredDeliveryStatusEvent: () => createStoredDeliveryStatusEvent,
|
|
@@ -73,6 +74,7 @@ __export(index_exports, {
|
|
|
73
74
|
storeDeviceState: () => storeDeviceState,
|
|
74
75
|
syncBubblesDevice: () => syncBubblesDevice,
|
|
75
76
|
updateBubblesDevice: () => updateBubblesDevice,
|
|
77
|
+
updateBubblesDeviceAttributes: () => updateBubblesDeviceAttributes,
|
|
76
78
|
useBubblesNotifications: () => useBubblesNotifications
|
|
77
79
|
});
|
|
78
80
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -535,6 +537,78 @@ function throwAppAuthenticationError(error) {
|
|
|
535
537
|
}
|
|
536
538
|
throw error;
|
|
537
539
|
}
|
|
540
|
+
function normalizeDeviceAttributes(value) {
|
|
541
|
+
return normalizeDeviceAttributeObject(value, "attributes", /* @__PURE__ */ new WeakSet());
|
|
542
|
+
}
|
|
543
|
+
function normalizeDeviceAttributeValue(value, sourceDescription, seenValues) {
|
|
544
|
+
if (value === null) {
|
|
545
|
+
return null;
|
|
546
|
+
}
|
|
547
|
+
if (typeof value === "string" || typeof value === "boolean") {
|
|
548
|
+
return value;
|
|
549
|
+
}
|
|
550
|
+
if (typeof value === "number") {
|
|
551
|
+
if (!Number.isFinite(value)) {
|
|
552
|
+
failWithBubblesError(`${sourceDescription} must be a finite number.`);
|
|
553
|
+
}
|
|
554
|
+
return value;
|
|
555
|
+
}
|
|
556
|
+
if (Array.isArray(value)) {
|
|
557
|
+
if (seenValues.has(value)) {
|
|
558
|
+
failWithBubblesError(`${sourceDescription} must not contain cycles.`);
|
|
559
|
+
}
|
|
560
|
+
seenValues.add(value);
|
|
561
|
+
const normalizedValue = value.map(
|
|
562
|
+
(entry, index) => normalizeDeviceAttributeValue(
|
|
563
|
+
entry,
|
|
564
|
+
`${sourceDescription}[${index}]`,
|
|
565
|
+
seenValues
|
|
566
|
+
)
|
|
567
|
+
);
|
|
568
|
+
seenValues.delete(value);
|
|
569
|
+
return normalizedValue;
|
|
570
|
+
}
|
|
571
|
+
if (isPlainObject(value)) {
|
|
572
|
+
return normalizeDeviceAttributeObject(
|
|
573
|
+
value,
|
|
574
|
+
sourceDescription,
|
|
575
|
+
seenValues
|
|
576
|
+
);
|
|
577
|
+
}
|
|
578
|
+
failWithBubblesError(
|
|
579
|
+
`${sourceDescription} must be a JSON-compatible value.`
|
|
580
|
+
);
|
|
581
|
+
}
|
|
582
|
+
function normalizeDeviceAttributeObject(value, sourceDescription, seenValues) {
|
|
583
|
+
if (!isPlainObject(value)) {
|
|
584
|
+
failWithBubblesError('"attributes" must be an object.');
|
|
585
|
+
}
|
|
586
|
+
const valuePrototype = Object.getPrototypeOf(value);
|
|
587
|
+
if (valuePrototype !== Object.prototype && valuePrototype !== null) {
|
|
588
|
+
failWithBubblesError(
|
|
589
|
+
`${sourceDescription} must be a JSON-compatible object.`
|
|
590
|
+
);
|
|
591
|
+
}
|
|
592
|
+
if (Object.getOwnPropertySymbols(value).length > 0) {
|
|
593
|
+
failWithBubblesError(
|
|
594
|
+
`${sourceDescription} must not contain symbol keys.`
|
|
595
|
+
);
|
|
596
|
+
}
|
|
597
|
+
if (seenValues.has(value)) {
|
|
598
|
+
failWithBubblesError(`${sourceDescription} must not contain cycles.`);
|
|
599
|
+
}
|
|
600
|
+
seenValues.add(value);
|
|
601
|
+
const normalizedValue = {};
|
|
602
|
+
for (const [attributeName, attributeValue] of Object.entries(value)) {
|
|
603
|
+
normalizedValue[attributeName] = normalizeDeviceAttributeValue(
|
|
604
|
+
attributeValue,
|
|
605
|
+
`${sourceDescription}.${attributeName}`,
|
|
606
|
+
seenValues
|
|
607
|
+
);
|
|
608
|
+
}
|
|
609
|
+
seenValues.delete(value);
|
|
610
|
+
return normalizedValue;
|
|
611
|
+
}
|
|
538
612
|
function extractDeviceIdFromDeviceResponse(value) {
|
|
539
613
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
540
614
|
return null;
|
|
@@ -574,6 +648,18 @@ async function updateBubblesDevice(options) {
|
|
|
574
648
|
throwAppAuthenticationError(error);
|
|
575
649
|
}
|
|
576
650
|
}
|
|
651
|
+
async function updateBubblesDeviceAttributes(options) {
|
|
652
|
+
const client = resolveDeviceClient(options);
|
|
653
|
+
try {
|
|
654
|
+
return await client.updateDeviceAttributes(
|
|
655
|
+
normalizeIdentifier(options.deviceId, "deviceId"),
|
|
656
|
+
normalizeDeviceAttributes(options.attributes),
|
|
657
|
+
withAppKeyHeader(options.appKey, options.requestOptions)
|
|
658
|
+
);
|
|
659
|
+
} catch (error) {
|
|
660
|
+
throwAppAuthenticationError(error);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
577
663
|
async function syncBubblesDevice(options) {
|
|
578
664
|
const client = resolveDeviceClient(options);
|
|
579
665
|
if (options.deviceId !== null) {
|
|
@@ -1332,27 +1418,28 @@ function registerBubblesBackgroundHandlers() {
|
|
|
1332
1418
|
}
|
|
1333
1419
|
|
|
1334
1420
|
// src/runtime/tokens.ts
|
|
1335
|
-
var import_messaging2 = require("@react-native-firebase/messaging");
|
|
1336
1421
|
var import_react_native4 = require("react-native");
|
|
1337
|
-
|
|
1422
|
+
|
|
1423
|
+
// src/runtime/installations.ts
|
|
1424
|
+
var import_installations = require("@react-native-firebase/installations");
|
|
1425
|
+
async function getFirebaseInstallationId(platform) {
|
|
1338
1426
|
try {
|
|
1339
|
-
const
|
|
1340
|
-
|
|
1341
|
-
await (0, import_messaging2.registerDeviceForRemoteMessages)(messagingInstance);
|
|
1342
|
-
}
|
|
1343
|
-
const token = await (0, import_messaging2.getToken)(messagingInstance);
|
|
1427
|
+
const installationsInstance = (0, import_installations.getInstallations)();
|
|
1428
|
+
const installationId = await (0, import_installations.getId)(installationsInstance);
|
|
1344
1429
|
return getRequiredNonEmptyStringFromSource(
|
|
1345
|
-
|
|
1346
|
-
`${platform}
|
|
1430
|
+
installationId,
|
|
1431
|
+
`${platform} Firebase installation id from Firebase Installations`
|
|
1347
1432
|
);
|
|
1348
1433
|
} catch (error) {
|
|
1349
1434
|
const reason = getErrorMessage(error);
|
|
1350
|
-
const platformSpecificSetupHint = platform === "ios" ? " On iOS,
|
|
1435
|
+
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.";
|
|
1351
1436
|
failWithBubblesError(
|
|
1352
|
-
`Failed to get a ${platform}
|
|
1437
|
+
`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}`
|
|
1353
1438
|
);
|
|
1354
1439
|
}
|
|
1355
1440
|
}
|
|
1441
|
+
|
|
1442
|
+
// src/runtime/tokens.ts
|
|
1356
1443
|
async function getDeviceRegistrationState(options) {
|
|
1357
1444
|
const platform = getSupportedPlatform(import_react_native4.Platform.OS);
|
|
1358
1445
|
const permissions = await getNotificationPermissions(options);
|
|
@@ -1367,10 +1454,10 @@ async function getDeviceRegistrationState(options) {
|
|
|
1367
1454
|
notificationsEnabled: false
|
|
1368
1455
|
};
|
|
1369
1456
|
}
|
|
1370
|
-
const token = await
|
|
1457
|
+
const token = await getFirebaseInstallationId(platform);
|
|
1371
1458
|
return {
|
|
1372
1459
|
platform,
|
|
1373
|
-
tokenType: "
|
|
1460
|
+
tokenType: "fid",
|
|
1374
1461
|
token,
|
|
1375
1462
|
permissionStatus,
|
|
1376
1463
|
notificationsEnabled: true
|
|
@@ -1383,7 +1470,7 @@ async function getDeviceToken(options) {
|
|
|
1383
1470
|
}
|
|
1384
1471
|
return {
|
|
1385
1472
|
platform: registrationState.platform,
|
|
1386
|
-
tokenType: "
|
|
1473
|
+
tokenType: "fid",
|
|
1387
1474
|
token: registrationState.token
|
|
1388
1475
|
};
|
|
1389
1476
|
}
|
|
@@ -1392,6 +1479,73 @@ async function getFCMToken(options) {
|
|
|
1392
1479
|
return tokenResult.token;
|
|
1393
1480
|
}
|
|
1394
1481
|
|
|
1482
|
+
// src/runtime/device-attributes.ts
|
|
1483
|
+
var Application = __toESM(require("expo-application"));
|
|
1484
|
+
var Device = __toESM(require("expo-device"));
|
|
1485
|
+
function compactAttributes(attributes) {
|
|
1486
|
+
return Object.fromEntries(
|
|
1487
|
+
Object.entries(attributes).filter((entry) => entry[1] !== null && entry[1] !== void 0)
|
|
1488
|
+
);
|
|
1489
|
+
}
|
|
1490
|
+
function addSection(attributes, name, section) {
|
|
1491
|
+
const compactedSection = compactAttributes(section);
|
|
1492
|
+
if (Object.keys(compactedSection).length > 0) {
|
|
1493
|
+
attributes[name] = compactedSection;
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
function getDeviceTypeName(deviceType2) {
|
|
1497
|
+
switch (deviceType2) {
|
|
1498
|
+
case Device.DeviceType.PHONE:
|
|
1499
|
+
return "phone";
|
|
1500
|
+
case Device.DeviceType.TABLET:
|
|
1501
|
+
return "tablet";
|
|
1502
|
+
case Device.DeviceType.DESKTOP:
|
|
1503
|
+
return "desktop";
|
|
1504
|
+
case Device.DeviceType.TV:
|
|
1505
|
+
return "tv";
|
|
1506
|
+
case Device.DeviceType.UNKNOWN:
|
|
1507
|
+
return "unknown";
|
|
1508
|
+
default:
|
|
1509
|
+
return void 0;
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
async function getDeviceType() {
|
|
1513
|
+
if (Device.deviceType !== null && Device.deviceType !== void 0) {
|
|
1514
|
+
return Device.deviceType;
|
|
1515
|
+
}
|
|
1516
|
+
try {
|
|
1517
|
+
return await Device.getDeviceTypeAsync();
|
|
1518
|
+
} catch {
|
|
1519
|
+
return null;
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
async function collectBubblesDeviceAttributes() {
|
|
1523
|
+
const attributes = {};
|
|
1524
|
+
const deviceType2 = await getDeviceType();
|
|
1525
|
+
addSection(attributes, "device", {
|
|
1526
|
+
manufacturer: Device.manufacturer,
|
|
1527
|
+
brand: Device.brand,
|
|
1528
|
+
modelName: Device.modelName,
|
|
1529
|
+
modelId: typeof Device.modelId === "string" ? Device.modelId : void 0,
|
|
1530
|
+
deviceName: Device.deviceName,
|
|
1531
|
+
deviceType: getDeviceTypeName(deviceType2),
|
|
1532
|
+
totalMemory: Device.totalMemory
|
|
1533
|
+
});
|
|
1534
|
+
addSection(attributes, "os", {
|
|
1535
|
+
name: Device.osName,
|
|
1536
|
+
version: Device.osVersion,
|
|
1537
|
+
buildId: Device.osBuildId,
|
|
1538
|
+
platformApiLevel: Device.platformApiLevel
|
|
1539
|
+
});
|
|
1540
|
+
addSection(attributes, "app", {
|
|
1541
|
+
applicationId: Application.applicationId,
|
|
1542
|
+
applicationName: Application.applicationName,
|
|
1543
|
+
nativeApplicationVersion: Application.nativeApplicationVersion,
|
|
1544
|
+
nativeBuildVersion: Application.nativeBuildVersion
|
|
1545
|
+
});
|
|
1546
|
+
return attributes;
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1395
1549
|
// src/runtime/BubblesNotificationsProvider.tsx
|
|
1396
1550
|
var import_react3 = require("react");
|
|
1397
1551
|
|
|
@@ -1530,7 +1684,7 @@ async function prepareBubblesNotificationPresentation(options) {
|
|
|
1530
1684
|
}
|
|
1531
1685
|
|
|
1532
1686
|
// src/runtime/foreground-message-observer.ts
|
|
1533
|
-
var
|
|
1687
|
+
var import_messaging2 = require("@react-native-firebase/messaging");
|
|
1534
1688
|
var import_react_native5 = require("react-native");
|
|
1535
1689
|
function supportsFirebaseForegroundMessages() {
|
|
1536
1690
|
return import_react_native5.Platform.OS === "android" || import_react_native5.Platform.OS === "ios";
|
|
@@ -1550,8 +1704,8 @@ function observeBubblesForegroundMessages() {
|
|
|
1550
1704
|
return () => {
|
|
1551
1705
|
};
|
|
1552
1706
|
}
|
|
1553
|
-
const unsubscribe = (0,
|
|
1554
|
-
(0,
|
|
1707
|
+
const unsubscribe = (0, import_messaging2.onMessage)(
|
|
1708
|
+
(0, import_messaging2.getMessaging)(),
|
|
1555
1709
|
(remoteMessage) => {
|
|
1556
1710
|
void handleFirebaseForegroundMessage(remoteMessage).catch(
|
|
1557
1711
|
(error) => {
|
|
@@ -1568,25 +1722,6 @@ function observeBubblesForegroundMessages() {
|
|
|
1568
1722
|
};
|
|
1569
1723
|
}
|
|
1570
1724
|
|
|
1571
|
-
// src/runtime/installations.ts
|
|
1572
|
-
var import_installations = require("@react-native-firebase/installations");
|
|
1573
|
-
async function getFirebaseInstallationId(platform) {
|
|
1574
|
-
try {
|
|
1575
|
-
const installationsInstance = (0, import_installations.getInstallations)();
|
|
1576
|
-
const installationId = await (0, import_installations.getId)(installationsInstance);
|
|
1577
|
-
return getRequiredNonEmptyStringFromSource(
|
|
1578
|
-
installationId,
|
|
1579
|
-
`${platform} Firebase installation id from Firebase Installations`
|
|
1580
|
-
);
|
|
1581
|
-
} catch (error) {
|
|
1582
|
-
const reason = getErrorMessage(error);
|
|
1583
|
-
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.";
|
|
1584
|
-
failWithBubblesError(
|
|
1585
|
-
`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}`
|
|
1586
|
-
);
|
|
1587
|
-
}
|
|
1588
|
-
}
|
|
1589
|
-
|
|
1590
1725
|
// src/runtime/transport.ts
|
|
1591
1726
|
function observeBubblesForegroundRemoteMessages() {
|
|
1592
1727
|
return observeBubblesForegroundMessages();
|
|
@@ -1594,9 +1729,6 @@ function observeBubblesForegroundRemoteMessages() {
|
|
|
1594
1729
|
async function getBubblesDeviceRegistrationState(options) {
|
|
1595
1730
|
return getDeviceRegistrationState(options);
|
|
1596
1731
|
}
|
|
1597
|
-
async function getBubblesInstallationId(platform) {
|
|
1598
|
-
return getFirebaseInstallationId(platform);
|
|
1599
|
-
}
|
|
1600
1732
|
|
|
1601
1733
|
// src/runtime/sync-device.ts
|
|
1602
1734
|
var BubblesNotificationsSyncError = class extends Error {
|
|
@@ -1635,6 +1767,20 @@ function buildRuntimeSnapshot(deviceId, registrationState) {
|
|
|
1635
1767
|
notificationsEnabled: registrationState.notificationsEnabled
|
|
1636
1768
|
};
|
|
1637
1769
|
}
|
|
1770
|
+
async function syncBubblesDeviceAttributesBestEffort(apiBaseUrl, appKey, deviceId) {
|
|
1771
|
+
try {
|
|
1772
|
+
const attributes = await collectBubblesDeviceAttributes();
|
|
1773
|
+
if (Object.keys(attributes).length > 0) {
|
|
1774
|
+
await updateBubblesDeviceAttributes({
|
|
1775
|
+
apiBaseUrl,
|
|
1776
|
+
appKey,
|
|
1777
|
+
deviceId,
|
|
1778
|
+
attributes
|
|
1779
|
+
});
|
|
1780
|
+
}
|
|
1781
|
+
} catch {
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1638
1784
|
async function syncDeviceRegistrationState(options) {
|
|
1639
1785
|
const apiBaseUrl = getRequiredNonEmptyString3(options.apiBaseUrl, "apiBaseUrl");
|
|
1640
1786
|
const appKey = getRequiredNonEmptyString3(options.appKey, "appKey");
|
|
@@ -1645,7 +1791,7 @@ async function syncDeviceRegistrationState(options) {
|
|
|
1645
1791
|
options.registrationState
|
|
1646
1792
|
);
|
|
1647
1793
|
try {
|
|
1648
|
-
const installationId = options.registrationState.notificationsEnabled ?
|
|
1794
|
+
const installationId = options.registrationState.notificationsEnabled ? options.registrationState.token : null;
|
|
1649
1795
|
const syncResult = await syncBubblesDevice({
|
|
1650
1796
|
apiBaseUrl,
|
|
1651
1797
|
appId: options.appId,
|
|
@@ -1661,6 +1807,13 @@ async function syncDeviceRegistrationState(options) {
|
|
|
1661
1807
|
});
|
|
1662
1808
|
const nextDeviceId = syncResult.deviceId ?? options.deviceId;
|
|
1663
1809
|
storeDeviceId(nextDeviceId);
|
|
1810
|
+
if (nextDeviceId) {
|
|
1811
|
+
await syncBubblesDeviceAttributesBestEffort(
|
|
1812
|
+
apiBaseUrl,
|
|
1813
|
+
appKey,
|
|
1814
|
+
nextDeviceId
|
|
1815
|
+
);
|
|
1816
|
+
}
|
|
1664
1817
|
return {
|
|
1665
1818
|
...snapshot,
|
|
1666
1819
|
action: syncResult.action,
|
|
@@ -1848,6 +2001,7 @@ function BubblesNotificationsProvider(props) {
|
|
|
1848
2001
|
const [state, setState] = (0, import_react3.useState)(getInitialRuntimeState);
|
|
1849
2002
|
const { enqueueOperation, isMountedRef } = useBubblesRuntimeOperationQueue(setState);
|
|
1850
2003
|
const lastAutomaticSyncSignatureRef = (0, import_react3.useRef)(null);
|
|
2004
|
+
const currentDeviceIdRef = (0, import_react3.useRef)(state.deviceId);
|
|
1851
2005
|
const previousUserIdRef = (0, import_react3.useRef)(normalizedUserId);
|
|
1852
2006
|
const buildAutomaticSyncSignature = (0, import_react3.useCallback)((deviceId) => {
|
|
1853
2007
|
return getAutomaticSyncSignature({
|
|
@@ -1873,6 +2027,9 @@ function BubblesNotificationsProvider(props) {
|
|
|
1873
2027
|
clearBubblesForegroundPresentation();
|
|
1874
2028
|
};
|
|
1875
2029
|
}, [isMountedRef]);
|
|
2030
|
+
(0, import_react3.useEffect)(() => {
|
|
2031
|
+
currentDeviceIdRef.current = state.deviceId;
|
|
2032
|
+
}, [state.deviceId]);
|
|
1876
2033
|
(0, import_react3.useEffect)(() => {
|
|
1877
2034
|
let isCancelled = false;
|
|
1878
2035
|
void (async () => {
|
|
@@ -1940,6 +2097,7 @@ function BubblesNotificationsProvider(props) {
|
|
|
1940
2097
|
appVersion,
|
|
1941
2098
|
deviceId: storedDeviceId
|
|
1942
2099
|
});
|
|
2100
|
+
currentDeviceIdRef.current = result.deviceId;
|
|
1943
2101
|
return getRuntimeStateUpdate(result);
|
|
1944
2102
|
});
|
|
1945
2103
|
}, [
|
|
@@ -1973,9 +2131,10 @@ function BubblesNotificationsProvider(props) {
|
|
|
1973
2131
|
userId: normalizedUserId,
|
|
1974
2132
|
aliasing,
|
|
1975
2133
|
appVersion,
|
|
1976
|
-
deviceId:
|
|
2134
|
+
deviceId: currentDeviceIdRef.current,
|
|
1977
2135
|
registrationOptions: options
|
|
1978
2136
|
});
|
|
2137
|
+
currentDeviceIdRef.current = result.deviceId;
|
|
1979
2138
|
if (result.deviceId !== null) {
|
|
1980
2139
|
lastAutomaticSyncSignatureRef.current = buildAutomaticSyncSignature(
|
|
1981
2140
|
result.deviceId
|
|
@@ -1984,8 +2143,33 @@ function BubblesNotificationsProvider(props) {
|
|
|
1984
2143
|
return getRuntimeStateUpdate(result);
|
|
1985
2144
|
});
|
|
1986
2145
|
}
|
|
2146
|
+
function addDeviceAttribute(name, value) {
|
|
2147
|
+
return enqueueOperation(async () => {
|
|
2148
|
+
const attributeName = getRequiredNonEmptyString(name, "attributeName");
|
|
2149
|
+
if (value === void 0) {
|
|
2150
|
+
failWithBubblesError(
|
|
2151
|
+
'"addDeviceAttribute()" requires "value" to be a JSON-compatible value.'
|
|
2152
|
+
);
|
|
2153
|
+
}
|
|
2154
|
+
const deviceId = currentDeviceIdRef.current;
|
|
2155
|
+
if (deviceId === null) {
|
|
2156
|
+
failWithBubblesError(
|
|
2157
|
+
'"addDeviceAttribute()" requires a backend "deviceId" before attributes can be synced.'
|
|
2158
|
+
);
|
|
2159
|
+
}
|
|
2160
|
+
await updateBubblesDeviceAttributes({
|
|
2161
|
+
apiBaseUrl,
|
|
2162
|
+
appKey,
|
|
2163
|
+
deviceId,
|
|
2164
|
+
attributes: {
|
|
2165
|
+
[attributeName]: value
|
|
2166
|
+
}
|
|
2167
|
+
});
|
|
2168
|
+
});
|
|
2169
|
+
}
|
|
1987
2170
|
const contextValue = {
|
|
1988
2171
|
registerDevice,
|
|
2172
|
+
addDeviceAttribute,
|
|
1989
2173
|
deviceId: state.deviceId,
|
|
1990
2174
|
pushToken: state.pushToken,
|
|
1991
2175
|
tokenType: state.tokenType,
|
|
@@ -2022,6 +2206,7 @@ function useBubblesNotifications() {
|
|
|
2022
2206
|
buildCreateDevicePayload,
|
|
2023
2207
|
buildStoredDeliveryStatusEventKey,
|
|
2024
2208
|
buildUpdateDevicePayload,
|
|
2209
|
+
collectBubblesDeviceAttributes,
|
|
2025
2210
|
createBubblesDevice,
|
|
2026
2211
|
createBubblesDeviceClient,
|
|
2027
2212
|
createStoredDeliveryStatusEvent,
|
|
@@ -2056,5 +2241,6 @@ function useBubblesNotifications() {
|
|
|
2056
2241
|
storeDeviceState,
|
|
2057
2242
|
syncBubblesDevice,
|
|
2058
2243
|
updateBubblesDevice,
|
|
2244
|
+
updateBubblesDeviceAttributes,
|
|
2059
2245
|
useBubblesNotifications
|
|
2060
2246
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -47,36 +47,33 @@ import {
|
|
|
47
47
|
storeDeviceId,
|
|
48
48
|
storeDeviceState,
|
|
49
49
|
syncBubblesDevice,
|
|
50
|
-
updateBubblesDevice
|
|
51
|
-
|
|
50
|
+
updateBubblesDevice,
|
|
51
|
+
updateBubblesDeviceAttributes
|
|
52
|
+
} from "./chunk-3BAGWXHY.mjs";
|
|
52
53
|
|
|
53
54
|
// src/runtime/tokens.ts
|
|
54
|
-
import {
|
|
55
|
-
getMessaging,
|
|
56
|
-
getToken,
|
|
57
|
-
isDeviceRegisteredForRemoteMessages,
|
|
58
|
-
registerDeviceForRemoteMessages
|
|
59
|
-
} from "@react-native-firebase/messaging";
|
|
60
55
|
import { Platform } from "react-native";
|
|
61
|
-
|
|
56
|
+
|
|
57
|
+
// src/runtime/installations.ts
|
|
58
|
+
import { getId, getInstallations } from "@react-native-firebase/installations";
|
|
59
|
+
async function getFirebaseInstallationId(platform) {
|
|
62
60
|
try {
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
await registerDeviceForRemoteMessages(messagingInstance);
|
|
66
|
-
}
|
|
67
|
-
const token = await getToken(messagingInstance);
|
|
61
|
+
const installationsInstance = getInstallations();
|
|
62
|
+
const installationId = await getId(installationsInstance);
|
|
68
63
|
return getRequiredNonEmptyStringFromSource(
|
|
69
|
-
|
|
70
|
-
`${platform}
|
|
64
|
+
installationId,
|
|
65
|
+
`${platform} Firebase installation id from Firebase Installations`
|
|
71
66
|
);
|
|
72
67
|
} catch (error) {
|
|
73
68
|
const reason = getErrorMessage(error);
|
|
74
|
-
const platformSpecificSetupHint = platform === "ios" ? " On iOS,
|
|
69
|
+
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.";
|
|
75
70
|
failWithBubblesError(
|
|
76
|
-
`Failed to get a ${platform}
|
|
71
|
+
`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}`
|
|
77
72
|
);
|
|
78
73
|
}
|
|
79
74
|
}
|
|
75
|
+
|
|
76
|
+
// src/runtime/tokens.ts
|
|
80
77
|
async function getDeviceRegistrationState(options) {
|
|
81
78
|
const platform = getSupportedPlatform(Platform.OS);
|
|
82
79
|
const permissions = await getNotificationPermissions(options);
|
|
@@ -91,10 +88,10 @@ async function getDeviceRegistrationState(options) {
|
|
|
91
88
|
notificationsEnabled: false
|
|
92
89
|
};
|
|
93
90
|
}
|
|
94
|
-
const token = await
|
|
91
|
+
const token = await getFirebaseInstallationId(platform);
|
|
95
92
|
return {
|
|
96
93
|
platform,
|
|
97
|
-
tokenType: "
|
|
94
|
+
tokenType: "fid",
|
|
98
95
|
token,
|
|
99
96
|
permissionStatus,
|
|
100
97
|
notificationsEnabled: true
|
|
@@ -107,7 +104,7 @@ async function getDeviceToken(options) {
|
|
|
107
104
|
}
|
|
108
105
|
return {
|
|
109
106
|
platform: registrationState.platform,
|
|
110
|
-
tokenType: "
|
|
107
|
+
tokenType: "fid",
|
|
111
108
|
token: registrationState.token
|
|
112
109
|
};
|
|
113
110
|
}
|
|
@@ -116,6 +113,73 @@ async function getFCMToken(options) {
|
|
|
116
113
|
return tokenResult.token;
|
|
117
114
|
}
|
|
118
115
|
|
|
116
|
+
// src/runtime/device-attributes.ts
|
|
117
|
+
import * as Application from "expo-application";
|
|
118
|
+
import * as Device from "expo-device";
|
|
119
|
+
function compactAttributes(attributes) {
|
|
120
|
+
return Object.fromEntries(
|
|
121
|
+
Object.entries(attributes).filter((entry) => entry[1] !== null && entry[1] !== void 0)
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
function addSection(attributes, name, section) {
|
|
125
|
+
const compactedSection = compactAttributes(section);
|
|
126
|
+
if (Object.keys(compactedSection).length > 0) {
|
|
127
|
+
attributes[name] = compactedSection;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function getDeviceTypeName(deviceType2) {
|
|
131
|
+
switch (deviceType2) {
|
|
132
|
+
case Device.DeviceType.PHONE:
|
|
133
|
+
return "phone";
|
|
134
|
+
case Device.DeviceType.TABLET:
|
|
135
|
+
return "tablet";
|
|
136
|
+
case Device.DeviceType.DESKTOP:
|
|
137
|
+
return "desktop";
|
|
138
|
+
case Device.DeviceType.TV:
|
|
139
|
+
return "tv";
|
|
140
|
+
case Device.DeviceType.UNKNOWN:
|
|
141
|
+
return "unknown";
|
|
142
|
+
default:
|
|
143
|
+
return void 0;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
async function getDeviceType() {
|
|
147
|
+
if (Device.deviceType !== null && Device.deviceType !== void 0) {
|
|
148
|
+
return Device.deviceType;
|
|
149
|
+
}
|
|
150
|
+
try {
|
|
151
|
+
return await Device.getDeviceTypeAsync();
|
|
152
|
+
} catch {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
async function collectBubblesDeviceAttributes() {
|
|
157
|
+
const attributes = {};
|
|
158
|
+
const deviceType2 = await getDeviceType();
|
|
159
|
+
addSection(attributes, "device", {
|
|
160
|
+
manufacturer: Device.manufacturer,
|
|
161
|
+
brand: Device.brand,
|
|
162
|
+
modelName: Device.modelName,
|
|
163
|
+
modelId: typeof Device.modelId === "string" ? Device.modelId : void 0,
|
|
164
|
+
deviceName: Device.deviceName,
|
|
165
|
+
deviceType: getDeviceTypeName(deviceType2),
|
|
166
|
+
totalMemory: Device.totalMemory
|
|
167
|
+
});
|
|
168
|
+
addSection(attributes, "os", {
|
|
169
|
+
name: Device.osName,
|
|
170
|
+
version: Device.osVersion,
|
|
171
|
+
buildId: Device.osBuildId,
|
|
172
|
+
platformApiLevel: Device.platformApiLevel
|
|
173
|
+
});
|
|
174
|
+
addSection(attributes, "app", {
|
|
175
|
+
applicationId: Application.applicationId,
|
|
176
|
+
applicationName: Application.applicationName,
|
|
177
|
+
nativeApplicationVersion: Application.nativeApplicationVersion,
|
|
178
|
+
nativeBuildVersion: Application.nativeBuildVersion
|
|
179
|
+
});
|
|
180
|
+
return attributes;
|
|
181
|
+
}
|
|
182
|
+
|
|
119
183
|
// src/runtime/BubblesNotificationsProvider.tsx
|
|
120
184
|
import { useCallback as useCallback2, useEffect, useRef as useRef2, useState } from "react";
|
|
121
185
|
|
|
@@ -255,7 +319,7 @@ async function prepareBubblesNotificationPresentation(options) {
|
|
|
255
319
|
|
|
256
320
|
// src/runtime/foreground-message-observer.ts
|
|
257
321
|
import {
|
|
258
|
-
getMessaging
|
|
322
|
+
getMessaging,
|
|
259
323
|
onMessage
|
|
260
324
|
} from "@react-native-firebase/messaging";
|
|
261
325
|
import { Platform as Platform2 } from "react-native";
|
|
@@ -278,7 +342,7 @@ function observeBubblesForegroundMessages() {
|
|
|
278
342
|
};
|
|
279
343
|
}
|
|
280
344
|
const unsubscribe = onMessage(
|
|
281
|
-
|
|
345
|
+
getMessaging(),
|
|
282
346
|
(remoteMessage) => {
|
|
283
347
|
void handleFirebaseForegroundMessage(remoteMessage).catch(
|
|
284
348
|
(error) => {
|
|
@@ -295,25 +359,6 @@ function observeBubblesForegroundMessages() {
|
|
|
295
359
|
};
|
|
296
360
|
}
|
|
297
361
|
|
|
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
362
|
// src/runtime/transport.ts
|
|
318
363
|
function observeBubblesForegroundRemoteMessages() {
|
|
319
364
|
return observeBubblesForegroundMessages();
|
|
@@ -321,9 +366,6 @@ function observeBubblesForegroundRemoteMessages() {
|
|
|
321
366
|
async function getBubblesDeviceRegistrationState(options) {
|
|
322
367
|
return getDeviceRegistrationState(options);
|
|
323
368
|
}
|
|
324
|
-
async function getBubblesInstallationId(platform) {
|
|
325
|
-
return getFirebaseInstallationId(platform);
|
|
326
|
-
}
|
|
327
369
|
|
|
328
370
|
// src/runtime/sync-device.ts
|
|
329
371
|
var BubblesNotificationsSyncError = class extends Error {
|
|
@@ -362,6 +404,20 @@ function buildRuntimeSnapshot(deviceId, registrationState) {
|
|
|
362
404
|
notificationsEnabled: registrationState.notificationsEnabled
|
|
363
405
|
};
|
|
364
406
|
}
|
|
407
|
+
async function syncBubblesDeviceAttributesBestEffort(apiBaseUrl, appKey, deviceId) {
|
|
408
|
+
try {
|
|
409
|
+
const attributes = await collectBubblesDeviceAttributes();
|
|
410
|
+
if (Object.keys(attributes).length > 0) {
|
|
411
|
+
await updateBubblesDeviceAttributes({
|
|
412
|
+
apiBaseUrl,
|
|
413
|
+
appKey,
|
|
414
|
+
deviceId,
|
|
415
|
+
attributes
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
} catch {
|
|
419
|
+
}
|
|
420
|
+
}
|
|
365
421
|
async function syncDeviceRegistrationState(options) {
|
|
366
422
|
const apiBaseUrl = getRequiredNonEmptyString2(options.apiBaseUrl, "apiBaseUrl");
|
|
367
423
|
const appKey = getRequiredNonEmptyString2(options.appKey, "appKey");
|
|
@@ -372,7 +428,7 @@ async function syncDeviceRegistrationState(options) {
|
|
|
372
428
|
options.registrationState
|
|
373
429
|
);
|
|
374
430
|
try {
|
|
375
|
-
const installationId = options.registrationState.notificationsEnabled ?
|
|
431
|
+
const installationId = options.registrationState.notificationsEnabled ? options.registrationState.token : null;
|
|
376
432
|
const syncResult = await syncBubblesDevice({
|
|
377
433
|
apiBaseUrl,
|
|
378
434
|
appId: options.appId,
|
|
@@ -388,6 +444,13 @@ async function syncDeviceRegistrationState(options) {
|
|
|
388
444
|
});
|
|
389
445
|
const nextDeviceId = syncResult.deviceId ?? options.deviceId;
|
|
390
446
|
storeDeviceId(nextDeviceId);
|
|
447
|
+
if (nextDeviceId) {
|
|
448
|
+
await syncBubblesDeviceAttributesBestEffort(
|
|
449
|
+
apiBaseUrl,
|
|
450
|
+
appKey,
|
|
451
|
+
nextDeviceId
|
|
452
|
+
);
|
|
453
|
+
}
|
|
391
454
|
return {
|
|
392
455
|
...snapshot,
|
|
393
456
|
action: syncResult.action,
|
|
@@ -578,6 +641,7 @@ function BubblesNotificationsProvider(props) {
|
|
|
578
641
|
const [state, setState] = useState(getInitialRuntimeState);
|
|
579
642
|
const { enqueueOperation, isMountedRef } = useBubblesRuntimeOperationQueue(setState);
|
|
580
643
|
const lastAutomaticSyncSignatureRef = useRef2(null);
|
|
644
|
+
const currentDeviceIdRef = useRef2(state.deviceId);
|
|
581
645
|
const previousUserIdRef = useRef2(normalizedUserId);
|
|
582
646
|
const buildAutomaticSyncSignature = useCallback2((deviceId) => {
|
|
583
647
|
return getAutomaticSyncSignature({
|
|
@@ -603,6 +667,9 @@ function BubblesNotificationsProvider(props) {
|
|
|
603
667
|
clearBubblesForegroundPresentation();
|
|
604
668
|
};
|
|
605
669
|
}, [isMountedRef]);
|
|
670
|
+
useEffect(() => {
|
|
671
|
+
currentDeviceIdRef.current = state.deviceId;
|
|
672
|
+
}, [state.deviceId]);
|
|
606
673
|
useEffect(() => {
|
|
607
674
|
let isCancelled = false;
|
|
608
675
|
void (async () => {
|
|
@@ -670,6 +737,7 @@ function BubblesNotificationsProvider(props) {
|
|
|
670
737
|
appVersion,
|
|
671
738
|
deviceId: storedDeviceId
|
|
672
739
|
});
|
|
740
|
+
currentDeviceIdRef.current = result.deviceId;
|
|
673
741
|
return getRuntimeStateUpdate(result);
|
|
674
742
|
});
|
|
675
743
|
}, [
|
|
@@ -703,9 +771,10 @@ function BubblesNotificationsProvider(props) {
|
|
|
703
771
|
userId: normalizedUserId,
|
|
704
772
|
aliasing,
|
|
705
773
|
appVersion,
|
|
706
|
-
deviceId:
|
|
774
|
+
deviceId: currentDeviceIdRef.current,
|
|
707
775
|
registrationOptions: options
|
|
708
776
|
});
|
|
777
|
+
currentDeviceIdRef.current = result.deviceId;
|
|
709
778
|
if (result.deviceId !== null) {
|
|
710
779
|
lastAutomaticSyncSignatureRef.current = buildAutomaticSyncSignature(
|
|
711
780
|
result.deviceId
|
|
@@ -714,8 +783,33 @@ function BubblesNotificationsProvider(props) {
|
|
|
714
783
|
return getRuntimeStateUpdate(result);
|
|
715
784
|
});
|
|
716
785
|
}
|
|
786
|
+
function addDeviceAttribute(name, value) {
|
|
787
|
+
return enqueueOperation(async () => {
|
|
788
|
+
const attributeName = getRequiredNonEmptyString(name, "attributeName");
|
|
789
|
+
if (value === void 0) {
|
|
790
|
+
failWithBubblesError(
|
|
791
|
+
'"addDeviceAttribute()" requires "value" to be a JSON-compatible value.'
|
|
792
|
+
);
|
|
793
|
+
}
|
|
794
|
+
const deviceId = currentDeviceIdRef.current;
|
|
795
|
+
if (deviceId === null) {
|
|
796
|
+
failWithBubblesError(
|
|
797
|
+
'"addDeviceAttribute()" requires a backend "deviceId" before attributes can be synced.'
|
|
798
|
+
);
|
|
799
|
+
}
|
|
800
|
+
await updateBubblesDeviceAttributes({
|
|
801
|
+
apiBaseUrl,
|
|
802
|
+
appKey,
|
|
803
|
+
deviceId,
|
|
804
|
+
attributes: {
|
|
805
|
+
[attributeName]: value
|
|
806
|
+
}
|
|
807
|
+
});
|
|
808
|
+
});
|
|
809
|
+
}
|
|
717
810
|
const contextValue = {
|
|
718
811
|
registerDevice,
|
|
812
|
+
addDeviceAttribute,
|
|
719
813
|
deviceId: state.deviceId,
|
|
720
814
|
pushToken: state.pushToken,
|
|
721
815
|
tokenType: state.tokenType,
|
|
@@ -751,6 +845,7 @@ export {
|
|
|
751
845
|
buildCreateDevicePayload,
|
|
752
846
|
buildStoredDeliveryStatusEventKey,
|
|
753
847
|
buildUpdateDevicePayload,
|
|
848
|
+
collectBubblesDeviceAttributes,
|
|
754
849
|
createBubblesDevice,
|
|
755
850
|
createBubblesDeviceClient,
|
|
756
851
|
createStoredDeliveryStatusEvent,
|
|
@@ -785,5 +880,6 @@ export {
|
|
|
785
880
|
storeDeviceState,
|
|
786
881
|
syncBubblesDevice,
|
|
787
882
|
updateBubblesDevice,
|
|
883
|
+
updateBubblesDeviceAttributes,
|
|
788
884
|
useBubblesNotifications
|
|
789
885
|
};
|
package/dist/register-task.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fishonfire/bubbles-expo",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Expo config plugin, explicit background registration API, and React provider for Bubbles notifications.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -68,8 +68,10 @@
|
|
|
68
68
|
}
|
|
69
69
|
},
|
|
70
70
|
"dependencies": {
|
|
71
|
+
"expo-application": "~57.0.2",
|
|
71
72
|
"@fishonfire/bubbles-js": "0.1.0",
|
|
72
73
|
"expo-constants": "~57.0.16",
|
|
74
|
+
"expo-device": "~57.0.1",
|
|
73
75
|
"expo-file-system": "~57.0.6"
|
|
74
76
|
},
|
|
75
77
|
"devDependencies": {
|
package/plugin/build/index.js
CHANGED
|
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const config_1 = require("./config");
|
|
7
7
|
const with_firebase_messaging_manifest_1 = __importDefault(require("./with-firebase-messaging-manifest"));
|
|
8
|
+
const with_firebase_messaging_registration_1 = __importDefault(require("./with-firebase-messaging-registration"));
|
|
8
9
|
const with_expo_notifications_1 = __importDefault(require("./with-expo-notifications"));
|
|
9
10
|
const with_runtime_defaults_1 = __importDefault(require("./with-runtime-defaults"));
|
|
10
11
|
const with_rnfirebase_disable_spm_1 = __importDefault(require("./with-rnfirebase-disable-spm"));
|
|
@@ -13,6 +14,7 @@ const withBubblesNotificationsExpo = (config, input) => {
|
|
|
13
14
|
config = (0, with_runtime_defaults_1.default)(config, options);
|
|
14
15
|
config = (0, with_expo_notifications_1.default)(config, options);
|
|
15
16
|
config = (0, with_firebase_messaging_manifest_1.default)(config, options);
|
|
17
|
+
config = (0, with_firebase_messaging_registration_1.default)(config, options);
|
|
16
18
|
config = (0, with_rnfirebase_disable_spm_1.default)(config);
|
|
17
19
|
return config;
|
|
18
20
|
};
|
|
@@ -3,6 +3,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
const config_plugins_1 = require("expo/config-plugins");
|
|
4
4
|
const FCM_DEFAULT_CHANNEL = 'com.google.firebase.messaging.default_notification_channel_id';
|
|
5
5
|
const FCM_DEFAULT_COLOR = 'com.google.firebase.messaging.default_notification_color';
|
|
6
|
+
const FCM_INSTALLATION_ID_ENABLED = 'firebase_messaging_installation_id_enabled';
|
|
7
|
+
const RN_FIREBASE_MESSAGING_SERVICE = 'io.invertase.firebase.messaging.ReactNativeFirebaseMessagingService';
|
|
8
|
+
const BUBBLES_FIREBASE_MESSAGING_SERVICE = '.BubblesFirebaseMessagingService';
|
|
9
|
+
const BUBBLES_FIREBASE_MESSAGING_REGISTRAR = '.BubblesFirebaseMessagingRegistrar';
|
|
6
10
|
const withFirebaseMessagingManifest = (config, options) => {
|
|
7
11
|
return (0, config_plugins_1.withAndroidManifest)(config, config => {
|
|
8
12
|
const manifest = config.modResults;
|
|
@@ -17,6 +21,8 @@ const withFirebaseMessagingManifest = (config, options) => {
|
|
|
17
21
|
if (channel) {
|
|
18
22
|
setToolsReplace(channel.$, 'android:value');
|
|
19
23
|
}
|
|
24
|
+
config_plugins_1.AndroidConfig.Manifest.addMetaDataItemToMainApplication(application, FCM_INSTALLATION_ID_ENABLED, 'true', 'value');
|
|
25
|
+
addBubblesFirebaseMessagingRegistrationEntries(application, getAndroidPackageName(config));
|
|
20
26
|
// Expo only creates the FCM color metadata when a notification
|
|
21
27
|
// color has actually been configured.
|
|
22
28
|
if (options.androidNotificationColor) {
|
|
@@ -29,6 +35,36 @@ const withFirebaseMessagingManifest = (config, options) => {
|
|
|
29
35
|
return config;
|
|
30
36
|
});
|
|
31
37
|
};
|
|
38
|
+
function getAndroidPackageName(config) {
|
|
39
|
+
const packageName = config.android?.package?.trim();
|
|
40
|
+
if (!packageName) {
|
|
41
|
+
throw new Error('[@fishonfire/bubbles-expo] "expo.android.package" is required to configure Android Firebase Messaging registration.');
|
|
42
|
+
}
|
|
43
|
+
return packageName;
|
|
44
|
+
}
|
|
45
|
+
function addBubblesFirebaseMessagingRegistrationEntries(application, packageName) {
|
|
46
|
+
application.service = application.service ?? [];
|
|
47
|
+
application.provider = application.provider ?? [];
|
|
48
|
+
removeManifestService(application, RN_FIREBASE_MESSAGING_SERVICE);
|
|
49
|
+
removeManifestService(application, BUBBLES_FIREBASE_MESSAGING_SERVICE);
|
|
50
|
+
upsertManifestProvider(application, {
|
|
51
|
+
$: {
|
|
52
|
+
'android:name': BUBBLES_FIREBASE_MESSAGING_REGISTRAR,
|
|
53
|
+
'android:authorities': `${packageName}.bubblesnotifications.fcmregistrar`,
|
|
54
|
+
'android:exported': 'false',
|
|
55
|
+
'android:initOrder': '100',
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
function removeManifestService(application, serviceName) {
|
|
60
|
+
application.service = (application.service ?? []).filter(item => item.$['android:name'] !== serviceName);
|
|
61
|
+
}
|
|
62
|
+
function upsertManifestProvider(application, provider) {
|
|
63
|
+
application.provider = [
|
|
64
|
+
...(application.provider ?? []).filter(item => item.$['android:name'] !== provider.$['android:name']),
|
|
65
|
+
provider,
|
|
66
|
+
];
|
|
67
|
+
}
|
|
32
68
|
function setToolsReplace(attributes, value) {
|
|
33
69
|
attributes['tools:replace'] = value;
|
|
34
70
|
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { ConfigPlugin } from 'expo/config-plugins';
|
|
2
|
+
import type { NormalizedBubblesNotificationsExpoPluginConfig } from './config';
|
|
3
|
+
declare const withFirebaseMessagingRegistration: ConfigPlugin<NormalizedBubblesNotificationsExpoPluginConfig>;
|
|
4
|
+
export default withFirebaseMessagingRegistration;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
7
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
8
|
+
const config_plugins_1 = require("expo/config-plugins");
|
|
9
|
+
const REGISTRAR_CLASS = 'BubblesFirebaseMessagingRegistrar';
|
|
10
|
+
const withFirebaseMessagingRegistration = config => {
|
|
11
|
+
return (0, config_plugins_1.withDangerousMod)(config, [
|
|
12
|
+
'android',
|
|
13
|
+
async (config) => {
|
|
14
|
+
const packageName = getAndroidPackageName(config);
|
|
15
|
+
const sourceDirectory = node_path_1.default.join(config.modRequest.projectRoot, 'android', 'app', 'src', 'main', 'java', ...packageName.split('.'));
|
|
16
|
+
await promises_1.default.mkdir(sourceDirectory, { recursive: true });
|
|
17
|
+
await promises_1.default.writeFile(node_path_1.default.join(sourceDirectory, `${REGISTRAR_CLASS}.java`), buildFirebaseMessagingRegistrarSource(packageName));
|
|
18
|
+
return config;
|
|
19
|
+
},
|
|
20
|
+
]);
|
|
21
|
+
};
|
|
22
|
+
function getAndroidPackageName(config) {
|
|
23
|
+
const packageName = config.android?.package?.trim();
|
|
24
|
+
if (!packageName) {
|
|
25
|
+
throw new Error('[@fishonfire/bubbles-expo] "expo.android.package" is required to configure Android Firebase Messaging registration.');
|
|
26
|
+
}
|
|
27
|
+
return packageName;
|
|
28
|
+
}
|
|
29
|
+
function buildFirebaseMessagingRegistrarSource(packageName) {
|
|
30
|
+
return `package ${packageName};
|
|
31
|
+
|
|
32
|
+
import android.content.ContentProvider;
|
|
33
|
+
import android.content.ContentValues;
|
|
34
|
+
import android.database.Cursor;
|
|
35
|
+
import android.net.Uri;
|
|
36
|
+
import android.util.Log;
|
|
37
|
+
|
|
38
|
+
import androidx.annotation.NonNull;
|
|
39
|
+
import androidx.annotation.Nullable;
|
|
40
|
+
|
|
41
|
+
public final class ${REGISTRAR_CLASS} extends ContentProvider {
|
|
42
|
+
private static final String TAG = "BubblesFCMRegistrar";
|
|
43
|
+
|
|
44
|
+
@Override
|
|
45
|
+
public boolean onCreate() {
|
|
46
|
+
try {
|
|
47
|
+
Class<?> firebaseAppClass = Class.forName("com.google.firebase.FirebaseApp");
|
|
48
|
+
Class<?> firebaseMessagingClass = Class.forName("com.google.firebase.messaging.FirebaseMessaging");
|
|
49
|
+
|
|
50
|
+
if (getContext() != null) {
|
|
51
|
+
Object apps = firebaseAppClass
|
|
52
|
+
.getMethod("getApps", android.content.Context.class)
|
|
53
|
+
.invoke(null, getContext());
|
|
54
|
+
|
|
55
|
+
if (apps instanceof java.util.List && ((java.util.List<?>) apps).isEmpty()) {
|
|
56
|
+
firebaseAppClass
|
|
57
|
+
.getMethod("initializeApp", android.content.Context.class)
|
|
58
|
+
.invoke(null, getContext());
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
Object messaging = firebaseMessagingClass
|
|
63
|
+
.getMethod("getInstance")
|
|
64
|
+
.invoke(null);
|
|
65
|
+
|
|
66
|
+
firebaseMessagingClass
|
|
67
|
+
.getMethod("register")
|
|
68
|
+
.invoke(messaging);
|
|
69
|
+
} catch (ReflectiveOperationException | RuntimeException error) {
|
|
70
|
+
Log.w(TAG, "Failed to start Firebase Messaging registration.", error);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
@Nullable
|
|
77
|
+
@Override
|
|
78
|
+
public Cursor query(
|
|
79
|
+
@NonNull Uri uri,
|
|
80
|
+
@Nullable String[] projection,
|
|
81
|
+
@Nullable String selection,
|
|
82
|
+
@Nullable String[] selectionArgs,
|
|
83
|
+
@Nullable String sortOrder) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
@Nullable
|
|
88
|
+
@Override
|
|
89
|
+
public String getType(@NonNull Uri uri) {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
@Nullable
|
|
94
|
+
@Override
|
|
95
|
+
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
@Override
|
|
100
|
+
public int delete(
|
|
101
|
+
@NonNull Uri uri,
|
|
102
|
+
@Nullable String selection,
|
|
103
|
+
@Nullable String[] selectionArgs) {
|
|
104
|
+
return 0;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
@Override
|
|
108
|
+
public int update(
|
|
109
|
+
@NonNull Uri uri,
|
|
110
|
+
@Nullable ContentValues values,
|
|
111
|
+
@Nullable String selection,
|
|
112
|
+
@Nullable String[] selectionArgs) {
|
|
113
|
+
return 0;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
`;
|
|
117
|
+
}
|
|
118
|
+
exports.default = withFirebaseMessagingRegistration;
|