@sendoracloud/sdk-react-native 1.0.4 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +50 -1
- package/dist/index.d.cts +39 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.js +50 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -237,6 +237,8 @@ var Auth = class {
|
|
|
237
237
|
this.accessExpiresAt = 0;
|
|
238
238
|
this.inflight = Promise.resolve();
|
|
239
239
|
this.refreshInflight = null;
|
|
240
|
+
this.takeoverListeners = /* @__PURE__ */ new Set();
|
|
241
|
+
this.lastTakeover = null;
|
|
240
242
|
/**
|
|
241
243
|
* Proactive refresh (s58.72). Fires when access-token age crosses
|
|
242
244
|
* ~80% of TTL. Pairs with the AppState foreground listener so a
|
|
@@ -299,6 +301,47 @@ var Auth = class {
|
|
|
299
301
|
getCurrentUser() {
|
|
300
302
|
return this.user;
|
|
301
303
|
}
|
|
304
|
+
/**
|
|
305
|
+
* Subscribe to device-takeover events (s58.111+). Backend retires
|
|
306
|
+
* the anonymous user_id whenever a previously-anon device signs in
|
|
307
|
+
* to an identified account. Use this to clean up any local mirror
|
|
308
|
+
* keyed by the anonymous id — your own users table, an analytics
|
|
309
|
+
* cache, an audience filter — without needing a webhook receiver.
|
|
310
|
+
*
|
|
311
|
+
* Listener is invoked from `signIn`, `loginSocial`, `verifyMagicLink`,
|
|
312
|
+
* `verifyEmailOtp`, `passkey authentication`, and SSO completion
|
|
313
|
+
* (via `consumeSsoFragment`). Returns an unsubscribe function.
|
|
314
|
+
*
|
|
315
|
+
* Listeners are best-effort: errors thrown inside them are swallowed
|
|
316
|
+
* + logged in dev mode. Auth state never depends on listener success.
|
|
317
|
+
*/
|
|
318
|
+
onDeviceTakeover(listener) {
|
|
319
|
+
this.takeoverListeners.add(listener);
|
|
320
|
+
return () => {
|
|
321
|
+
this.takeoverListeners.delete(listener);
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Inspect the most recent device-takeover event observed by the
|
|
326
|
+
* SDK. Cleared on signOut. Returns null when no takeover has
|
|
327
|
+
* happened during this session — useful for late subscribers or
|
|
328
|
+
* post-sign-in side-effects (e.g. app-launch hydrate).
|
|
329
|
+
*/
|
|
330
|
+
getLastDeviceTakeover() {
|
|
331
|
+
return this.lastTakeover;
|
|
332
|
+
}
|
|
333
|
+
/** Internal — fan out to subscribers + cache the latest. */
|
|
334
|
+
fireDeviceTakeover(retiredAnonUserId, identifiedUserId) {
|
|
335
|
+
const evt = { retiredAnonUserId, identifiedUserId, at: /* @__PURE__ */ new Date() };
|
|
336
|
+
this.lastTakeover = evt;
|
|
337
|
+
for (const fn of this.takeoverListeners) {
|
|
338
|
+
try {
|
|
339
|
+
fn(evt);
|
|
340
|
+
} catch (err) {
|
|
341
|
+
if (this.hooks.debug) console.warn("[sendoracloud] onDeviceTakeover listener threw", err);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
302
345
|
/**
|
|
303
346
|
* Returns a non-expired access token or null. If the cached token
|
|
304
347
|
* is expired but a refresh token exists, transparently refreshes
|
|
@@ -741,6 +784,7 @@ var Auth = class {
|
|
|
741
784
|
if (err) throw new AuthError("SSO_FAILED", decodeURIComponent(err));
|
|
742
785
|
const refresh = params.get("sendora_oidc_token") ?? params.get("sendora_saml_token");
|
|
743
786
|
if (!refresh) throw new AuthError("SSO_NO_TOKEN", "Callback URL has no SSO token");
|
|
787
|
+
const retiredAnonUserId = params.get("sendora_retired_anon");
|
|
744
788
|
const res = await post(
|
|
745
789
|
{ apiUrl: this.hooks.apiUrl, publicKey: this.hooks.publicKey, debug: this.hooks.debug },
|
|
746
790
|
"/api/v1/auth-service/token/refresh",
|
|
@@ -769,7 +813,8 @@ var Auth = class {
|
|
|
769
813
|
refreshToken: newRefresh,
|
|
770
814
|
expiresIn,
|
|
771
815
|
tokenType: "Bearer"
|
|
772
|
-
}
|
|
816
|
+
},
|
|
817
|
+
...retiredAnonUserId ? { retiredAnonUserId } : {}
|
|
773
818
|
});
|
|
774
819
|
return user;
|
|
775
820
|
});
|
|
@@ -1010,11 +1055,15 @@ var Auth = class {
|
|
|
1010
1055
|
this.hooks.storage.set(REFRESH_KEY, data.tokens.refreshToken);
|
|
1011
1056
|
this.hooks.onIdentityChange(data.user.id);
|
|
1012
1057
|
this.startProactiveRefreshCron();
|
|
1058
|
+
if (data.retiredAnonUserId) {
|
|
1059
|
+
this.fireDeviceTakeover(data.retiredAnonUserId, data.user.id);
|
|
1060
|
+
}
|
|
1013
1061
|
}
|
|
1014
1062
|
async wipeLocalIdentity() {
|
|
1015
1063
|
this.user = null;
|
|
1016
1064
|
this.accessToken = null;
|
|
1017
1065
|
this.accessExpiresAt = 0;
|
|
1066
|
+
this.lastTakeover = null;
|
|
1018
1067
|
this.hooks.storage.remove(USER_KEY);
|
|
1019
1068
|
this.hooks.storage.remove(TOKEN_KEY);
|
|
1020
1069
|
this.hooks.storage.remove(TOKEN_EXPIRES_KEY);
|
package/dist/index.d.cts
CHANGED
|
@@ -99,6 +99,19 @@ interface AuthHooks {
|
|
|
99
99
|
publicKey: string;
|
|
100
100
|
debug: boolean;
|
|
101
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* Detail handed to onDeviceTakeover subscribers. Fires once per
|
|
104
|
+
* signIn / loginSocial / verifyMagicLink / verifyEmailOtp /
|
|
105
|
+
* passkey-auth / SSO completion that retires an anonymous row.
|
|
106
|
+
*/
|
|
107
|
+
interface DeviceTakeoverEvent {
|
|
108
|
+
/** The anonymous user_id Sendora just hard-deleted. */
|
|
109
|
+
retiredAnonUserId: string;
|
|
110
|
+
/** The identified user_id that took the device over. */
|
|
111
|
+
identifiedUserId: string;
|
|
112
|
+
/** Local clock at the time the SDK observed the event. */
|
|
113
|
+
at: Date;
|
|
114
|
+
}
|
|
102
115
|
declare class Auth {
|
|
103
116
|
private hooks;
|
|
104
117
|
private user;
|
|
@@ -106,6 +119,8 @@ declare class Auth {
|
|
|
106
119
|
private accessExpiresAt;
|
|
107
120
|
private inflight;
|
|
108
121
|
private refreshInflight;
|
|
122
|
+
private takeoverListeners;
|
|
123
|
+
private lastTakeover;
|
|
109
124
|
/**
|
|
110
125
|
* Resolves when the parent SDK has finished its async init() —
|
|
111
126
|
* AsyncStorage hydrated + auth.hydrate() restored the cached
|
|
@@ -142,6 +157,30 @@ declare class Auth {
|
|
|
142
157
|
*/
|
|
143
158
|
hydrate(): void;
|
|
144
159
|
getCurrentUser(): AuthUser | null;
|
|
160
|
+
/**
|
|
161
|
+
* Subscribe to device-takeover events (s58.111+). Backend retires
|
|
162
|
+
* the anonymous user_id whenever a previously-anon device signs in
|
|
163
|
+
* to an identified account. Use this to clean up any local mirror
|
|
164
|
+
* keyed by the anonymous id — your own users table, an analytics
|
|
165
|
+
* cache, an audience filter — without needing a webhook receiver.
|
|
166
|
+
*
|
|
167
|
+
* Listener is invoked from `signIn`, `loginSocial`, `verifyMagicLink`,
|
|
168
|
+
* `verifyEmailOtp`, `passkey authentication`, and SSO completion
|
|
169
|
+
* (via `consumeSsoFragment`). Returns an unsubscribe function.
|
|
170
|
+
*
|
|
171
|
+
* Listeners are best-effort: errors thrown inside them are swallowed
|
|
172
|
+
* + logged in dev mode. Auth state never depends on listener success.
|
|
173
|
+
*/
|
|
174
|
+
onDeviceTakeover(listener: (evt: DeviceTakeoverEvent) => void): () => void;
|
|
175
|
+
/**
|
|
176
|
+
* Inspect the most recent device-takeover event observed by the
|
|
177
|
+
* SDK. Cleared on signOut. Returns null when no takeover has
|
|
178
|
+
* happened during this session — useful for late subscribers or
|
|
179
|
+
* post-sign-in side-effects (e.g. app-launch hydrate).
|
|
180
|
+
*/
|
|
181
|
+
getLastDeviceTakeover(): DeviceTakeoverEvent | null;
|
|
182
|
+
/** Internal — fan out to subscribers + cache the latest. */
|
|
183
|
+
private fireDeviceTakeover;
|
|
145
184
|
/**
|
|
146
185
|
* Returns a non-expired access token or null. If the cached token
|
|
147
186
|
* is expired but a refresh token exists, transparently refreshes
|
package/dist/index.d.ts
CHANGED
|
@@ -99,6 +99,19 @@ interface AuthHooks {
|
|
|
99
99
|
publicKey: string;
|
|
100
100
|
debug: boolean;
|
|
101
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* Detail handed to onDeviceTakeover subscribers. Fires once per
|
|
104
|
+
* signIn / loginSocial / verifyMagicLink / verifyEmailOtp /
|
|
105
|
+
* passkey-auth / SSO completion that retires an anonymous row.
|
|
106
|
+
*/
|
|
107
|
+
interface DeviceTakeoverEvent {
|
|
108
|
+
/** The anonymous user_id Sendora just hard-deleted. */
|
|
109
|
+
retiredAnonUserId: string;
|
|
110
|
+
/** The identified user_id that took the device over. */
|
|
111
|
+
identifiedUserId: string;
|
|
112
|
+
/** Local clock at the time the SDK observed the event. */
|
|
113
|
+
at: Date;
|
|
114
|
+
}
|
|
102
115
|
declare class Auth {
|
|
103
116
|
private hooks;
|
|
104
117
|
private user;
|
|
@@ -106,6 +119,8 @@ declare class Auth {
|
|
|
106
119
|
private accessExpiresAt;
|
|
107
120
|
private inflight;
|
|
108
121
|
private refreshInflight;
|
|
122
|
+
private takeoverListeners;
|
|
123
|
+
private lastTakeover;
|
|
109
124
|
/**
|
|
110
125
|
* Resolves when the parent SDK has finished its async init() —
|
|
111
126
|
* AsyncStorage hydrated + auth.hydrate() restored the cached
|
|
@@ -142,6 +157,30 @@ declare class Auth {
|
|
|
142
157
|
*/
|
|
143
158
|
hydrate(): void;
|
|
144
159
|
getCurrentUser(): AuthUser | null;
|
|
160
|
+
/**
|
|
161
|
+
* Subscribe to device-takeover events (s58.111+). Backend retires
|
|
162
|
+
* the anonymous user_id whenever a previously-anon device signs in
|
|
163
|
+
* to an identified account. Use this to clean up any local mirror
|
|
164
|
+
* keyed by the anonymous id — your own users table, an analytics
|
|
165
|
+
* cache, an audience filter — without needing a webhook receiver.
|
|
166
|
+
*
|
|
167
|
+
* Listener is invoked from `signIn`, `loginSocial`, `verifyMagicLink`,
|
|
168
|
+
* `verifyEmailOtp`, `passkey authentication`, and SSO completion
|
|
169
|
+
* (via `consumeSsoFragment`). Returns an unsubscribe function.
|
|
170
|
+
*
|
|
171
|
+
* Listeners are best-effort: errors thrown inside them are swallowed
|
|
172
|
+
* + logged in dev mode. Auth state never depends on listener success.
|
|
173
|
+
*/
|
|
174
|
+
onDeviceTakeover(listener: (evt: DeviceTakeoverEvent) => void): () => void;
|
|
175
|
+
/**
|
|
176
|
+
* Inspect the most recent device-takeover event observed by the
|
|
177
|
+
* SDK. Cleared on signOut. Returns null when no takeover has
|
|
178
|
+
* happened during this session — useful for late subscribers or
|
|
179
|
+
* post-sign-in side-effects (e.g. app-launch hydrate).
|
|
180
|
+
*/
|
|
181
|
+
getLastDeviceTakeover(): DeviceTakeoverEvent | null;
|
|
182
|
+
/** Internal — fan out to subscribers + cache the latest. */
|
|
183
|
+
private fireDeviceTakeover;
|
|
145
184
|
/**
|
|
146
185
|
* Returns a non-expired access token or null. If the cached token
|
|
147
186
|
* is expired but a refresh token exists, transparently refreshes
|
package/dist/index.js
CHANGED
|
@@ -194,6 +194,8 @@ var Auth = class {
|
|
|
194
194
|
this.accessExpiresAt = 0;
|
|
195
195
|
this.inflight = Promise.resolve();
|
|
196
196
|
this.refreshInflight = null;
|
|
197
|
+
this.takeoverListeners = /* @__PURE__ */ new Set();
|
|
198
|
+
this.lastTakeover = null;
|
|
197
199
|
/**
|
|
198
200
|
* Proactive refresh (s58.72). Fires when access-token age crosses
|
|
199
201
|
* ~80% of TTL. Pairs with the AppState foreground listener so a
|
|
@@ -256,6 +258,47 @@ var Auth = class {
|
|
|
256
258
|
getCurrentUser() {
|
|
257
259
|
return this.user;
|
|
258
260
|
}
|
|
261
|
+
/**
|
|
262
|
+
* Subscribe to device-takeover events (s58.111+). Backend retires
|
|
263
|
+
* the anonymous user_id whenever a previously-anon device signs in
|
|
264
|
+
* to an identified account. Use this to clean up any local mirror
|
|
265
|
+
* keyed by the anonymous id — your own users table, an analytics
|
|
266
|
+
* cache, an audience filter — without needing a webhook receiver.
|
|
267
|
+
*
|
|
268
|
+
* Listener is invoked from `signIn`, `loginSocial`, `verifyMagicLink`,
|
|
269
|
+
* `verifyEmailOtp`, `passkey authentication`, and SSO completion
|
|
270
|
+
* (via `consumeSsoFragment`). Returns an unsubscribe function.
|
|
271
|
+
*
|
|
272
|
+
* Listeners are best-effort: errors thrown inside them are swallowed
|
|
273
|
+
* + logged in dev mode. Auth state never depends on listener success.
|
|
274
|
+
*/
|
|
275
|
+
onDeviceTakeover(listener) {
|
|
276
|
+
this.takeoverListeners.add(listener);
|
|
277
|
+
return () => {
|
|
278
|
+
this.takeoverListeners.delete(listener);
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Inspect the most recent device-takeover event observed by the
|
|
283
|
+
* SDK. Cleared on signOut. Returns null when no takeover has
|
|
284
|
+
* happened during this session — useful for late subscribers or
|
|
285
|
+
* post-sign-in side-effects (e.g. app-launch hydrate).
|
|
286
|
+
*/
|
|
287
|
+
getLastDeviceTakeover() {
|
|
288
|
+
return this.lastTakeover;
|
|
289
|
+
}
|
|
290
|
+
/** Internal — fan out to subscribers + cache the latest. */
|
|
291
|
+
fireDeviceTakeover(retiredAnonUserId, identifiedUserId) {
|
|
292
|
+
const evt = { retiredAnonUserId, identifiedUserId, at: /* @__PURE__ */ new Date() };
|
|
293
|
+
this.lastTakeover = evt;
|
|
294
|
+
for (const fn of this.takeoverListeners) {
|
|
295
|
+
try {
|
|
296
|
+
fn(evt);
|
|
297
|
+
} catch (err) {
|
|
298
|
+
if (this.hooks.debug) console.warn("[sendoracloud] onDeviceTakeover listener threw", err);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
259
302
|
/**
|
|
260
303
|
* Returns a non-expired access token or null. If the cached token
|
|
261
304
|
* is expired but a refresh token exists, transparently refreshes
|
|
@@ -698,6 +741,7 @@ var Auth = class {
|
|
|
698
741
|
if (err) throw new AuthError("SSO_FAILED", decodeURIComponent(err));
|
|
699
742
|
const refresh = params.get("sendora_oidc_token") ?? params.get("sendora_saml_token");
|
|
700
743
|
if (!refresh) throw new AuthError("SSO_NO_TOKEN", "Callback URL has no SSO token");
|
|
744
|
+
const retiredAnonUserId = params.get("sendora_retired_anon");
|
|
701
745
|
const res = await post(
|
|
702
746
|
{ apiUrl: this.hooks.apiUrl, publicKey: this.hooks.publicKey, debug: this.hooks.debug },
|
|
703
747
|
"/api/v1/auth-service/token/refresh",
|
|
@@ -726,7 +770,8 @@ var Auth = class {
|
|
|
726
770
|
refreshToken: newRefresh,
|
|
727
771
|
expiresIn,
|
|
728
772
|
tokenType: "Bearer"
|
|
729
|
-
}
|
|
773
|
+
},
|
|
774
|
+
...retiredAnonUserId ? { retiredAnonUserId } : {}
|
|
730
775
|
});
|
|
731
776
|
return user;
|
|
732
777
|
});
|
|
@@ -967,11 +1012,15 @@ var Auth = class {
|
|
|
967
1012
|
this.hooks.storage.set(REFRESH_KEY, data.tokens.refreshToken);
|
|
968
1013
|
this.hooks.onIdentityChange(data.user.id);
|
|
969
1014
|
this.startProactiveRefreshCron();
|
|
1015
|
+
if (data.retiredAnonUserId) {
|
|
1016
|
+
this.fireDeviceTakeover(data.retiredAnonUserId, data.user.id);
|
|
1017
|
+
}
|
|
970
1018
|
}
|
|
971
1019
|
async wipeLocalIdentity() {
|
|
972
1020
|
this.user = null;
|
|
973
1021
|
this.accessToken = null;
|
|
974
1022
|
this.accessExpiresAt = 0;
|
|
1023
|
+
this.lastTakeover = null;
|
|
975
1024
|
this.hooks.storage.remove(USER_KEY);
|
|
976
1025
|
this.hooks.storage.remove(TOKEN_KEY);
|
|
977
1026
|
this.hooks.storage.remove(TOKEN_EXPIRES_KEY);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sendoracloud/sdk-react-native",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "Sendora Cloud React Native + Expo SDK — analytics, identity, push-token registration, auth, deep links (Branch / Firebase Dynamic Links parity). Auth + analytics + deep links work in Expo Go; push token registration requires a Dev Client (EAS Build or `npx expo prebuild`).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|