@sendoracloud/sdk-react-native 0.10.1 → 0.10.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/dist/index.cjs +38 -8
- package/dist/index.d.cts +37 -1
- package/dist/index.d.ts +37 -1
- package/dist/index.js +38 -8
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -231,9 +231,14 @@ var Auth = class {
|
|
|
231
231
|
this.inflight = Promise.resolve();
|
|
232
232
|
this.refreshInflight = null;
|
|
233
233
|
this.hooks = hooks;
|
|
234
|
+
this.readyPromise = new Promise((res) => {
|
|
235
|
+
this.resolveReady = res;
|
|
236
|
+
});
|
|
234
237
|
}
|
|
235
238
|
/** Re-hydrate session from storage. Called by the parent SDK
|
|
236
|
-
* during init() after Storage.hydrate() has run.
|
|
239
|
+
* during init() after Storage.hydrate() has run. Marks the
|
|
240
|
+
* internal ready promise as resolved so queued auth calls
|
|
241
|
+
* unblock. */
|
|
237
242
|
hydrate() {
|
|
238
243
|
const cachedUser = this.hooks.storage.get(USER_KEY);
|
|
239
244
|
const cachedToken = this.hooks.storage.get(TOKEN_KEY);
|
|
@@ -253,6 +258,7 @@ var Auth = class {
|
|
|
253
258
|
this.hooks.storage.remove(REFRESH_KEY);
|
|
254
259
|
}
|
|
255
260
|
}
|
|
261
|
+
this.resolveReady();
|
|
256
262
|
}
|
|
257
263
|
getCurrentUser() {
|
|
258
264
|
return this.user;
|
|
@@ -283,11 +289,29 @@ var Auth = class {
|
|
|
283
289
|
return data.user;
|
|
284
290
|
});
|
|
285
291
|
}
|
|
292
|
+
/**
|
|
293
|
+
* Create an email/password account.
|
|
294
|
+
*
|
|
295
|
+
* Industry-standard upgrade-first flow (matches Firebase
|
|
296
|
+
* `linkWithCredential`, Auth0 anonymous-merge, Supabase
|
|
297
|
+
* `linkIdentity`): when a refresh token is present in storage we
|
|
298
|
+
* try `/auth-service/upgrade` first — backend validates the
|
|
299
|
+
* refresh, checks the bound user is anonymous, and promotes in
|
|
300
|
+
* place preserving `user.id`. If the bound user is NOT anonymous
|
|
301
|
+
* (already identified, expired refresh, etc.), backend rejects
|
|
302
|
+
* with FORBIDDEN_NON_ANONYMOUS / INVALID_REFRESH and we fall back
|
|
303
|
+
* to a fresh /signup.
|
|
304
|
+
*
|
|
305
|
+
* Why we don't gate on the cached `this.user.isAnonymous`
|
|
306
|
+
* boolean: the in-memory user can be stale during cold start or
|
|
307
|
+
* fast-refresh races. The refresh token in storage is the
|
|
308
|
+
* authoritative signal — if it's there, the upgrade path is
|
|
309
|
+
* worth attempting; backend has the source of truth.
|
|
310
|
+
*/
|
|
286
311
|
signUp(email, password, opts) {
|
|
287
312
|
return this.serialize(async () => {
|
|
288
313
|
const refresh = this.hooks.storage.get(REFRESH_KEY);
|
|
289
|
-
|
|
290
|
-
if (isAnonymous && refresh) {
|
|
314
|
+
if (refresh) {
|
|
291
315
|
try {
|
|
292
316
|
const data = await this.callAuth("/api/v1/auth-service/upgrade", {
|
|
293
317
|
refreshToken: refresh,
|
|
@@ -301,11 +325,13 @@ var Auth = class {
|
|
|
301
325
|
if (err instanceof AuthError && (err.code === "CONFLICT" || err.code === "EMAIL_ALREADY_TAKEN")) {
|
|
302
326
|
throw new EmailAlreadyTakenError(err.message);
|
|
303
327
|
}
|
|
304
|
-
|
|
328
|
+
if (err instanceof AuthError && (err.code === "FORBIDDEN_NON_ANONYMOUS" || err.code === "INVALID_REFRESH" || err.code === "UNAUTHORIZED" || err.code === "REFRESH_EXPIRED")) {
|
|
329
|
+
await this.wipeLocalIdentity();
|
|
330
|
+
} else {
|
|
331
|
+
throw err;
|
|
332
|
+
}
|
|
305
333
|
}
|
|
306
334
|
}
|
|
307
|
-
const wasIdentified = this.user !== null && this.user.isAnonymous === false;
|
|
308
|
-
if (wasIdentified) await this.wipeLocalIdentity();
|
|
309
335
|
try {
|
|
310
336
|
const data = await this.callAuth("/api/v1/auth-service/signup", {
|
|
311
337
|
email,
|
|
@@ -685,7 +711,11 @@ var Auth = class {
|
|
|
685
711
|
}
|
|
686
712
|
// --- internals ---
|
|
687
713
|
serialize(op) {
|
|
688
|
-
const
|
|
714
|
+
const guarded = async () => {
|
|
715
|
+
await this.readyPromise;
|
|
716
|
+
return op();
|
|
717
|
+
};
|
|
718
|
+
const next = this.inflight.then(guarded, guarded);
|
|
689
719
|
this.inflight = next.catch(() => void 0);
|
|
690
720
|
return next;
|
|
691
721
|
}
|
|
@@ -770,7 +800,7 @@ function decodeJwtPayload(token) {
|
|
|
770
800
|
}
|
|
771
801
|
|
|
772
802
|
// src/index.ts
|
|
773
|
-
var SDK_VERSION = "0.10.
|
|
803
|
+
var SDK_VERSION = "0.10.3";
|
|
774
804
|
var DEFAULT_API_URL = "https://api.sendoracloud.com";
|
|
775
805
|
var ANON_KEY = "anon_id";
|
|
776
806
|
var USER_ID_KEY = "user_id";
|
package/dist/index.d.cts
CHANGED
|
@@ -106,9 +106,26 @@ declare class Auth {
|
|
|
106
106
|
private accessExpiresAt;
|
|
107
107
|
private inflight;
|
|
108
108
|
private refreshInflight;
|
|
109
|
+
/**
|
|
110
|
+
* Resolves when the parent SDK has finished its async init() —
|
|
111
|
+
* AsyncStorage hydrated + auth.hydrate() restored the cached
|
|
112
|
+
* session. All public auth methods await this internally so a
|
|
113
|
+
* customer who forgets `await sendora.init()` before showing the
|
|
114
|
+
* signup UI doesn't hit the cold-start race where the upgrade
|
|
115
|
+
* path's `storage.get(REFRESH_KEY)` returns null even though a
|
|
116
|
+
* valid anonymous session is sitting on disk.
|
|
117
|
+
*
|
|
118
|
+
* Industry-standard "SDK gates on internal readiness" pattern
|
|
119
|
+
* (Firebase Auth, Auth0, Clerk all do equivalent internal
|
|
120
|
+
* gating).
|
|
121
|
+
*/
|
|
122
|
+
private readyPromise;
|
|
123
|
+
private resolveReady;
|
|
109
124
|
constructor(hooks: AuthHooks);
|
|
110
125
|
/** Re-hydrate session from storage. Called by the parent SDK
|
|
111
|
-
* during init() after Storage.hydrate() has run.
|
|
126
|
+
* during init() after Storage.hydrate() has run. Marks the
|
|
127
|
+
* internal ready promise as resolved so queued auth calls
|
|
128
|
+
* unblock. */
|
|
112
129
|
hydrate(): void;
|
|
113
130
|
getCurrentUser(): AuthUser | null;
|
|
114
131
|
/**
|
|
@@ -123,6 +140,25 @@ declare class Auth {
|
|
|
123
140
|
name?: string;
|
|
124
141
|
metadata?: Record<string, unknown>;
|
|
125
142
|
}): Promise<AuthUser>;
|
|
143
|
+
/**
|
|
144
|
+
* Create an email/password account.
|
|
145
|
+
*
|
|
146
|
+
* Industry-standard upgrade-first flow (matches Firebase
|
|
147
|
+
* `linkWithCredential`, Auth0 anonymous-merge, Supabase
|
|
148
|
+
* `linkIdentity`): when a refresh token is present in storage we
|
|
149
|
+
* try `/auth-service/upgrade` first — backend validates the
|
|
150
|
+
* refresh, checks the bound user is anonymous, and promotes in
|
|
151
|
+
* place preserving `user.id`. If the bound user is NOT anonymous
|
|
152
|
+
* (already identified, expired refresh, etc.), backend rejects
|
|
153
|
+
* with FORBIDDEN_NON_ANONYMOUS / INVALID_REFRESH and we fall back
|
|
154
|
+
* to a fresh /signup.
|
|
155
|
+
*
|
|
156
|
+
* Why we don't gate on the cached `this.user.isAnonymous`
|
|
157
|
+
* boolean: the in-memory user can be stale during cold start or
|
|
158
|
+
* fast-refresh races. The refresh token in storage is the
|
|
159
|
+
* authoritative signal — if it's there, the upgrade path is
|
|
160
|
+
* worth attempting; backend has the source of truth.
|
|
161
|
+
*/
|
|
126
162
|
signUp(email: string, password: string, opts?: {
|
|
127
163
|
name?: string;
|
|
128
164
|
metadata?: Record<string, unknown>;
|
package/dist/index.d.ts
CHANGED
|
@@ -106,9 +106,26 @@ declare class Auth {
|
|
|
106
106
|
private accessExpiresAt;
|
|
107
107
|
private inflight;
|
|
108
108
|
private refreshInflight;
|
|
109
|
+
/**
|
|
110
|
+
* Resolves when the parent SDK has finished its async init() —
|
|
111
|
+
* AsyncStorage hydrated + auth.hydrate() restored the cached
|
|
112
|
+
* session. All public auth methods await this internally so a
|
|
113
|
+
* customer who forgets `await sendora.init()` before showing the
|
|
114
|
+
* signup UI doesn't hit the cold-start race where the upgrade
|
|
115
|
+
* path's `storage.get(REFRESH_KEY)` returns null even though a
|
|
116
|
+
* valid anonymous session is sitting on disk.
|
|
117
|
+
*
|
|
118
|
+
* Industry-standard "SDK gates on internal readiness" pattern
|
|
119
|
+
* (Firebase Auth, Auth0, Clerk all do equivalent internal
|
|
120
|
+
* gating).
|
|
121
|
+
*/
|
|
122
|
+
private readyPromise;
|
|
123
|
+
private resolveReady;
|
|
109
124
|
constructor(hooks: AuthHooks);
|
|
110
125
|
/** Re-hydrate session from storage. Called by the parent SDK
|
|
111
|
-
* during init() after Storage.hydrate() has run.
|
|
126
|
+
* during init() after Storage.hydrate() has run. Marks the
|
|
127
|
+
* internal ready promise as resolved so queued auth calls
|
|
128
|
+
* unblock. */
|
|
112
129
|
hydrate(): void;
|
|
113
130
|
getCurrentUser(): AuthUser | null;
|
|
114
131
|
/**
|
|
@@ -123,6 +140,25 @@ declare class Auth {
|
|
|
123
140
|
name?: string;
|
|
124
141
|
metadata?: Record<string, unknown>;
|
|
125
142
|
}): Promise<AuthUser>;
|
|
143
|
+
/**
|
|
144
|
+
* Create an email/password account.
|
|
145
|
+
*
|
|
146
|
+
* Industry-standard upgrade-first flow (matches Firebase
|
|
147
|
+
* `linkWithCredential`, Auth0 anonymous-merge, Supabase
|
|
148
|
+
* `linkIdentity`): when a refresh token is present in storage we
|
|
149
|
+
* try `/auth-service/upgrade` first — backend validates the
|
|
150
|
+
* refresh, checks the bound user is anonymous, and promotes in
|
|
151
|
+
* place preserving `user.id`. If the bound user is NOT anonymous
|
|
152
|
+
* (already identified, expired refresh, etc.), backend rejects
|
|
153
|
+
* with FORBIDDEN_NON_ANONYMOUS / INVALID_REFRESH and we fall back
|
|
154
|
+
* to a fresh /signup.
|
|
155
|
+
*
|
|
156
|
+
* Why we don't gate on the cached `this.user.isAnonymous`
|
|
157
|
+
* boolean: the in-memory user can be stale during cold start or
|
|
158
|
+
* fast-refresh races. The refresh token in storage is the
|
|
159
|
+
* authoritative signal — if it's there, the upgrade path is
|
|
160
|
+
* worth attempting; backend has the source of truth.
|
|
161
|
+
*/
|
|
126
162
|
signUp(email: string, password: string, opts?: {
|
|
127
163
|
name?: string;
|
|
128
164
|
metadata?: Record<string, unknown>;
|
package/dist/index.js
CHANGED
|
@@ -191,9 +191,14 @@ var Auth = class {
|
|
|
191
191
|
this.inflight = Promise.resolve();
|
|
192
192
|
this.refreshInflight = null;
|
|
193
193
|
this.hooks = hooks;
|
|
194
|
+
this.readyPromise = new Promise((res) => {
|
|
195
|
+
this.resolveReady = res;
|
|
196
|
+
});
|
|
194
197
|
}
|
|
195
198
|
/** Re-hydrate session from storage. Called by the parent SDK
|
|
196
|
-
* during init() after Storage.hydrate() has run.
|
|
199
|
+
* during init() after Storage.hydrate() has run. Marks the
|
|
200
|
+
* internal ready promise as resolved so queued auth calls
|
|
201
|
+
* unblock. */
|
|
197
202
|
hydrate() {
|
|
198
203
|
const cachedUser = this.hooks.storage.get(USER_KEY);
|
|
199
204
|
const cachedToken = this.hooks.storage.get(TOKEN_KEY);
|
|
@@ -213,6 +218,7 @@ var Auth = class {
|
|
|
213
218
|
this.hooks.storage.remove(REFRESH_KEY);
|
|
214
219
|
}
|
|
215
220
|
}
|
|
221
|
+
this.resolveReady();
|
|
216
222
|
}
|
|
217
223
|
getCurrentUser() {
|
|
218
224
|
return this.user;
|
|
@@ -243,11 +249,29 @@ var Auth = class {
|
|
|
243
249
|
return data.user;
|
|
244
250
|
});
|
|
245
251
|
}
|
|
252
|
+
/**
|
|
253
|
+
* Create an email/password account.
|
|
254
|
+
*
|
|
255
|
+
* Industry-standard upgrade-first flow (matches Firebase
|
|
256
|
+
* `linkWithCredential`, Auth0 anonymous-merge, Supabase
|
|
257
|
+
* `linkIdentity`): when a refresh token is present in storage we
|
|
258
|
+
* try `/auth-service/upgrade` first — backend validates the
|
|
259
|
+
* refresh, checks the bound user is anonymous, and promotes in
|
|
260
|
+
* place preserving `user.id`. If the bound user is NOT anonymous
|
|
261
|
+
* (already identified, expired refresh, etc.), backend rejects
|
|
262
|
+
* with FORBIDDEN_NON_ANONYMOUS / INVALID_REFRESH and we fall back
|
|
263
|
+
* to a fresh /signup.
|
|
264
|
+
*
|
|
265
|
+
* Why we don't gate on the cached `this.user.isAnonymous`
|
|
266
|
+
* boolean: the in-memory user can be stale during cold start or
|
|
267
|
+
* fast-refresh races. The refresh token in storage is the
|
|
268
|
+
* authoritative signal — if it's there, the upgrade path is
|
|
269
|
+
* worth attempting; backend has the source of truth.
|
|
270
|
+
*/
|
|
246
271
|
signUp(email, password, opts) {
|
|
247
272
|
return this.serialize(async () => {
|
|
248
273
|
const refresh = this.hooks.storage.get(REFRESH_KEY);
|
|
249
|
-
|
|
250
|
-
if (isAnonymous && refresh) {
|
|
274
|
+
if (refresh) {
|
|
251
275
|
try {
|
|
252
276
|
const data = await this.callAuth("/api/v1/auth-service/upgrade", {
|
|
253
277
|
refreshToken: refresh,
|
|
@@ -261,11 +285,13 @@ var Auth = class {
|
|
|
261
285
|
if (err instanceof AuthError && (err.code === "CONFLICT" || err.code === "EMAIL_ALREADY_TAKEN")) {
|
|
262
286
|
throw new EmailAlreadyTakenError(err.message);
|
|
263
287
|
}
|
|
264
|
-
|
|
288
|
+
if (err instanceof AuthError && (err.code === "FORBIDDEN_NON_ANONYMOUS" || err.code === "INVALID_REFRESH" || err.code === "UNAUTHORIZED" || err.code === "REFRESH_EXPIRED")) {
|
|
289
|
+
await this.wipeLocalIdentity();
|
|
290
|
+
} else {
|
|
291
|
+
throw err;
|
|
292
|
+
}
|
|
265
293
|
}
|
|
266
294
|
}
|
|
267
|
-
const wasIdentified = this.user !== null && this.user.isAnonymous === false;
|
|
268
|
-
if (wasIdentified) await this.wipeLocalIdentity();
|
|
269
295
|
try {
|
|
270
296
|
const data = await this.callAuth("/api/v1/auth-service/signup", {
|
|
271
297
|
email,
|
|
@@ -645,7 +671,11 @@ var Auth = class {
|
|
|
645
671
|
}
|
|
646
672
|
// --- internals ---
|
|
647
673
|
serialize(op) {
|
|
648
|
-
const
|
|
674
|
+
const guarded = async () => {
|
|
675
|
+
await this.readyPromise;
|
|
676
|
+
return op();
|
|
677
|
+
};
|
|
678
|
+
const next = this.inflight.then(guarded, guarded);
|
|
649
679
|
this.inflight = next.catch(() => void 0);
|
|
650
680
|
return next;
|
|
651
681
|
}
|
|
@@ -730,7 +760,7 @@ function decodeJwtPayload(token) {
|
|
|
730
760
|
}
|
|
731
761
|
|
|
732
762
|
// src/index.ts
|
|
733
|
-
var SDK_VERSION = "0.10.
|
|
763
|
+
var SDK_VERSION = "0.10.3";
|
|
734
764
|
var DEFAULT_API_URL = "https://api.sendoracloud.com";
|
|
735
765
|
var ANON_KEY = "anon_id";
|
|
736
766
|
var USER_ID_KEY = "user_id";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sendoracloud/sdk-react-native",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.3",
|
|
4
4
|
"description": "Sendora Cloud React Native + Expo SDK — analytics, identity, push-token registration, auth. Expo Go compatible, no native modules beyond AsyncStorage.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|