@webority/mobile-core 0.0.29 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commonjs/api/baseUrl.js +1 -34
- package/lib/commonjs/api/curl.js +1 -109
- package/lib/commonjs/api/multipart.js +1 -21
- package/lib/commonjs/api/publicClient.js +1 -79
- package/lib/commonjs/api/responseBodyText.js +1 -0
- package/lib/commonjs/api/retry.js +1 -91
- package/lib/commonjs/api/tokenRefresh.js +1 -205
- package/lib/commonjs/api/userClient.js +1 -265
- package/lib/commonjs/auth/authStore.js +1 -265
- package/lib/commonjs/auth/index.js +1 -44
- package/lib/commonjs/auth/jwt.js +1 -112
- package/lib/commonjs/config/index.js +1 -72
- package/lib/commonjs/deepLink/index.js +1 -286
- package/lib/commonjs/download/adapters/expoFs.js +1 -69
- package/lib/commonjs/download/adapters/shared.js +1 -13
- package/lib/commonjs/download/index.js +1 -250
- package/lib/commonjs/formatters/currency.js +1 -70
- package/lib/commonjs/formatters/date.js +1 -167
- package/lib/commonjs/formatters/dateMath.js +1 -311
- package/lib/commonjs/formatters/index.js +1 -246
- package/lib/commonjs/formatters/initials.js +1 -32
- package/lib/commonjs/formatters/intlCache.js +1 -37
- package/lib/commonjs/formatters/number.js +1 -89
- package/lib/commonjs/formatters/phone.js +1 -369
- package/lib/commonjs/formatters/windowsToIanaTable.js +1 -164
- package/lib/commonjs/geolocation/adapters/expoLocation.js +1 -126
- package/lib/commonjs/geolocation/index.js +1 -355
- package/lib/commonjs/index.js +1 -644
- package/lib/commonjs/initSDK.js +1 -66
- package/lib/commonjs/initUserAuth.js +1 -37
- package/lib/commonjs/logger/crashlytics.js +1 -0
- package/lib/commonjs/logger/index.js +1 -301
- package/lib/commonjs/logger/redact.js +1 -50
- package/lib/commonjs/network/index.js +1 -31
- package/lib/commonjs/network/networkStatus.js +1 -171
- package/lib/commonjs/permissions/calendar.js +1 -14
- package/lib/commonjs/permissions/camera.js +1 -14
- package/lib/commonjs/permissions/contacts.js +1 -10
- package/lib/commonjs/permissions/index.js +1 -133
- package/lib/commonjs/permissions/location.js +1 -14
- package/lib/commonjs/permissions/mediaLibrary.js +1 -10
- package/lib/commonjs/permissions/notifications.js +1 -10
- package/lib/commonjs/permissions/photos.js +1 -14
- package/lib/commonjs/phone/countries.js +1 -265
- package/lib/commonjs/push/index.js +1 -61
- package/lib/commonjs/push/push.js +1 -468
- package/lib/commonjs/push/status.js +1 -0
- package/lib/commonjs/push/task.js +1 -43
- package/lib/commonjs/secureScreen/index.js +1 -0
- package/lib/commonjs/share/index.js +1 -70
- package/lib/commonjs/smsRetriever/index.js +1 -91
- package/lib/commonjs/specs/NativeSmsRetriever.js +1 -15
- package/lib/commonjs/sqlite/adapters/expo.js +1 -53
- package/lib/commonjs/sqlite/index.js +1 -604
- package/lib/commonjs/storage/index.js +1 -214
- package/lib/commonjs/types/axios.js +1 -4
- package/lib/commonjs/types/globals.d.js +1 -6
- package/lib/commonjs/utils/base64.js +1 -24
- package/lib/commonjs/utils/imageCompression.js +1 -389
- package/lib/commonjs/utils/index.js +1 -19
- package/lib/commonjs/validators/index.js +1 -227
- package/lib/commonjs/versionCheck/index.js +1 -419
- package/lib/module/api/baseUrl.js +1 -30
- package/lib/module/api/curl.js +1 -104
- package/lib/module/api/multipart.js +1 -16
- package/lib/module/api/publicClient.js +1 -75
- package/lib/module/api/responseBodyText.js +1 -0
- package/lib/module/api/retry.js +1 -85
- package/lib/module/api/tokenRefresh.js +1 -192
- package/lib/module/api/userClient.js +1 -243
- package/lib/module/auth/authStore.js +1 -257
- package/lib/module/auth/index.js +1 -5
- package/lib/module/auth/jwt.js +1 -106
- package/lib/module/config/index.js +1 -64
- package/lib/module/deepLink/index.js +1 -280
- package/lib/module/download/adapters/expoFs.js +1 -64
- package/lib/module/download/adapters/shared.js +1 -8
- package/lib/module/download/index.js +1 -242
- package/lib/module/formatters/currency.js +1 -65
- package/lib/module/formatters/date.js +1 -159
- package/lib/module/formatters/dateMath.js +1 -295
- package/lib/module/formatters/index.js +1 -9
- package/lib/module/formatters/initials.js +1 -27
- package/lib/module/formatters/intlCache.js +1 -30
- package/lib/module/formatters/number.js +1 -82
- package/lib/module/formatters/phone.js +1 -302
- package/lib/module/formatters/windowsToIanaTable.js +1 -160
- package/lib/module/geolocation/adapters/expoLocation.js +1 -121
- package/lib/module/geolocation/index.js +1 -347
- package/lib/module/index.js +1 -57
- package/lib/module/initSDK.js +1 -61
- package/lib/module/initUserAuth.js +1 -33
- package/lib/module/logger/crashlytics.js +1 -0
- package/lib/module/logger/index.js +1 -295
- package/lib/module/logger/redact.js +1 -44
- package/lib/module/network/index.js +1 -4
- package/lib/module/network/networkStatus.js +1 -161
- package/lib/module/permissions/calendar.js +1 -15
- package/lib/module/permissions/camera.js +1 -15
- package/lib/module/permissions/contacts.js +1 -11
- package/lib/module/permissions/index.js +1 -128
- package/lib/module/permissions/location.js +1 -15
- package/lib/module/permissions/mediaLibrary.js +1 -11
- package/lib/module/permissions/notifications.js +1 -11
- package/lib/module/permissions/photos.js +1 -15
- package/lib/module/phone/countries.js +1 -261
- package/lib/module/push/index.js +1 -4
- package/lib/module/push/push.js +1 -452
- package/lib/module/push/status.js +1 -0
- package/lib/module/push/task.js +1 -38
- package/lib/module/secureScreen/index.js +1 -0
- package/lib/module/share/index.js +1 -64
- package/lib/module/smsRetriever/index.js +1 -86
- package/lib/module/specs/NativeSmsRetriever.js +1 -13
- package/lib/module/sqlite/adapters/expo.js +1 -48
- package/lib/module/sqlite/index.js +1 -594
- package/lib/module/storage/index.js +1 -208
- package/lib/module/types/axios.js +1 -4
- package/lib/module/types/globals.d.js +1 -10
- package/lib/module/utils/base64.js +1 -19
- package/lib/module/utils/imageCompression.js +1 -383
- package/lib/module/utils/index.js +1 -4
- package/lib/module/validators/index.js +1 -205
- package/lib/module/versionCheck/index.js +1 -413
- package/lib/typescript/commonjs/api/responseBodyText.d.ts +6 -0
- package/lib/typescript/commonjs/index.d.ts +5 -0
- package/lib/typescript/commonjs/logger/crashlytics.d.ts +31 -0
- package/lib/typescript/commonjs/push/index.d.ts +3 -1
- package/lib/typescript/commonjs/push/push.d.ts +1 -1
- package/lib/typescript/commonjs/push/status.d.ts +9 -0
- package/lib/typescript/commonjs/secureScreen/index.d.ts +25 -0
- package/lib/typescript/module/api/responseBodyText.d.ts +6 -0
- package/lib/typescript/module/index.d.ts +5 -0
- package/lib/typescript/module/logger/crashlytics.d.ts +31 -0
- package/lib/typescript/module/push/index.d.ts +3 -1
- package/lib/typescript/module/push/push.d.ts +1 -1
- package/lib/typescript/module/push/status.d.ts +9 -0
- package/lib/typescript/module/secureScreen/index.d.ts +25 -0
- package/package.json +23 -4
package/lib/module/push/push.js
CHANGED
|
@@ -1,452 +1 @@
|
|
|
1
|
-
"
|
|
2
|
-
|
|
3
|
-
import { useSyncExternalStore } from 'react';
|
|
4
|
-
import { Platform } from 'react-native';
|
|
5
|
-
import { Logger } from "../logger/index.js";
|
|
6
|
-
|
|
7
|
-
/** A button on a notification category. */
|
|
8
|
-
|
|
9
|
-
/** A set of buttons a push names through its category id. */
|
|
10
|
-
|
|
11
|
-
/** A button tap on a notification. */
|
|
12
|
-
|
|
13
|
-
/** The slice of expo-notifications this module calls, so the optional peer stays untyped at build. */
|
|
14
|
-
|
|
15
|
-
let override = null;
|
|
16
|
-
|
|
17
|
-
/** Expo modules resolved once; null when push cannot work in this build. */
|
|
18
|
-
let resolved;
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Replace the native modules. For tests and the showcase, to reach paths a
|
|
22
|
-
* real device never takes; an injected backend is taken on trust, Expo Go
|
|
23
|
-
* included. Pass `null` to restore expo-notifications.
|
|
24
|
-
*/
|
|
25
|
-
export const setPushImplementation = impl => {
|
|
26
|
-
override = impl;
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* The only place expo-notifications and expo-application are named. Expo Go is
|
|
31
|
-
* checked first because expo-notifications refuses remote push there and its
|
|
32
|
-
* import throws on Android. Exported for `./task`; not on the `/push` entry.
|
|
33
|
-
*/
|
|
34
|
-
export const resolveModules = () => {
|
|
35
|
-
if (override) {
|
|
36
|
-
return override;
|
|
37
|
-
}
|
|
38
|
-
if (resolved !== undefined) {
|
|
39
|
-
return resolved;
|
|
40
|
-
}
|
|
41
|
-
try {
|
|
42
|
-
const expo = require('expo');
|
|
43
|
-
if (expo.isRunningInExpoGo()) {
|
|
44
|
-
resolved = null;
|
|
45
|
-
return resolved;
|
|
46
|
-
}
|
|
47
|
-
const notifications = require('expo-notifications');
|
|
48
|
-
const application = require('expo-application');
|
|
49
|
-
resolved = {
|
|
50
|
-
notifications,
|
|
51
|
-
applicationId: application.applicationId
|
|
52
|
-
};
|
|
53
|
-
} catch (error) {
|
|
54
|
-
Logger.warn('[push] expo-notifications is not available; push is off', error);
|
|
55
|
-
resolved = null;
|
|
56
|
-
}
|
|
57
|
-
return resolved;
|
|
58
|
-
};
|
|
59
|
-
let state = {
|
|
60
|
-
status: 'unknown',
|
|
61
|
-
enabled: false
|
|
62
|
-
};
|
|
63
|
-
const subscribers = new Set();
|
|
64
|
-
const setState = next => {
|
|
65
|
-
state = {
|
|
66
|
-
...state,
|
|
67
|
-
...next
|
|
68
|
-
};
|
|
69
|
-
for (const notify of subscribers) {
|
|
70
|
-
notify();
|
|
71
|
-
}
|
|
72
|
-
};
|
|
73
|
-
let config = null;
|
|
74
|
-
let channelsReady = Promise.resolve();
|
|
75
|
-
let tokenSubscription = null;
|
|
76
|
-
let actionHandler = null;
|
|
77
|
-
let listening = false;
|
|
78
|
-
/** Responses that arrived before anything could handle them, e.g. the launching tap before initPush. */
|
|
79
|
-
const pendingResponses = [];
|
|
80
|
-
/** Response key to when it was routed. */
|
|
81
|
-
const routedAt = new Map();
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* The cold-start read, the live listener and, on Android, the background task
|
|
85
|
-
* can each report the same response a moment apart.
|
|
86
|
-
*/
|
|
87
|
-
const REPEAT_WINDOW_MS = 10_000;
|
|
88
|
-
const requireConfig = () => {
|
|
89
|
-
if (!config) {
|
|
90
|
-
throw new Error('[@webority/mobile-core] call initPush before enablePush or disablePush.');
|
|
91
|
-
}
|
|
92
|
-
return config;
|
|
93
|
-
};
|
|
94
|
-
const toPermissionStatus = response => {
|
|
95
|
-
if (response.granted) {
|
|
96
|
-
return 'granted';
|
|
97
|
-
}
|
|
98
|
-
return response.status === 'denied' ? 'denied' : 'unknown';
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
/** Push data travels as strings; iOS can hand back numbers or objects for top-level keys. */
|
|
102
|
-
const toStringData = data => {
|
|
103
|
-
const out = {};
|
|
104
|
-
for (const [key, value] of Object.entries(data ?? {})) {
|
|
105
|
-
if (value === null || value === undefined) {
|
|
106
|
-
continue;
|
|
107
|
-
}
|
|
108
|
-
out[key] = typeof value === 'object' ? JSON.stringify(value) : String(value);
|
|
109
|
-
}
|
|
110
|
-
return out;
|
|
111
|
-
};
|
|
112
|
-
const contentData = response => {
|
|
113
|
-
const content = response.notification.request.content;
|
|
114
|
-
if (content.data) {
|
|
115
|
-
return toStringData(content.data);
|
|
116
|
-
}
|
|
117
|
-
if (typeof content.dataString === 'string') {
|
|
118
|
-
try {
|
|
119
|
-
return toStringData(JSON.parse(content.dataString));
|
|
120
|
-
} catch (error) {
|
|
121
|
-
Logger.error('[push] notification data is not JSON', error);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
return {};
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* expo-notifications fills iOS `content.data` only from a `body` object, but our
|
|
129
|
-
* servers send raw APNs with the app's keys at the top level beside `aps`.
|
|
130
|
-
*/
|
|
131
|
-
const triggerData = response => {
|
|
132
|
-
const trigger = response.notification.request.trigger;
|
|
133
|
-
const extra = {
|
|
134
|
-
...(trigger?.remoteMessage?.data ?? {})
|
|
135
|
-
};
|
|
136
|
-
for (const [key, value] of Object.entries(trigger?.payload ?? {})) {
|
|
137
|
-
// A `body` object is what expo already turned into content.data.
|
|
138
|
-
if (key === 'aps' || key === 'body' && typeof value === 'object') {
|
|
139
|
-
continue;
|
|
140
|
-
}
|
|
141
|
-
extra[key] = value;
|
|
142
|
-
}
|
|
143
|
-
return toStringData(extra);
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
/** The push's own data merged over the raw remote payload's keys, so content.data wins a conflict. */
|
|
147
|
-
const responseData = response => ({
|
|
148
|
-
...triggerData(response),
|
|
149
|
-
...contentData(response)
|
|
150
|
-
});
|
|
151
|
-
export const toPushAction = response => ({
|
|
152
|
-
actionId: response.actionIdentifier,
|
|
153
|
-
...(response.userText ? {
|
|
154
|
-
userText: response.userText
|
|
155
|
-
} : {}),
|
|
156
|
-
data: responseData(response),
|
|
157
|
-
notificationId: response.notification.request.identifier
|
|
158
|
-
});
|
|
159
|
-
export const toNotificationActions = actions => actions.map(action => ({
|
|
160
|
-
identifier: action.id,
|
|
161
|
-
buttonTitle: action.title,
|
|
162
|
-
...(action.textInput ? {
|
|
163
|
-
textInput: {
|
|
164
|
-
submitButtonTitle: action.textInput.submitTitle,
|
|
165
|
-
placeholder: action.textInput.placeholder
|
|
166
|
-
}
|
|
167
|
-
} : {}),
|
|
168
|
-
options: {
|
|
169
|
-
opensAppToForeground: action.opensApp ?? true,
|
|
170
|
-
isDestructive: action.destructive ?? false,
|
|
171
|
-
isAuthenticationRequired: action.authenticationRequired ?? false
|
|
172
|
-
}
|
|
173
|
-
}));
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Sends a button tap to the action handler and a plain tap to `onOpen`. A
|
|
177
|
-
* button tap with no action handler opens like a plain tap. Resolves once the
|
|
178
|
-
* handler has finished, so a background task stays alive for its request.
|
|
179
|
-
*/
|
|
180
|
-
const routeResponse = async (m, response) => {
|
|
181
|
-
const key = `${response.notification.request.identifier}:${response.actionIdentifier}`;
|
|
182
|
-
const last = routedAt.get(key);
|
|
183
|
-
if (last !== undefined && Date.now() - last < REPEAT_WINDOW_MS) {
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
186
|
-
const isAction = response.actionIdentifier !== m.DEFAULT_ACTION_IDENTIFIER;
|
|
187
|
-
if (isAction && actionHandler) {
|
|
188
|
-
routedAt.set(key, Date.now());
|
|
189
|
-
try {
|
|
190
|
-
await actionHandler(toPushAction(response));
|
|
191
|
-
} catch (error) {
|
|
192
|
-
Logger.error('[push] onAction threw', error);
|
|
193
|
-
}
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
|
-
if (!config) {
|
|
197
|
-
pendingResponses.push(response);
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
routedAt.set(key, Date.now());
|
|
201
|
-
try {
|
|
202
|
-
config.onOpen(responseData(response));
|
|
203
|
-
} catch (error) {
|
|
204
|
-
Logger.error('[push] onOpen threw', error);
|
|
205
|
-
}
|
|
206
|
-
};
|
|
207
|
-
const drainPending = async m => {
|
|
208
|
-
const queued = pendingResponses.splice(0);
|
|
209
|
-
for (const response of queued) {
|
|
210
|
-
await routeResponse(m, response);
|
|
211
|
-
}
|
|
212
|
-
};
|
|
213
|
-
|
|
214
|
-
/** Follows taps and button taps, including the one that launched the app. Wired once per process. */
|
|
215
|
-
const listenForResponses = m => {
|
|
216
|
-
if (listening) {
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
219
|
-
listening = true;
|
|
220
|
-
m.addNotificationResponseReceivedListener(response => {
|
|
221
|
-
void routeResponse(m, response);
|
|
222
|
-
});
|
|
223
|
-
void m.getLastNotificationResponseAsync().then(async response => {
|
|
224
|
-
if (response) {
|
|
225
|
-
await routeResponse(m, response);
|
|
226
|
-
}
|
|
227
|
-
return m.clearLastNotificationResponseAsync?.();
|
|
228
|
-
}).catch(error => Logger.error('[push] reading the launch notification failed', error));
|
|
229
|
-
};
|
|
230
|
-
|
|
231
|
-
/** The payload of the background notification task: routes button taps, ignores received pushes. */
|
|
232
|
-
export const routeTaskPayload = async payload => {
|
|
233
|
-
const mods = resolveModules();
|
|
234
|
-
const response = payload;
|
|
235
|
-
if (!mods || typeof response?.actionIdentifier !== 'string' || typeof response.notification?.request?.identifier !== 'string') {
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
238
|
-
await routeResponse(mods.notifications, response);
|
|
239
|
-
};
|
|
240
|
-
const createChannels = async (m, channels) => {
|
|
241
|
-
for (const channel of channels) {
|
|
242
|
-
try {
|
|
243
|
-
await m.setNotificationChannelAsync(channel.id, {
|
|
244
|
-
name: channel.name,
|
|
245
|
-
importance: channel.importance === 'high' ? m.AndroidImportance.HIGH : m.AndroidImportance.DEFAULT
|
|
246
|
-
});
|
|
247
|
-
} catch (error) {
|
|
248
|
-
Logger.error(`[push] creating channel ${channel.id} failed`, error);
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
};
|
|
252
|
-
const toDevice = async (token, applicationId) => {
|
|
253
|
-
const platform = token.type === 'android' ? 'Fcm' : token.type === 'ios' ? 'Apns' : null;
|
|
254
|
-
if (!platform || typeof token.data !== 'string') {
|
|
255
|
-
return null;
|
|
256
|
-
}
|
|
257
|
-
if (!applicationId) {
|
|
258
|
-
throw new Error('[@webority/mobile-core] push needs the app bundle id from expo-application.');
|
|
259
|
-
}
|
|
260
|
-
const installationId = await requireConfig().getInstallationId();
|
|
261
|
-
return {
|
|
262
|
-
installationId,
|
|
263
|
-
platform,
|
|
264
|
-
token: token.data,
|
|
265
|
-
appId: applicationId
|
|
266
|
-
};
|
|
267
|
-
};
|
|
268
|
-
|
|
269
|
-
/**
|
|
270
|
-
* Wires push at startup: the foreground handler, Android channels, and tap
|
|
271
|
-
* handling, including the tap that launched the app. Calling it again only
|
|
272
|
-
* replaces the config.
|
|
273
|
-
*/
|
|
274
|
-
export const initPush = next => {
|
|
275
|
-
const first = config === null;
|
|
276
|
-
config = next;
|
|
277
|
-
if (!first) {
|
|
278
|
-
return;
|
|
279
|
-
}
|
|
280
|
-
const mods = resolveModules();
|
|
281
|
-
if (!mods) {
|
|
282
|
-
setState({
|
|
283
|
-
status: 'unavailable'
|
|
284
|
-
});
|
|
285
|
-
return;
|
|
286
|
-
}
|
|
287
|
-
const m = mods.notifications;
|
|
288
|
-
m.setNotificationHandler({
|
|
289
|
-
handleNotification: async () => {
|
|
290
|
-
const show = config?.showInForeground?.() ?? true;
|
|
291
|
-
return {
|
|
292
|
-
shouldShowBanner: show,
|
|
293
|
-
shouldShowList: show,
|
|
294
|
-
shouldPlaySound: show && (config?.playSoundInForeground?.() ?? true),
|
|
295
|
-
shouldSetBadge: true
|
|
296
|
-
};
|
|
297
|
-
}
|
|
298
|
-
});
|
|
299
|
-
if (Platform.OS === 'android' && next.androidChannels?.length) {
|
|
300
|
-
channelsReady = createChannels(m, next.androidChannels);
|
|
301
|
-
}
|
|
302
|
-
listenForResponses(m);
|
|
303
|
-
void drainPending(m);
|
|
304
|
-
void m.getPermissionsAsync().then(response => setState({
|
|
305
|
-
status: toPermissionStatus(response)
|
|
306
|
-
})).catch(error => Logger.error('[push] permission check failed', error));
|
|
307
|
-
};
|
|
308
|
-
|
|
309
|
-
/**
|
|
310
|
-
* Asks for permission, reads the native device token and registers it through
|
|
311
|
-
* `config.register`, then re-registers whenever the OS rotates the token.
|
|
312
|
-
*/
|
|
313
|
-
export const enablePush = async () => {
|
|
314
|
-
requireConfig();
|
|
315
|
-
const mods = resolveModules();
|
|
316
|
-
if (!mods) {
|
|
317
|
-
setState({
|
|
318
|
-
status: 'unavailable'
|
|
319
|
-
});
|
|
320
|
-
return 'unavailable';
|
|
321
|
-
}
|
|
322
|
-
const m = mods.notifications;
|
|
323
|
-
|
|
324
|
-
// Android 13 shows the permission prompt only once a channel exists.
|
|
325
|
-
await channelsReady;
|
|
326
|
-
let permission = await m.getPermissionsAsync();
|
|
327
|
-
if (!permission.granted) {
|
|
328
|
-
permission = await m.requestPermissionsAsync();
|
|
329
|
-
}
|
|
330
|
-
if (!permission.granted) {
|
|
331
|
-
setState({
|
|
332
|
-
status: 'denied'
|
|
333
|
-
});
|
|
334
|
-
return 'denied';
|
|
335
|
-
}
|
|
336
|
-
const device = await toDevice(await m.getDevicePushTokenAsync(), mods.applicationId);
|
|
337
|
-
if (!device) {
|
|
338
|
-
setState({
|
|
339
|
-
status: 'unavailable'
|
|
340
|
-
});
|
|
341
|
-
return 'unavailable';
|
|
342
|
-
}
|
|
343
|
-
await requireConfig().register(device);
|
|
344
|
-
tokenSubscription?.remove();
|
|
345
|
-
tokenSubscription = m.addPushTokenListener(token => {
|
|
346
|
-
void toDevice(token, mods.applicationId).then(next => next ? requireConfig().register(next) : undefined).catch(error => Logger.error('[push] re-registering a new token failed', error));
|
|
347
|
-
});
|
|
348
|
-
setState({
|
|
349
|
-
status: 'granted',
|
|
350
|
-
enabled: true
|
|
351
|
-
});
|
|
352
|
-
return 'granted';
|
|
353
|
-
};
|
|
354
|
-
|
|
355
|
-
/**
|
|
356
|
-
* Unregisters the device through `config.unregister` and stops following token
|
|
357
|
-
* changes. Taps on notifications already delivered still reach `onOpen`.
|
|
358
|
-
*/
|
|
359
|
-
export const disablePush = async () => {
|
|
360
|
-
const current = requireConfig();
|
|
361
|
-
// Stop following token changes first, so a failed unregister cannot re-register the phone.
|
|
362
|
-
tokenSubscription?.remove();
|
|
363
|
-
tokenSubscription = null;
|
|
364
|
-
setState({
|
|
365
|
-
enabled: false
|
|
366
|
-
});
|
|
367
|
-
await current.unregister(await current.getInstallationId());
|
|
368
|
-
};
|
|
369
|
-
|
|
370
|
-
/** Sets the app icon badge; a no-op where push is unavailable or the launcher has no badge. */
|
|
371
|
-
export const setBadgeCount = async count => {
|
|
372
|
-
const mods = resolveModules();
|
|
373
|
-
if (!mods) {
|
|
374
|
-
return;
|
|
375
|
-
}
|
|
376
|
-
await mods.notifications.setBadgeCountAsync(count);
|
|
377
|
-
};
|
|
378
|
-
|
|
379
|
-
/**
|
|
380
|
-
* Registers the button sets a push can name by category id. Call it at every
|
|
381
|
-
* start, with the same ids on both platforms. On Android the buttons show only
|
|
382
|
-
* on notifications expo-notifications draws, such as `notifyLocal`.
|
|
383
|
-
*/
|
|
384
|
-
export const setCategories = async categories => {
|
|
385
|
-
const mods = resolveModules();
|
|
386
|
-
if (!mods) {
|
|
387
|
-
return;
|
|
388
|
-
}
|
|
389
|
-
for (const category of categories) {
|
|
390
|
-
await mods.notifications.setNotificationCategoryAsync(category.id, toNotificationActions(category.actions));
|
|
391
|
-
}
|
|
392
|
-
};
|
|
393
|
-
|
|
394
|
-
/**
|
|
395
|
-
* Handles button taps while JavaScript is running; a plain tap still goes to
|
|
396
|
-
* `onOpen`. For taps on a killed app use `definePushActionTask` from
|
|
397
|
-
* `/push/task`, which sets this handler too. Returns a function that removes it.
|
|
398
|
-
*/
|
|
399
|
-
export const onAction = handler => {
|
|
400
|
-
actionHandler = handler;
|
|
401
|
-
const mods = resolveModules();
|
|
402
|
-
if (mods) {
|
|
403
|
-
listenForResponses(mods.notifications);
|
|
404
|
-
void drainPending(mods.notifications);
|
|
405
|
-
}
|
|
406
|
-
return () => {
|
|
407
|
-
if (actionHandler === handler) {
|
|
408
|
-
actionHandler = null;
|
|
409
|
-
}
|
|
410
|
-
};
|
|
411
|
-
};
|
|
412
|
-
|
|
413
|
-
/** Shows a notification from the phone itself, for example when a button's request failed. */
|
|
414
|
-
export const notifyLocal = async ({
|
|
415
|
-
title,
|
|
416
|
-
body,
|
|
417
|
-
data
|
|
418
|
-
}) => {
|
|
419
|
-
const mods = resolveModules();
|
|
420
|
-
if (!mods) {
|
|
421
|
-
return;
|
|
422
|
-
}
|
|
423
|
-
await mods.notifications.scheduleNotificationAsync({
|
|
424
|
-
content: {
|
|
425
|
-
title,
|
|
426
|
-
body,
|
|
427
|
-
data: data ?? {}
|
|
428
|
-
},
|
|
429
|
-
trigger: null
|
|
430
|
-
});
|
|
431
|
-
};
|
|
432
|
-
const subscribe = notify => {
|
|
433
|
-
subscribers.add(notify);
|
|
434
|
-
return () => {
|
|
435
|
-
subscribers.delete(notify);
|
|
436
|
-
};
|
|
437
|
-
};
|
|
438
|
-
const getSnapshot = () => state;
|
|
439
|
-
const enable = async () => {
|
|
440
|
-
await enablePush();
|
|
441
|
-
};
|
|
442
|
-
|
|
443
|
-
/** Push status for a settings switch, re-rendering when it changes. */
|
|
444
|
-
export const usePush = () => {
|
|
445
|
-
const snapshot = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
446
|
-
return {
|
|
447
|
-
...snapshot,
|
|
448
|
-
enable,
|
|
449
|
-
disable: disablePush
|
|
450
|
-
};
|
|
451
|
-
};
|
|
452
|
-
//# sourceMappingURL=push.js.map
|
|
1
|
+
import{useSyncExternalStore as t}from"react";import{Platform as e}from"react-native";import{Logger as n}from"../logger/index.js";let o,i=null;export const setPushImplementation=t=>{i=t};export const resolveModules=()=>{if(i)return i;if(void 0!==o)return o;try{if(require("expo").isRunningInExpoGo())return o=null,o;const t=require("expo-notifications"),e=require("expo-application");o={notifications:t,applicationId:e.applicationId}}catch(t){n.warn("[push] expo-notifications is not available; push is off",t),o=null}return o};let a={status:"unknown",enabled:!1};const s=new Set,setState=t=>{a={...a,...t};for(const t of s)t()};let r=null,c=Promise.resolve(),l=null,u=null,d=!1;const p=[],f=new Map,requireConfig=()=>{if(!r)throw new Error("[@webority/mobile-core] call initPush before enablePush or disablePush.");return r},toPermissionStatus=t=>t.granted?"granted":"denied"===t.status?"denied":"unknown",toStringData=t=>{const e={};for(const[n,o]of Object.entries(t??{}))null!=o&&(e[n]="object"==typeof o?JSON.stringify(o):String(o));return e},contentData=t=>{const e=t.notification.request.content;if(e.data)return toStringData(e.data);if("string"==typeof e.dataString)try{return toStringData(JSON.parse(e.dataString))}catch(t){n.error("[push] notification data is not JSON",t)}return{}},triggerData=t=>{const e=t.notification.request.trigger,n={...e?.remoteMessage?.data??{}};for(const[t,o]of Object.entries(e?.payload??{}))"aps"===t||"body"===t&&"object"==typeof o||(n[t]=o);return toStringData(n)},responseData=t=>({...triggerData(t),...contentData(t)});export const toPushAction=t=>({actionId:t.actionIdentifier,...t.userText?{userText:t.userText}:{},data:responseData(t),notificationId:t.notification.request.identifier});export const toNotificationActions=t=>t.map(t=>({identifier:t.id,buttonTitle:t.title,...t.textInput?{textInput:{submitButtonTitle:t.textInput.submitTitle,placeholder:t.textInput.placeholder}}:{},options:{opensAppToForeground:t.opensApp??!0,isDestructive:t.destructive??!1,isAuthenticationRequired:t.authenticationRequired??!1}}));const routeResponse=async(t,e)=>{const o=`${e.notification.request.identifier}:${e.actionIdentifier}`,i=f.get(o);if(void 0!==i&&Date.now()-i<1e4)return;if(e.actionIdentifier!==t.DEFAULT_ACTION_IDENTIFIER&&u){f.set(o,Date.now());try{await u(toPushAction(e))}catch(t){n.error("[push] onAction threw",t)}}else if(r){f.set(o,Date.now());try{r.onOpen(responseData(e))}catch(t){n.error("[push] onOpen threw",t)}}else p.push(e)},drainPending=async t=>{const e=p.splice(0);for(const n of e)await routeResponse(t,n)},listenForResponses=t=>{d||(d=!0,t.addNotificationResponseReceivedListener(e=>{routeResponse(t,e)}),t.getLastNotificationResponseAsync().then(async e=>(e&&await routeResponse(t,e),t.clearLastNotificationResponseAsync?.())).catch(t=>n.error("[push] reading the launch notification failed",t)))};export const routeTaskPayload=async t=>{const e=resolveModules(),n=t;e&&"string"==typeof n?.actionIdentifier&&"string"==typeof n.notification?.request?.identifier&&await routeResponse(e.notifications,n)};const toDevice=async(t,e)=>{const n="android"===t.type?"Fcm":"ios"===t.type?"Apns":null;if(!n||"string"!=typeof t.data)return null;if(!e)throw new Error("[@webority/mobile-core] push needs the app bundle id from expo-application.");return{installationId:await requireConfig().getInstallationId(),platform:n,token:t.data,appId:e}};export const initPush=t=>{const o=null===r;if(r=t,!o)return;const i=resolveModules();if(!i)return void setState({status:"unavailable"});const a=i.notifications;a.setNotificationHandler({handleNotification:async()=>{const t=r?.showInForeground?.()??!0;return{shouldShowBanner:t,shouldShowList:t,shouldPlaySound:t&&(r?.playSoundInForeground?.()??!0),shouldSetBadge:!0}}}),"android"===e.OS&&t.androidChannels?.length&&(c=(async(t,e)=>{for(const o of e)try{await t.setNotificationChannelAsync(o.id,{name:o.name,importance:"high"===o.importance?t.AndroidImportance.HIGH:t.AndroidImportance.DEFAULT})}catch(t){n.error(`[push] creating channel ${o.id} failed`,t)}})(a,t.androidChannels)),listenForResponses(a),drainPending(a),a.getPermissionsAsync().then(t=>setState({status:toPermissionStatus(t)})).catch(t=>n.error("[push] permission check failed",t))};let h=null,y=null;const registerOnce=t=>{const e=JSON.stringify(t);if(h?.key===e)return h.promise;const n=requireConfig().register(t).then(()=>{y=e}).finally(()=>{h?.promise===n&&(h=null)});return h={key:e,promise:n},n};export const enablePush=async()=>{requireConfig();const t=resolveModules();if(!t)return setState({status:"unavailable"}),"unavailable";const e=t.notifications;await c;let o=await e.getPermissionsAsync();if(o.granted||(o=await e.requestPermissionsAsync()),!o.granted)return setState({status:"denied"}),"denied";const i=await toDevice(await e.getDevicePushTokenAsync(),t.applicationId);return i?(await registerOnce(i),l?.remove(),l=e.addPushTokenListener(e=>{toDevice(e,t.applicationId).then(t=>t&&JSON.stringify(t)!==y?registerOnce(t):void 0).catch(t=>n.error("[push] re-registering a new token failed",t))}),setState({status:"granted",enabled:!0}),"granted"):(setState({status:"unavailable"}),"unavailable")};export const disablePush=async()=>{const t=requireConfig();l?.remove(),l=null,y=null,setState({enabled:!1}),await t.unregister(await t.getInstallationId())};export const setBadgeCount=async t=>{const e=resolveModules();e&&await e.notifications.setBadgeCountAsync(t)};export const setCategories=async t=>{const e=resolveModules();if(e)for(const n of t)await e.notifications.setNotificationCategoryAsync(n.id,toNotificationActions(n.actions))};export const onAction=t=>{u=t;const e=resolveModules();return e&&(listenForResponses(e.notifications),drainPending(e.notifications)),()=>{u===t&&(u=null)}};export const notifyLocal=async({title:t,body:e,data:n})=>{const o=resolveModules();o&&await o.notifications.scheduleNotificationAsync({content:{title:t,body:e,data:n??{}},trigger:null})};const subscribe=t=>(s.add(t),()=>{s.delete(t)}),getSnapshot=()=>a,enable=async()=>{await enablePush()};export const usePush=()=>({...t(subscribe,getSnapshot,getSnapshot),enable:enable,disable:disablePush});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const PUSH_STATUS={unknown:"unknown",granted:"granted",denied:"denied",unavailable:"unavailable"};
|
package/lib/module/push/task.js
CHANGED
|
@@ -1,38 +1 @@
|
|
|
1
|
-
"
|
|
2
|
-
|
|
3
|
-
import { Platform } from 'react-native';
|
|
4
|
-
import { Logger } from "../logger/index.js";
|
|
5
|
-
import { onAction, resolveModules, routeTaskPayload } from "./push.js";
|
|
6
|
-
const PUSH_ACTION_TASK = 'webority-push-action';
|
|
7
|
-
/**
|
|
8
|
-
* Handles notification button taps in every app state, the killed app
|
|
9
|
-
* included. Call it once at module scope in the app's entry file (index.js),
|
|
10
|
-
* before the root component registers: a tap on a killed app starts the
|
|
11
|
-
* JavaScript bundle without the UI, so nothing later in the app runs.
|
|
12
|
-
*
|
|
13
|
-
* Android runs the handler from a background notification task
|
|
14
|
-
* (expo-task-manager). iOS has no such task for button taps: it launches the
|
|
15
|
-
* app in the background and the response reaches the listener this sets up.
|
|
16
|
-
* The handler's promise is awaited, so a request it makes can finish first.
|
|
17
|
-
*/
|
|
18
|
-
export const definePushActionTask = handler => {
|
|
19
|
-
onAction(handler);
|
|
20
|
-
const mods = resolveModules();
|
|
21
|
-
if (!mods || Platform.OS !== 'android') {
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
/** The one place `expo-task-manager` is named. Import `/push/task` only if you use button taps. */
|
|
25
|
-
const taskManager = require('expo-task-manager');
|
|
26
|
-
taskManager.defineTask(PUSH_ACTION_TASK, async ({
|
|
27
|
-
data,
|
|
28
|
-
error
|
|
29
|
-
}) => {
|
|
30
|
-
if (error) {
|
|
31
|
-
Logger.error('[push] the notification task failed', error);
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
await routeTaskPayload(data);
|
|
35
|
-
});
|
|
36
|
-
void mods.notifications.registerTaskAsync(PUSH_ACTION_TASK).catch(registerError => Logger.error('[push] registering the notification task failed', registerError));
|
|
37
|
-
};
|
|
38
|
-
//# sourceMappingURL=task.js.map
|
|
1
|
+
import{Platform as r}from"react-native";import{Logger as t}from"../logger/index.js";import{onAction as i,resolveModules as e,routeTaskPayload as o}from"./push.js";const a="webority-push-action";export const definePushActionTask=n=>{i(n);const s=e();if(!s||"android"!==r.OS)return;require("expo-task-manager").defineTask(a,async({data:r,error:i})=>{i?t.error("[push] the notification task failed",i):await o(r)}),s.notifications.registerTaskAsync(a).catch(r=>t.error("[push] registering the notification task failed",r))};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{useEffect as e,useRef as r}from"react";import{Platform as c}from"react-native";import{Logger as t}from"../logger/index.js";const report=e=>r=>{t.error(`[@webority/mobile-core] useSecureScreen could not ${e}.`,r)};let n=0,o=0;export const useSecureScreen=(t={})=>{const{focused:s=!0}=t,p=r(null);null===p.current&&(n+=1,p.current=`webority-secure-screen-${n}`),e(()=>{if(!s)return;const e=p.current,r=require("expo-screen-capture");r.preventScreenCaptureAsync(e).catch(report("block screen capture"));const t="ios"===c.OS;return t&&(o+=1,1===o&&r.enableAppSwitcherProtectionAsync().catch(report("hide the app switcher snapshot"))),()=>{r.allowScreenCaptureAsync(e).catch(report("release screen capture")),t&&(o-=1,0===o&&r.disableAppSwitcherProtectionAsync().catch(report("restore the app switcher snapshot")))}},[s])};
|
|
@@ -1,64 +1 @@
|
|
|
1
|
-
"
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Opens a downloaded file through the system share sheet, where the person picks
|
|
5
|
-
* a viewer or saves it. A separate entry point because it names expo-sharing, an
|
|
6
|
-
* optional peer: Metro resolves every require reachable from an import, so keeping
|
|
7
|
-
* it off the main barrel and off `/download` lets an app skip the package when it
|
|
8
|
-
* never opens files.
|
|
9
|
-
*
|
|
10
|
-
* The share sheet rather than `Linking.openURL`: Android 7 and later refuse a
|
|
11
|
-
* `file://` link, and a `content://` link sent through Linking carries no read
|
|
12
|
-
* grant, so the OS-open route never reached a viewer there.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
const PREFIX = '[@webority/mobile-core]';
|
|
16
|
-
/** The one place `expo-sharing` is named, and only this subpath reaches it. */
|
|
17
|
-
const sharing = () => require('expo-sharing');
|
|
18
|
-
|
|
19
|
-
// iOS picks the apps it offers from the UTI, not the MIME type.
|
|
20
|
-
const UTIS = {
|
|
21
|
-
'application/pdf': 'com.adobe.pdf',
|
|
22
|
-
'text/plain': 'public.plain-text',
|
|
23
|
-
'text/csv': 'public.comma-separated-values-text',
|
|
24
|
-
'image/jpeg': 'public.jpeg',
|
|
25
|
-
'image/png': 'public.png'
|
|
26
|
-
};
|
|
27
|
-
export class ShareError extends Error {
|
|
28
|
-
constructor(code, message) {
|
|
29
|
-
super(`${PREFIX} ${message}`);
|
|
30
|
-
this.name = 'ShareError';
|
|
31
|
-
this.code = code;
|
|
32
|
-
// Babel's class transform can drop the prototype link, which makes
|
|
33
|
-
// `instanceof ShareError` false in the transpiled build.
|
|
34
|
-
Object.setPrototypeOf(this, ShareError.prototype);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
const HAS_SCHEME = /^[a-z][a-z0-9+.-]*:/i;
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Hands a local file (the `path` `downloadFile` returns) to the share sheet.
|
|
41
|
-
* A bare path is given a `file://` scheme. Rejects with `ShareError`: `unavailable`
|
|
42
|
-
* when the device has no share sheet, `failed` when the sheet could not open the file.
|
|
43
|
-
*/
|
|
44
|
-
export const shareFile = async (path, options = {}) => {
|
|
45
|
-
const url = HAS_SCHEME.test(path) ? path : `file://${path}`;
|
|
46
|
-
const m = sharing();
|
|
47
|
-
if (!(await m.isAvailableAsync())) {
|
|
48
|
-
throw new ShareError('unavailable', `Sharing is not available on this device, so ${url} cannot be opened.`);
|
|
49
|
-
}
|
|
50
|
-
const {
|
|
51
|
-
mimeType,
|
|
52
|
-
dialogTitle
|
|
53
|
-
} = options;
|
|
54
|
-
try {
|
|
55
|
-
await m.shareAsync(url, {
|
|
56
|
-
mimeType,
|
|
57
|
-
UTI: mimeType ? UTIS[mimeType] : undefined,
|
|
58
|
-
dialogTitle
|
|
59
|
-
});
|
|
60
|
-
} catch (error) {
|
|
61
|
-
throw new ShareError('failed', `${url}${mimeType ? ` (${mimeType})` : ''} could not be shared: ${error instanceof Error ? error.message : String(error)}`);
|
|
62
|
-
}
|
|
63
|
-
};
|
|
64
|
-
//# sourceMappingURL=index.js.map
|
|
1
|
+
const e={"application/pdf":"com.adobe.pdf","text/plain":"public.plain-text","text/csv":"public.comma-separated-values-text","image/jpeg":"public.jpeg","image/png":"public.png"};export class ShareError extends Error{constructor(e,r){super(`[@webority/mobile-core] ${r}`),this.name="ShareError",this.code=e,Object.setPrototypeOf(this,ShareError.prototype)}}const r=/^[a-z][a-z0-9+.-]*:/i;export const shareFile=async(t,a={})=>{const o=r.test(t)?t:`file://${t}`,i=require("expo-sharing");if(!await i.isAvailableAsync())throw new ShareError("unavailable",`Sharing is not available on this device, so ${o} cannot be opened.`);const{mimeType:s,dialogTitle:n}=a;try{await i.shareAsync(o,{mimeType:s,UTI:s?e[s]:void 0,dialogTitle:n})}catch(e){throw new ShareError("failed",`${o}${s?` (${s})`:""} could not be shared: ${e instanceof Error?e.message:String(e)}`)}};
|
|
@@ -1,86 +1 @@
|
|
|
1
|
-
"
|
|
2
|
-
|
|
3
|
-
import { DeviceEventEmitter, Platform } from 'react-native';
|
|
4
|
-
import { Logger } from "../logger/index.js";
|
|
5
|
-
const OTP_EVENT = 'WeborityMobileCoreSmsRetrieved';
|
|
6
|
-
const ERROR_EVENT = 'WeborityMobileCoreSmsRetrieverError';
|
|
7
|
-
let backend = null;
|
|
8
|
-
|
|
9
|
-
/** Test-only injection point. Pass `null` to reset to the native module. */
|
|
10
|
-
export const setSmsRetrieverImplementation = impl => {
|
|
11
|
-
backend = impl;
|
|
12
|
-
};
|
|
13
|
-
let nativeModule;
|
|
14
|
-
const resolveNativeModule = () => {
|
|
15
|
-
if (nativeModule !== undefined) {
|
|
16
|
-
return nativeModule;
|
|
17
|
-
}
|
|
18
|
-
if (Platform.OS !== 'android') {
|
|
19
|
-
nativeModule = null;
|
|
20
|
-
return null;
|
|
21
|
-
}
|
|
22
|
-
try {
|
|
23
|
-
nativeModule = require('../specs/NativeSmsRetriever').default ?? null;
|
|
24
|
-
} catch (error) {
|
|
25
|
-
Logger.warn('[smsRetriever] native module not available', error);
|
|
26
|
-
nativeModule = null;
|
|
27
|
-
}
|
|
28
|
-
return nativeModule ?? null;
|
|
29
|
-
};
|
|
30
|
-
const defaultBackend = {
|
|
31
|
-
getAppHash: async () => {
|
|
32
|
-
const m = resolveNativeModule();
|
|
33
|
-
if (!m) {
|
|
34
|
-
throw new Error('[smsRetriever] getAppHash: native module unavailable (Android only).');
|
|
35
|
-
}
|
|
36
|
-
return m.getAppHash();
|
|
37
|
-
},
|
|
38
|
-
startListening: (onMessage, onError) => {
|
|
39
|
-
const m = resolveNativeModule();
|
|
40
|
-
if (!m) {
|
|
41
|
-
onError?.({
|
|
42
|
-
type: 'SERVICE_UNAVAILABLE',
|
|
43
|
-
message: 'SMS Retriever is Android-only, or the native module is not linked.'
|
|
44
|
-
});
|
|
45
|
-
return {
|
|
46
|
-
remove: () => undefined
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
const messageSub = DeviceEventEmitter.addListener(OTP_EVENT, payload => onMessage(payload.message));
|
|
50
|
-
const errorSub = onError ? DeviceEventEmitter.addListener(ERROR_EVENT, payload => onError(payload)) : null;
|
|
51
|
-
try {
|
|
52
|
-
m.startSMSListener();
|
|
53
|
-
} catch (error) {
|
|
54
|
-
Logger.error('[smsRetriever] startSMSListener failed', error);
|
|
55
|
-
}
|
|
56
|
-
return {
|
|
57
|
-
remove: () => {
|
|
58
|
-
messageSub.remove();
|
|
59
|
-
errorSub?.remove();
|
|
60
|
-
try {
|
|
61
|
-
m.stopSMSListener();
|
|
62
|
-
} catch (error) {
|
|
63
|
-
Logger.error('[smsRetriever] stopSMSListener failed', error);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* The Play Services SMS Retriever API — auto-reads an incoming OTP SMS with
|
|
72
|
-
* no SMS permission, matched by an 11-character app-signature hash
|
|
73
|
-
* (`getAppHash`) the backend appends to the message. Android only; every
|
|
74
|
-
* method is safe to call on iOS and resolves/reports unavailable rather than
|
|
75
|
-
* throwing, so a caller doesn't need a platform check at every call site.
|
|
76
|
-
*
|
|
77
|
-
* OTP extraction is left to the caller — `onMessage` receives the raw SMS
|
|
78
|
-
* text, since the expected OTP format (digit count, prefix) is a backend
|
|
79
|
-
* concern, not this module's.
|
|
80
|
-
*/
|
|
81
|
-
export const SmsRetriever = {
|
|
82
|
-
isAvailable: () => resolveNativeModule() !== null,
|
|
83
|
-
getAppHash: () => (backend ?? defaultBackend).getAppHash(),
|
|
84
|
-
startListening: (onMessage, onError) => (backend ?? defaultBackend).startListening(onMessage, onError)
|
|
85
|
-
};
|
|
86
|
-
//# sourceMappingURL=index.js.map
|
|
1
|
+
import{DeviceEventEmitter as e,Platform as r}from"react-native";import{Logger as t}from"../logger/index.js";let i,s=null;export const setSmsRetrieverImplementation=e=>{s=e};const resolveNativeModule=()=>{if(void 0!==i)return i;if("android"!==r.OS)return i=null,null;try{i=require("../specs/NativeSmsRetriever").default??null}catch(e){t.warn("[smsRetriever] native module not available",e),i=null}return i??null},n={getAppHash:async()=>{const e=resolveNativeModule();if(!e)throw new Error("[smsRetriever] getAppHash: native module unavailable (Android only).");return e.getAppHash()},startListening:(r,i)=>{const s=resolveNativeModule();if(!s)return i?.({type:"SERVICE_UNAVAILABLE",message:"SMS Retriever is Android-only, or the native module is not linked."}),{remove:()=>{}};const n=e.addListener("WeborityMobileCoreSmsRetrieved",e=>r(e.message)),o=i?e.addListener("WeborityMobileCoreSmsRetrieverError",e=>i(e)):null;try{s.startSMSListener()}catch(e){t.error("[smsRetriever] startSMSListener failed",e)}return{remove:()=>{n.remove(),o?.remove();try{s.stopSMSListener()}catch(e){t.error("[smsRetriever] stopSMSListener failed",e)}}}}};export const SmsRetriever={isAvailable:()=>null!==resolveNativeModule(),getAppHash:()=>(s??n).getAppHash(),startListening:(e,r)=>(s??n).startListening(e,r)};
|
|
@@ -1,13 +1 @@
|
|
|
1
|
-
"
|
|
2
|
-
|
|
3
|
-
import { TurboModuleRegistry } from 'react-native';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Android-only — the Play Services SMS Retriever API has no iOS equivalent.
|
|
7
|
-
* The JS wrapper in `smsRetriever/index.ts` never touches this module on
|
|
8
|
-
* iOS, so `getEnforcing` only ever runs on Android, where autolinking has
|
|
9
|
-
* registered `SmsRetrieverPackage`.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
export default TurboModuleRegistry.getEnforcing('WeborityMobileCoreSmsRetriever');
|
|
13
|
-
//# sourceMappingURL=NativeSmsRetriever.js.map
|
|
1
|
+
import{TurboModuleRegistry as e}from"react-native";export default e.getEnforcing("WeborityMobileCoreSmsRetriever");
|