@sendoracloud/sdk-react-native 0.10.0 → 0.10.2

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 CHANGED
@@ -200,11 +200,14 @@ function isAuthApiResponse(v) {
200
200
  const user = r.user;
201
201
  const tokens = r.tokens;
202
202
  if (!user || typeof user.id !== "string" || user.id.length === 0) return false;
203
- if (typeof user.isAnonymous !== "boolean") return false;
204
203
  if (!tokens) return false;
205
204
  if (typeof tokens.accessToken !== "string" || tokens.accessToken.length === 0) return false;
206
205
  if (typeof tokens.refreshToken !== "string" || tokens.refreshToken.length === 0) return false;
207
206
  if (typeof tokens.expiresIn !== "number" || tokens.expiresIn <= 0) return false;
207
+ if (user.isAnonymous !== void 0 && typeof user.isAnonymous !== "boolean") return false;
208
+ if (user.email !== void 0 && user.email !== null && typeof user.email !== "string") return false;
209
+ if (user.emailVerified !== void 0 && typeof user.emailVerified !== "boolean") return false;
210
+ if (user.name !== void 0 && user.name !== null && typeof user.name !== "string") return false;
208
211
  return true;
209
212
  }
210
213
  var EmailAlreadyTakenError = class extends Error {
@@ -280,11 +283,29 @@ var Auth = class {
280
283
  return data.user;
281
284
  });
282
285
  }
