@teardown/react-native 2.0.24 → 2.0.28
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/docs/adapters/device/basic.mdx +59 -0
- package/docs/adapters/device/device-info.mdx +76 -0
- package/docs/adapters/device/expo.mdx +61 -0
- package/docs/adapters/device/index.mdx +102 -0
- package/docs/adapters/device/meta.json +4 -0
- package/docs/adapters/index.mdx +96 -0
- package/docs/adapters/meta.json +4 -0
- package/docs/adapters/notifications/expo.mdx +127 -0
- package/docs/adapters/notifications/firebase.mdx +142 -0
- package/docs/adapters/notifications/index.mdx +100 -0
- package/docs/adapters/notifications/meta.json +4 -0
- package/docs/adapters/notifications/wix.mdx +140 -0
- package/docs/adapters/storage/async-storage.mdx +95 -0
- package/docs/adapters/storage/index.mdx +93 -0
- package/docs/adapters/storage/meta.json +4 -0
- package/docs/adapters/storage/mmkv.mdx +86 -0
- package/docs/advanced.mdx +280 -0
- package/docs/api-reference.mdx +241 -0
- package/docs/core-concepts.mdx +158 -0
- package/docs/force-updates.mdx +185 -0
- package/docs/getting-started.mdx +156 -0
- package/docs/hooks-reference.mdx +232 -0
- package/docs/identity.mdx +171 -0
- package/docs/index.mdx +61 -0
- package/docs/logging.mdx +144 -0
- package/docs/meta.json +14 -0
- package/package.json +49 -31
- package/src/clients/api/index.ts +1 -1
- package/src/clients/device/adapters/basic.adapter.ts +57 -66
- package/src/clients/device/adapters/device-info.adapter.ts +21 -28
- package/src/clients/device/adapters/device.adpater-interface.ts +1 -8
- package/src/clients/device/adapters/expo.adapter.ts +33 -40
- package/src/clients/device/device.client.test.ts +20 -35
- package/src/clients/device/device.client.ts +0 -3
- package/src/clients/identity/identity.client.test.ts +8 -8
- package/src/clients/identity/identity.client.ts +1 -1
- package/src/clients/identity/index.ts +1 -1
- package/src/clients/logging/index.ts +1 -1
- package/src/clients/notifications/adapters/expo-notifications.adapter.ts +105 -0
- package/src/clients/notifications/adapters/firebase-messaging.adapter.ts +87 -0
- package/src/clients/notifications/adapters/notifications.adapter-interface.ts +112 -0
- package/src/clients/notifications/adapters/wix-notifications.adapter.ts +183 -0
- package/src/clients/notifications/index.ts +2 -0
- package/src/clients/notifications/notifications.client.ts +214 -3
- package/src/clients/storage/adapters/async-storage.adapter.ts +2 -6
- package/src/clients/storage/adapters/storage-adapters.test.ts +2 -7
- package/src/clients/storage/adapters/storage.adpater-interface.ts +1 -5
- package/src/clients/utils/index.ts +1 -1
- package/src/clients/utils/utils.client.ts +1 -1
- package/src/exports/adapters/async-storage.ts +1 -1
- package/src/exports/adapters/expo.ts +1 -1
- package/src/exports/expo.ts +2 -0
- package/src/exports/firebase.ts +1 -0
- package/src/exports/index.ts +6 -9
- package/src/exports/wix.ts +1 -0
- package/src/hooks/use-force-update.ts +31 -34
- package/src/index.ts +1 -0
- package/src/teardown.core.ts +0 -2
- package/src/.DS_Store +0 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { Platform } from "react-native";
|
|
2
|
+
import { type Notification, Notifications, type Registered } from "react-native-notifications";
|
|
3
|
+
import { NotificationPlatformEnum } from "../../device/device.client";
|
|
4
|
+
import {
|
|
5
|
+
type DataMessage,
|
|
6
|
+
NotificationAdapter,
|
|
7
|
+
type PermissionStatus,
|
|
8
|
+
type PushNotification,
|
|
9
|
+
type Unsubscribe,
|
|
10
|
+
} from "./notifications.adapter-interface";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Notification adapter for react-native-notifications (Wix) library.
|
|
14
|
+
* Uses native FCM/APNS tokens for push notifications.
|
|
15
|
+
*
|
|
16
|
+
* Note: This library uses event-based token delivery, which is normalized
|
|
17
|
+
* to a Promise-based API by this adapter.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```typescript
|
|
21
|
+
* import { NotificationsClient } from "@teardown/react-native";
|
|
22
|
+
* import { WixNotificationsAdapter } from "@teardown/react-native/wix-notifications";
|
|
23
|
+
*
|
|
24
|
+
* const notifications = new NotificationsClient(logging, storage, {
|
|
25
|
+
* adapter: new WixNotificationsAdapter()
|
|
26
|
+
* });
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export class WixNotificationsAdapter extends NotificationAdapter {
|
|
30
|
+
private tokenPromise: Promise<string> | null = null;
|
|
31
|
+
private tokenResolver: ((token: string) => void) | null = null;
|
|
32
|
+
private currentToken: string | null = null;
|
|
33
|
+
private tokenListeners: Set<(token: string) => void> = new Set();
|
|
34
|
+
|
|
35
|
+
constructor() {
|
|
36
|
+
super();
|
|
37
|
+
this.setupTokenListener();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
get platform(): NotificationPlatformEnum {
|
|
41
|
+
return Platform.OS === "ios" ? NotificationPlatformEnum.APNS : NotificationPlatformEnum.FCM;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async getToken(): Promise<string | null> {
|
|
45
|
+
if (this.currentToken) {
|
|
46
|
+
return this.currentToken;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Create a promise that will resolve when we receive the token
|
|
50
|
+
if (!this.tokenPromise) {
|
|
51
|
+
this.tokenPromise = new Promise<string>((resolve) => {
|
|
52
|
+
this.tokenResolver = resolve;
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Trigger registration to get token
|
|
56
|
+
Notifications.registerRemoteNotifications();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
// Wait for token with timeout
|
|
61
|
+
const token = await Promise.race([
|
|
62
|
+
this.tokenPromise,
|
|
63
|
+
new Promise<null>((_, reject) => setTimeout(() => reject(new Error("Token timeout")), 10000)),
|
|
64
|
+
]);
|
|
65
|
+
return token;
|
|
66
|
+
} catch {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async requestPermissions(): Promise<PermissionStatus> {
|
|
72
|
+
return new Promise((resolve) => {
|
|
73
|
+
// Set up one-time listener for registration result
|
|
74
|
+
const registeredHandler = () => {
|
|
75
|
+
resolve({ granted: true, canAskAgain: true });
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const deniedHandler = () => {
|
|
79
|
+
resolve({ granted: false, canAskAgain: false });
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// Subscribe to registration events
|
|
83
|
+
Notifications.events().registerRemoteNotificationsRegistered(registeredHandler);
|
|
84
|
+
Notifications.events().registerRemoteNotificationsRegistrationDenied(deniedHandler);
|
|
85
|
+
|
|
86
|
+
// Trigger registration
|
|
87
|
+
Notifications.registerRemoteNotifications();
|
|
88
|
+
|
|
89
|
+
// Timeout fallback
|
|
90
|
+
setTimeout(() => {
|
|
91
|
+
resolve({ granted: false, canAskAgain: true });
|
|
92
|
+
}, 10000);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
onTokenRefresh(listener: (token: string) => void): Unsubscribe {
|
|
97
|
+
this.tokenListeners.add(listener);
|
|
98
|
+
|
|
99
|
+
// If we already have a token, call listener immediately
|
|
100
|
+
if (this.currentToken) {
|
|
101
|
+
listener(this.currentToken);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return () => {
|
|
105
|
+
this.tokenListeners.delete(listener);
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
onNotificationReceived(listener: (notification: PushNotification) => void): Unsubscribe {
|
|
110
|
+
const subscription = Notifications.events().registerNotificationReceivedForeground(
|
|
111
|
+
(
|
|
112
|
+
notification: Notification,
|
|
113
|
+
completion: (response: { alert: boolean; sound: boolean; badge: boolean }) => void
|
|
114
|
+
) => {
|
|
115
|
+
const payload = notification.payload;
|
|
116
|
+
listener({
|
|
117
|
+
title: payload.title,
|
|
118
|
+
body: payload.body,
|
|
119
|
+
data: payload,
|
|
120
|
+
});
|
|
121
|
+
completion({ alert: true, sound: true, badge: true });
|
|
122
|
+
}
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
return () => subscription.remove();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
onNotificationOpened(listener: (notification: PushNotification) => void): Unsubscribe {
|
|
129
|
+
const subscription = Notifications.events().registerNotificationOpened(
|
|
130
|
+
(notification: Notification, completion: () => void) => {
|
|
131
|
+
const payload = notification.payload;
|
|
132
|
+
listener({
|
|
133
|
+
title: payload.title,
|
|
134
|
+
body: payload.body,
|
|
135
|
+
data: payload,
|
|
136
|
+
});
|
|
137
|
+
completion();
|
|
138
|
+
}
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
return () => subscription.remove();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
onDataMessage(listener: (message: DataMessage) => void): Unsubscribe {
|
|
145
|
+
// Wix library handles data-only messages through the same foreground listener
|
|
146
|
+
// but without title/body in the payload
|
|
147
|
+
const subscription = Notifications.events().registerNotificationReceivedForeground(
|
|
148
|
+
(
|
|
149
|
+
notification: Notification,
|
|
150
|
+
completion: (response: { alert: boolean; sound: boolean; badge: boolean }) => void
|
|
151
|
+
) => {
|
|
152
|
+
const payload = notification.payload;
|
|
153
|
+
// Data-only message: has payload but no title or body
|
|
154
|
+
if (payload && !payload.title && !payload.body) {
|
|
155
|
+
listener({
|
|
156
|
+
data: payload,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
completion({ alert: false, sound: false, badge: false });
|
|
160
|
+
}
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
return () => subscription.remove();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
private setupTokenListener(): void {
|
|
167
|
+
Notifications.events().registerRemoteNotificationsRegistered((event: Registered) => {
|
|
168
|
+
this.currentToken = event.deviceToken;
|
|
169
|
+
|
|
170
|
+
// Resolve any pending getToken promise
|
|
171
|
+
if (this.tokenResolver) {
|
|
172
|
+
this.tokenResolver(event.deviceToken);
|
|
173
|
+
this.tokenResolver = null;
|
|
174
|
+
this.tokenPromise = null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Notify all listeners
|
|
178
|
+
for (const listener of this.tokenListeners) {
|
|
179
|
+
listener(event.deviceToken);
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
}
|
|
@@ -1,10 +1,221 @@
|
|
|
1
|
-
|
|
1
|
+
import EventEmitter from "eventemitter3";
|
|
2
|
+
import type { NotificationPlatformEnum } from "../device/device.client";
|
|
3
|
+
import type { Logger, LoggingClient } from "../logging";
|
|
4
|
+
import type { StorageClient, SupportedStorage } from "../storage";
|
|
5
|
+
import type {
|
|
6
|
+
DataMessage,
|
|
7
|
+
NotificationAdapter,
|
|
8
|
+
PermissionStatus,
|
|
9
|
+
PushNotification,
|
|
10
|
+
Unsubscribe,
|
|
11
|
+
} from "./adapters/notifications.adapter-interface";
|
|
2
12
|
|
|
13
|
+
interface NotificationEvents {
|
|
14
|
+
TOKEN_CHANGED: (token: string) => void;
|
|
15
|
+
NOTIFICATION_RECEIVED: (notification: PushNotification) => void;
|
|
16
|
+
NOTIFICATION_OPENED: (notification: PushNotification) => void;
|
|
17
|
+
DATA_MESSAGE: (message: DataMessage) => void;
|
|
18
|
+
}
|
|
3
19
|
|
|
20
|
+
export interface NotificationsClientOptions {
|
|
21
|
+
/** The notification adapter to use (expo, firebase, wix) */
|
|
22
|
+
adapter: NotificationAdapter;
|
|
23
|
+
}
|
|
4
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Client for managing push notifications across different providers.
|
|
27
|
+
*
|
|
28
|
+
* Uses an adapter pattern to support multiple notification libraries:
|
|
29
|
+
* - expo-notifications (Expo projects)
|
|
30
|
+
* - @react-native-firebase/messaging (Firebase FCM)
|
|
31
|
+
* - react-native-notifications (Wix)
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```typescript
|
|
35
|
+
* import { NotificationsClient } from "@teardown/react-native";
|
|
36
|
+
* import { ExpoNotificationsAdapter } from "@teardown/react-native/expo-notifications";
|
|
37
|
+
*
|
|
38
|
+
* const notifications = new NotificationsClient(logging, storage, {
|
|
39
|
+
* adapter: new ExpoNotificationsAdapter()
|
|
40
|
+
* });
|
|
41
|
+
*
|
|
42
|
+
* await notifications.requestPermissions();
|
|
43
|
+
* const token = await notifications.getToken();
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
5
46
|
export class NotificationsClient {
|
|
6
|
-
|
|
47
|
+
private logger: Logger;
|
|
48
|
+
private storage: SupportedStorage;
|
|
49
|
+
private emitter = new EventEmitter<NotificationEvents>();
|
|
50
|
+
private token: string | null = null;
|
|
51
|
+
private initialized = false;
|
|
52
|
+
private adapterUnsubscribers: Unsubscribe[] = [];
|
|
7
53
|
|
|
54
|
+
constructor(
|
|
55
|
+
logging: LoggingClient,
|
|
56
|
+
storage: StorageClient,
|
|
57
|
+
private readonly options: NotificationsClientOptions
|
|
58
|
+
) {
|
|
59
|
+
this.logger = logging.createLogger({
|
|
60
|
+
name: "NotificationsClient",
|
|
61
|
+
});
|
|
62
|
+
this.storage = storage.createStorage("notifications");
|
|
63
|
+
}
|
|
8
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Initialize the notifications client.
|
|
67
|
+
* Sets up event listeners for token refresh and incoming notifications.
|
|
68
|
+
* Call this after constructing the client.
|
|
69
|
+
*/
|
|
70
|
+
async initialize(): Promise<void> {
|
|
71
|
+
if (this.initialized) {
|
|
72
|
+
this.logger.warn("NotificationsClient already initialized");
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
9
75
|
|
|
10
|
-
|
|
76
|
+
this.logger.debug("Initializing NotificationsClient");
|
|
77
|
+
|
|
78
|
+
// Load cached token
|
|
79
|
+
const cachedToken = this.storage.getItem("token");
|
|
80
|
+
if (cachedToken) {
|
|
81
|
+
this.token = cachedToken;
|
|
82
|
+
this.logger.debug("Loaded cached token");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Subscribe to adapter events
|
|
86
|
+
const tokenRefreshUnsub = this.options.adapter.onTokenRefresh((newToken) => {
|
|
87
|
+
this.handleTokenChange(newToken);
|
|
88
|
+
});
|
|
89
|
+
this.adapterUnsubscribers.push(tokenRefreshUnsub);
|
|
90
|
+
|
|
91
|
+
const notificationReceivedUnsub = this.options.adapter.onNotificationReceived((notification) => {
|
|
92
|
+
this.logger.debug("Notification received in foreground", notification);
|
|
93
|
+
this.emitter.emit("NOTIFICATION_RECEIVED", notification);
|
|
94
|
+
});
|
|
95
|
+
this.adapterUnsubscribers.push(notificationReceivedUnsub);
|
|
96
|
+
|
|
97
|
+
const notificationOpenedUnsub = this.options.adapter.onNotificationOpened((notification) => {
|
|
98
|
+
this.logger.debug("Notification opened by user", notification);
|
|
99
|
+
this.emitter.emit("NOTIFICATION_OPENED", notification);
|
|
100
|
+
});
|
|
101
|
+
this.adapterUnsubscribers.push(notificationOpenedUnsub);
|
|
102
|
+
|
|
103
|
+
const dataMessageUnsub = this.options.adapter.onDataMessage((message) => {
|
|
104
|
+
this.logger.debug("Data-only message received", message);
|
|
105
|
+
this.emitter.emit("DATA_MESSAGE", message);
|
|
106
|
+
});
|
|
107
|
+
this.adapterUnsubscribers.push(dataMessageUnsub);
|
|
108
|
+
|
|
109
|
+
this.initialized = true;
|
|
110
|
+
this.logger.debug("NotificationsClient initialized");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Request push notification permissions from the user.
|
|
115
|
+
*/
|
|
116
|
+
async requestPermissions(): Promise<PermissionStatus> {
|
|
117
|
+
this.logger.debug("Requesting notification permissions");
|
|
118
|
+
const status = await this.options.adapter.requestPermissions();
|
|
119
|
+
this.logger.debug("Permission status", status);
|
|
120
|
+
return status;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Get the current push notification token.
|
|
125
|
+
* Returns cached token if available, otherwise fetches from adapter.
|
|
126
|
+
*/
|
|
127
|
+
async getToken(): Promise<string | null> {
|
|
128
|
+
// Return cached token if we have one
|
|
129
|
+
if (this.token) {
|
|
130
|
+
return this.token;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
this.logger.debug("Fetching token from adapter");
|
|
134
|
+
const token = await this.options.adapter.getToken();
|
|
135
|
+
|
|
136
|
+
if (token) {
|
|
137
|
+
this.handleTokenChange(token);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return token;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Get the notification platform type (EXPO, FCM, APNS).
|
|
145
|
+
*/
|
|
146
|
+
get platform(): NotificationPlatformEnum {
|
|
147
|
+
return this.options.adapter.platform;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Subscribe to token change events.
|
|
152
|
+
*
|
|
153
|
+
* @param listener - Callback invoked when token changes
|
|
154
|
+
* @returns Unsubscribe function
|
|
155
|
+
*/
|
|
156
|
+
onTokenChange(listener: (token: string) => void): Unsubscribe {
|
|
157
|
+
this.emitter.addListener("TOKEN_CHANGED", listener);
|
|
158
|
+
return () => this.emitter.removeListener("TOKEN_CHANGED", listener);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Subscribe to foreground notification events.
|
|
163
|
+
*
|
|
164
|
+
* @param listener - Callback invoked when notification received in foreground
|
|
165
|
+
* @returns Unsubscribe function
|
|
166
|
+
*/
|
|
167
|
+
onNotificationReceived(listener: (notification: PushNotification) => void): Unsubscribe {
|
|
168
|
+
this.emitter.addListener("NOTIFICATION_RECEIVED", listener);
|
|
169
|
+
return () => this.emitter.removeListener("NOTIFICATION_RECEIVED", listener);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Subscribe to notification opened events.
|
|
174
|
+
*
|
|
175
|
+
* @param listener - Callback invoked when user taps a notification
|
|
176
|
+
* @returns Unsubscribe function
|
|
177
|
+
*/
|
|
178
|
+
onNotificationOpened(listener: (notification: PushNotification) => void): Unsubscribe {
|
|
179
|
+
this.emitter.addListener("NOTIFICATION_OPENED", listener);
|
|
180
|
+
return () => this.emitter.removeListener("NOTIFICATION_OPENED", listener);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Subscribe to data-only message events (silent/background push).
|
|
185
|
+
* These are messages without notification display, used for background data sync.
|
|
186
|
+
*
|
|
187
|
+
* @param listener - Callback invoked when data message received
|
|
188
|
+
* @returns Unsubscribe function
|
|
189
|
+
*/
|
|
190
|
+
onDataMessage(listener: (message: DataMessage) => void): Unsubscribe {
|
|
191
|
+
this.emitter.addListener("DATA_MESSAGE", listener);
|
|
192
|
+
return () => this.emitter.removeListener("DATA_MESSAGE", listener);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Clean up event listeners and resources.
|
|
197
|
+
*/
|
|
198
|
+
destroy(): void {
|
|
199
|
+
this.logger.debug("Destroying NotificationsClient");
|
|
200
|
+
|
|
201
|
+
// Unsubscribe from adapter events
|
|
202
|
+
for (const unsub of this.adapterUnsubscribers) {
|
|
203
|
+
unsub();
|
|
204
|
+
}
|
|
205
|
+
this.adapterUnsubscribers = [];
|
|
206
|
+
|
|
207
|
+
// Remove all listeners
|
|
208
|
+
this.emitter.removeAllListeners();
|
|
209
|
+
|
|
210
|
+
this.initialized = false;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
private handleTokenChange(newToken: string): void {
|
|
214
|
+
if (this.token !== newToken) {
|
|
215
|
+
this.logger.debug("Token changed");
|
|
216
|
+
this.token = newToken;
|
|
217
|
+
this.storage.setItem("token", newToken);
|
|
218
|
+
this.emitter.emit("TOKEN_CHANGED", newToken);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import AsyncStorage from "@react-native-async-storage/async-storage";
|
|
2
2
|
import { StorageAdapter, type SupportedStorage } from "./storage.adpater-interface";
|
|
3
3
|
|
|
4
|
-
|
|
5
4
|
/**
|
|
6
5
|
* Creates a SupportedStorage adapter backed by AsyncStorage.
|
|
7
6
|
*
|
|
@@ -26,9 +25,7 @@ export class AsyncStorageAdapter extends StorageAdapter {
|
|
|
26
25
|
|
|
27
26
|
try {
|
|
28
27
|
const allKeys = await AsyncStorage.getAllKeys();
|
|
29
|
-
const relevantKeys = allKeys.filter((k) =>
|
|
30
|
-
k.startsWith(`${storageKey}:`)
|
|
31
|
-
);
|
|
28
|
+
const relevantKeys = allKeys.filter((k) => k.startsWith(`${storageKey}:`));
|
|
32
29
|
const pairs = await AsyncStorage.multiGet(relevantKeys);
|
|
33
30
|
|
|
34
31
|
for (const [fullKey, value] of pairs) {
|
|
@@ -75,5 +72,4 @@ export class AsyncStorageAdapter extends StorageAdapter {
|
|
|
75
72
|
},
|
|
76
73
|
};
|
|
77
74
|
}
|
|
78
|
-
|
|
79
|
-
}
|
|
75
|
+
}
|
|
@@ -29,9 +29,7 @@ function createMockAsyncStorage() {
|
|
|
29
29
|
const store = new Map<string, string>();
|
|
30
30
|
return {
|
|
31
31
|
getAllKeys: mock(async () => Array.from(store.keys())),
|
|
32
|
-
multiGet: mock(async (keys: string[]) =>
|
|
33
|
-
keys.map((k) => [k, store.get(k) ?? null] as [string, string | null])
|
|
34
|
-
),
|
|
32
|
+
multiGet: mock(async (keys: string[]) => keys.map((k) => [k, store.get(k) ?? null] as [string, string | null])),
|
|
35
33
|
setItem: mock(async (key: string, value: string) => {
|
|
36
34
|
store.set(key, value);
|
|
37
35
|
}),
|
|
@@ -528,10 +526,7 @@ describe("Edge Cases", () => {
|
|
|
528
526
|
test("handles special characters in keys (properly prefixed)", async () => {
|
|
529
527
|
storage.setItem("key:with/special-chars", "value");
|
|
530
528
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
531
|
-
expect(mockAsyncStorage.setItem).toHaveBeenCalledWith(
|
|
532
|
-
"edge-test:key:with/special-chars",
|
|
533
|
-
"value"
|
|
534
|
-
);
|
|
529
|
+
expect(mockAsyncStorage.setItem).toHaveBeenCalledWith("edge-test:key:with/special-chars", "value");
|
|
535
530
|
});
|
|
536
531
|
|
|
537
532
|
test("handles keys that look like prefixed keys", async () => {
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
/**
|
|
3
2
|
* A storage interface that is used to store data.
|
|
4
3
|
*/
|
|
@@ -16,15 +15,12 @@ export type SupportedStorage = {
|
|
|
16
15
|
* This interface is used to abstract the storage adapter implementation.
|
|
17
16
|
*/
|
|
18
17
|
export abstract class StorageAdapter {
|
|
19
|
-
|
|
20
18
|
/**
|
|
21
19
|
* Creates a storage instance for a given storage key.
|
|
22
20
|
* @param storageKey - The key to create the storage instance for.
|
|
23
21
|
* @returns A storage instance.
|
|
24
|
-
*
|
|
22
|
+
*
|
|
25
23
|
* We can have multiple storage instances for different purposes. Hence the storage key is used to create the storage instance.
|
|
26
24
|
*/
|
|
27
25
|
abstract createStorage(storageKey: string): SupportedStorage;
|
|
28
26
|
}
|
|
29
|
-
|
|
30
|
-
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export * from "./utils.client";
|
|
1
|
+
export * from "./utils.client";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export * from "../../clients/storage/adapters/async-storage.adapter";
|
|
1
|
+
export * from "../../clients/storage/adapters/async-storage.adapter";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export * from "../../clients/device/adapters/expo.adapter";
|
|
1
|
+
export * from "../../clients/device/adapters/expo.adapter";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "../clients/notifications/adapters/firebase-messaging.adapter";
|
package/src/exports/index.ts
CHANGED
|
@@ -1,16 +1,13 @@
|
|
|
1
|
-
export * from "../teardown.core";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
// Providers
|
|
5
|
-
export * from "../providers/teardown.provider";
|
|
6
|
-
|
|
7
1
|
// Clients
|
|
8
2
|
export * from "../clients/api";
|
|
3
|
+
export * from "../clients/device/device.client";
|
|
9
4
|
export * from "../clients/logging";
|
|
10
|
-
export * from "../clients/
|
|
5
|
+
export * from "../clients/notifications";
|
|
11
6
|
export * from "../clients/storage";
|
|
12
|
-
export * from "../clients/
|
|
13
|
-
|
|
7
|
+
export * from "../clients/utils";
|
|
14
8
|
// Hooks
|
|
15
9
|
export * from "../hooks/use-force-update";
|
|
16
10
|
export * from "../hooks/use-session";
|
|
11
|
+
// Providers
|
|
12
|
+
export * from "../providers/teardown.provider";
|
|
13
|
+
export * from "../teardown.core";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "../clients/notifications/adapters/wix-notifications.adapter";
|
|
@@ -3,45 +3,42 @@ import type { VersionStatus } from "../clients/force-update";
|
|
|
3
3
|
import { useTeardown } from "../contexts/teardown.context";
|
|
4
4
|
|
|
5
5
|
export interface UseForceUpdateResult {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
6
|
+
/**
|
|
7
|
+
* The current version status.
|
|
8
|
+
*/
|
|
9
|
+
versionStatus: VersionStatus;
|
|
10
|
+
/**
|
|
11
|
+
* Whether an update is available for this version, but is not required.
|
|
12
|
+
*/
|
|
13
|
+
isUpdateAvailable: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Whether an update is recommended for this version, but is not required.
|
|
16
|
+
*/
|
|
17
|
+
isUpdateRecommended: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Whether the the current version is out of date and is forced to be updated.
|
|
20
|
+
*/
|
|
21
|
+
isUpdateRequired: boolean;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
export const useForceUpdate = (): UseForceUpdateResult => {
|
|
25
|
-
|
|
25
|
+
const { core } = useTeardown();
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
core.forceUpdate.getVersionStatus()
|
|
29
|
-
);
|
|
27
|
+
const [versionStatus, setVersionStatus] = useState<VersionStatus>(core.forceUpdate.getVersionStatus());
|
|
30
28
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
const unsubscribe = core.forceUpdate.onVersionStatusChange(setVersionStatus);
|
|
31
|
+
return unsubscribe;
|
|
32
|
+
}, [core.forceUpdate]);
|
|
35
33
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
const isUpdateRequired = versionStatus.type === "update_required";
|
|
35
|
+
const isUpdateRecommended = versionStatus.type === "update_recommended";
|
|
36
|
+
const isUpdateAvailable = isUpdateRequired || isUpdateRecommended || versionStatus.type === "update_available";
|
|
39
37
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
38
|
+
return {
|
|
39
|
+
versionStatus,
|
|
40
|
+
isUpdateRequired,
|
|
41
|
+
isUpdateRecommended,
|
|
42
|
+
isUpdateAvailable,
|
|
43
|
+
};
|
|
46
44
|
};
|
|
47
|
-
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/src/teardown.core.ts
CHANGED
|
@@ -75,8 +75,6 @@ export class TeardownCore {
|
|
|
75
75
|
this.logger.debug("Initializing force update");
|
|
76
76
|
this.forceUpdate.initialize();
|
|
77
77
|
this.logger.debug("Force update initialized");
|
|
78
|
-
|
|
79
|
-
this.logger.debug("TeardownCore initialized");
|
|
80
78
|
}
|
|
81
79
|
|
|
82
80
|
setLogLevel(level: LogLevel): void {
|
package/src/.DS_Store
DELETED
|
Binary file
|