286
+ /**
287
+ * Create an email/password account.
288
+ *
289
+ * Industry-standard upgrade-first flow (matches Firebase
290
+ * `linkWithCredential`, Auth0 anonymous-merge, Supabase
291
+ * `linkIdentity`): when a refresh token is present in storage we
292
+ * try `/auth-service/upgrade` first — backend validates the
293
+ * refresh, checks the bound user is anonymous, and promotes in
294
+ * place preserving `user.id`. If the bound user is NOT anonymous
295
+ * (already identified, expired refresh, etc.), backend rejects
296
+ * with FORBIDDEN_NON_ANONYMOUS / INVALID_REFRESH and we fall back
297
+ * to a fresh /signup.
298
+ *
299
+ * Why we don't gate on the cached `this.user.isAnonymous`
300
+ * boolean: the in-memory user can be stale during cold start or
301
+ * fast-refresh races. The refresh token in storage is the
302
+ * authoritative signal — if it's there, the upgrade path is
303
+ * worth attempting; backend has the source of truth.
304
+ */
283
305
  signUp(email, password, opts) {
284
306
  return this.serialize(async () => {
285
307
  const refresh = this.hooks.storage.get(REFRESH_KEY);
286
- const isAnonymous = this.user?.isAnonymous === true;
287
- if (isAnonymous && refresh) {
308
+ if (refresh) {
288
309
  try {
289
310
  const data = await this.callAuth("/api/v1/auth-service/upgrade", {
290
311
  refreshToken: refresh,
@@ -298,11 +319,13 @@ var Auth = class {
298
319
  if (err instanceof AuthError && (err.code === "CONFLICT" || err.code === "EMAIL_ALREADY_TAKEN")) {
299
320
  throw new EmailAlreadyTakenError(err.message);
300
321
  }
301
- throw err;
322
+ if (err instanceof AuthError && (err.code === "FORBIDDEN_NON_ANONYMOUS" || err.code === "INVALID_REFRESH" || err.code === "UNAUTHORIZED" || err.code === "REFRESH_EXPIRED")) {
323
+ await this.wipeLocalIdentity();
324
+ } else {
325
+ throw err;
326
+ }
302
327
  }
303
328
  }
304
- const wasIdentified = this.user !== null && this.user.isAnonymous === false;
305
- if (wasIdentified) await this.wipeLocalIdentity();
306
329
  try {
307
330
  const data = await this.callAuth("/api/v1/auth-service/signup", {
308
331
  email,
@@ -767,7 +790,7 @@ function decodeJwtPayload(token) {
767
790
  }
768
791
 
769
792
  // src/index.ts
770
- var SDK_VERSION = "0.10.0";
793
+ var SDK_VERSION = "0.10.2";
771
794
  var DEFAULT_API_URL = "https://api.sendoracloud.com";
772
795
  var ANON_KEY = "anon_id";
773
796
  var USER_ID_KEY = "user_id";
package/dist/index.d.cts CHANGED
@@ -123,6 +123,25 @@ declare class Auth {
123
123
  name?: string;
124
124
  metadata?: Record<string, unknown>;
125
125
  }): Promise<AuthUser>;
126
+ /**
127
+ * Create an email/password account.
128
+ *
129
+ * Industry-standard upgrade-first flow (matches Firebase
130
+ * `linkWithCredential`, Auth0 anonymous-merge, Supabase
131
+ * `linkIdentity`): when a refresh token is present in storage we
132
+ * try `/auth-service/upgrade` first — backend validates the
133
+ * refresh, checks the bound user is anonymous, and promotes in
134
+ * place preserving `user.id`. If the bound user is NOT anonymous
135
+ * (already identified, expired refresh, etc.), backend rejects
136
+ * with FORBIDDEN_NON_ANONYMOUS / INVALID_REFRESH and we fall back
137
+ * to a fresh /signup.
138
+ *
139
+ * Why we don't gate on the cached `this.user.isAnonymous`
140
+ * boolean: the in-memory user can be stale during cold start or
141
+ * fast-refresh races. The refresh token in storage is the
142
+ * authoritative signal — if it's there, the upgrade path is
143
+ * worth attempting; backend has the source of truth.
144
+ */
126
145
  signUp(email: string, password: string, opts?: {
127
146
  name?: string;
128
147
  metadata?: Record<string, unknown>;
package/dist/index.d.ts CHANGED
@@ -123,6 +123,25 @@ declare class Auth {
123
123
  name?: string;
124
124
  metadata?: Record<string, unknown>;
125
125
  }): Promise<AuthUser>;
126
+ /**
127
+ * Create an email/password account.
128
+ *
129
+ * Industry-standard upgrade-first flow (matches Firebase
130
+ * `linkWithCredential`, Auth0 anonymous-merge, Supabase
131
+ * `linkIdentity`): when a refresh token is present in storage we
132
+ * try `/auth-service/upgrade` first — backend validates the
133
+ * refresh, checks the bound user is anonymous, and promotes in
134
+ * place preserving `user.id`. If the bound user is NOT anonymous
135
+ * (already identified, expired refresh, etc.), backend rejects
136
+ * with FORBIDDEN_NON_ANONYMOUS / INVALID_REFRESH and we fall back
137
+ * to a fresh /signup.
138
+ *
139
+ * Why we don't gate on the cached `this.user.isAnonymous`
140
+ * boolean: the in-memory user can be stale during cold start or
141
+ * fast-refresh races. The refresh token in storage is the
142
+ * authoritative signal — if it's there, the upgrade path is
143
+ * worth attempting; backend has the source of truth.
144
+ */
126
145
  signUp(email: string, password: string, opts?: {
127
146
  name?: string;
128
147
  metadata?: Record<string, unknown>;
package/dist/index.js CHANGED
@@ -160,11 +160,14 @@ function isAuthApiResponse(v) {
160
160
  const user = r.user;
161
161
  const tokens = r.tokens;
162
162
  if (!user || typeof user.id !== "string" || user.id.length === 0) return false;
163
- if (typeof user.isAnonymous !== "boolean") return false;
164
163
  if (!tokens) return false;
165
164
  if (typeof tokens.accessToken !== "string" || tokens.accessToken.length === 0) return false;
166
165
  if (typeof tokens.refreshToken !== "string" || tokens.refreshToken.length === 0) return false;
167
166
  if (typeof tokens.expiresIn !== "number" || tokens.expiresIn <= 0) return false;
167
+ if (user.isAnonymous !== void 0 && typeof user.isAnonymous !== "boolean") return false;
168
+ if (user.email !== void 0 && user.email !== null && typeof user.email !== "string") return false;
169
+ if (user.emailVerified !== void 0 && typeof user.emailVerified !== "boolean") return false;
170
+ if (user.name !== void 0 && user.name !== null && typeof user.name !== "string") return false;
168
171
  return true;
169
172
  }
170
173
  var EmailAlreadyTakenError = class extends Error {
@@ -240,11 +243,29 @@ var Auth = class {
240
243
  return data.user;
241
244
  });
242
245
  }
246
+ /**
247
+ * Create an email/password account.
248
+ *
249
+ * Industry-standard upgrade-first flow (matches Firebase
250
+ * `linkWithCredential`, Auth0 anonymous-merge, Supabase
251
+ * `linkIdentity`): when a refresh token is present in storage we
252
+ * try `/auth-service/upgrade` first — backend validates the
253
+ * refresh, checks the bound user is anonymous, and promotes in
254
+ * place preserving `user.id`. If the bound user is NOT anonymous
255
+ * (already identified, expired refresh, etc.), backend rejects
256
+ * with FORBIDDEN_NON_ANONYMOUS / INVALID_REFRESH and we fall back
257
+ * to a fresh /signup.
258
+ *
259
+ * Why we don't gate on the cached `this.user.isAnonymous`
260
+ * boolean: the in-memory user can be stale during cold start or
261
+ * fast-refresh races. The refresh token in storage is the
262
+ * authoritative signal — if it's there, the upgrade path is
263
+ * worth attempting; backend has the source of truth.
264
+ */
243
265
  signUp(email, password, opts) {
244
266
  return this.serialize(async () => {
245
267
  const refresh = this.hooks.storage.get(REFRESH_KEY);
246
- const isAnonymous = this.user?.isAnonymous === true;
247
- if (isAnonymous && refresh) {
268
+ if (refresh) {
248
269
  try {
249
270
  const data = await this.callAuth("/api/v1/auth-service/upgrade", {
250
271
  refreshToken: refresh,
@@ -258,11 +279,13 @@ var Auth = class {
258
279
  if (err instanceof AuthError && (err.code === "CONFLICT" || err.code === "EMAIL_ALREADY_TAKEN")) {
259
280
  throw new EmailAlreadyTakenError(err.message);
260
281
  }
261
- throw err;
282
+ if (err instanceof AuthError && (err.code === "FORBIDDEN_NON_ANONYMOUS" || err.code === "INVALID_REFRESH" || err.code === "UNAUTHORIZED" || err.code === "REFRESH_EXPIRED")) {
283
+ await this.wipeLocalIdentity();
284
+ } else {
285
+ throw err;
286
+ }
262
287
  }
263
288
  }
264
- const wasIdentified = this.user !== null && this.user.isAnonymous === false;
265
- if (wasIdentified) await this.wipeLocalIdentity();
266
289
  try {
267
290
  const data = await this.callAuth("/api/v1/auth-service/signup", {
268
291
  email,
@@ -727,7 +750,7 @@ function decodeJwtPayload(token) {
727
750
  }
728
751
 
729
752
  // src/index.ts
730
- var SDK_VERSION = "0.10.0";
753
+ var SDK_VERSION = "0.10.2";
731
754
  var DEFAULT_API_URL = "https://api.sendoracloud.com";
732
755
  var ANON_KEY = "anon_id";
733
756
  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.0",
3
+ "version": "0.10.2",
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",