@sendoracloud/sdk-react-native 0.8.0 → 0.10.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/dist/index.cjs CHANGED
@@ -355,6 +355,59 @@ var Auth = class {
355
355
  return parsed.data.user;
356
356
  });
357
357
  }
358
+ /**
359
+ * Verify an IdP-issued credential (authorization code OR id_token)
360
+ * and mint a Sendora session. Customer's app handles the IdP
361
+ * dance — typically via expo-auth-session / react-native-app-auth
362
+ * — then hands the result here. See web SDK for full provider
363
+ * matrix; mirrors that surface 1:1.
364
+ */
365
+ loginSocial(input) {
366
+ return this.serialize(async () => {
367
+ if (this.user !== null) await this.wipeLocalIdentity();
368
+ const res = await post(
369
+ { apiUrl: this.hooks.apiUrl, publicKey: this.hooks.publicKey, debug: this.hooks.debug },
370
+ "/api/v1/auth-service/login/social",
371
+ input
372
+ );
373
+ if (!res) throw new AuthError("NETWORK_ERROR", "Network request failed");
374
+ let parsed;
375
+ try {
376
+ parsed = await res.json();
377
+ } catch {
378
+ throw new AuthError("PARSE_ERROR", `HTTP ${res.status}`);
379
+ }
380
+ if (!res.ok || !parsed.success || !isAuthApiResponse(parsed.data)) {
381
+ throw new AuthError(
382
+ parsed.error?.code ?? "SOCIAL_LOGIN_FAILED",
383
+ parsed.error?.message ?? "Social login failed"
384
+ );
385
+ }
386
+ this.persist(parsed.data);
387
+ return parsed.data.user;
388
+ });
389
+ }
390
+ signInWithGoogle(code, redirectUri) {
391
+ return this.loginSocial({ provider: "google", code, redirectUri });
392
+ }
393
+ signInWithGitHub(code, redirectUri) {
394
+ return this.loginSocial({ provider: "github", code, redirectUri });
395
+ }
396
+ signInWithApple(input) {
397
+ return this.loginSocial({ provider: "apple", ...input });
398
+ }
399
+ signInWithMicrosoft(code, redirectUri) {
400
+ return this.loginSocial({ provider: "microsoft", code, redirectUri });
401
+ }
402
+ signInWithLinkedIn(code, redirectUri) {
403
+ return this.loginSocial({ provider: "linkedin", code, redirectUri });
404
+ }
405
+ signInWithFacebook(code, redirectUri) {
406
+ return this.loginSocial({ provider: "facebook", code, redirectUri });
407
+ }
408
+ signInWithDiscord(code, redirectUri) {
409
+ return this.loginSocial({ provider: "discord", code, redirectUri });
410
+ }
358
411
  /**
359
412
  * Exchange the MFA challenge token from `signIn` for a real session
360
413
  * by submitting a TOTP or recovery code from the user's authenticator.
@@ -391,6 +444,77 @@ var Auth = class {
391
444
  });
392
445
  }
393
446
  // ============================================================
447
+ // EMAIL OTP (6-digit cross-device code)
448
+ // ============================================================
449
+ async sendEmailOtp(email) {
450
+ const res = await post(
451
+ { apiUrl: this.hooks.apiUrl, publicKey: this.hooks.publicKey, debug: this.hooks.debug },
452
+ "/api/v1/auth-service/email-otp/request",
453
+ { email }
454
+ );
455
+ if (!res || !res.ok) {
456
+ throw new AuthError("EMAIL_OTP_REQUEST_FAILED", "Failed to send code");
457
+ }
458
+ }
459
+ verifyEmailOtp(email, code) {
460
+ return this.serialize(async () => {
461
+ if (this.user !== null) await this.wipeLocalIdentity();
462
+ const data = await this.callAuth("/api/v1/auth-service/email-otp/verify", { email, code });
463
+ this.persist(data);
464
+ return data.user;
465
+ });
466
+ }
467
+ // ============================================================
468
+ // PASSWORD RESET + EMAIL VERIFICATION
469
+ // ============================================================
470
+ /** Trigger password-reset email. Resolves on 204 even when address is unknown (anti-enumeration). */
471
+ async requestPasswordReset(email) {
472
+ const res = await post(
473
+ { apiUrl: this.hooks.apiUrl, publicKey: this.hooks.publicKey, debug: this.hooks.debug },
474
+ "/api/v1/auth-service/password/forgot",
475
+ { email }
476
+ );
477
+ if (!res || !res.ok) {
478
+ throw new AuthError("PASSWORD_RESET_REQUEST_FAILED", "Failed to send reset email");
479
+ }
480
+ }
481
+ /** Pair the reset-email token with the new password to complete the reset. */
482
+ async resetPassword(token, newPassword) {
483
+ const res = await post(
484
+ { apiUrl: this.hooks.apiUrl, publicKey: this.hooks.publicKey, debug: this.hooks.debug },
485
+ "/api/v1/auth-service/password/reset",
486
+ { token, newPassword }
487
+ );
488
+ if (!res || !res.ok) {
489
+ throw new AuthError("PASSWORD_RESET_FAILED", "Reset failed");
490
+ }
491
+ }
492
+ /** Verify the email-address token from the link Sendora sent on signup. */
493
+ async verifyEmail(token) {
494
+ const res = await post(
495
+ { apiUrl: this.hooks.apiUrl, publicKey: this.hooks.publicKey, debug: this.hooks.debug },
496
+ "/api/v1/auth-service/email/verify",
497
+ { token }
498
+ );
499
+ if (!res || !res.ok) {
500
+ throw new AuthError("EMAIL_VERIFICATION_FAILED", "Verification failed");
501
+ }
502
+ }
503
+ /** Re-send the email-verification email for the currently-signed-in user. */
504
+ async sendVerificationEmail() {
505
+ const headers = this.bearerHeaders();
506
+ if (!headers) throw new AuthError("UNAUTHORIZED", "Sign in before requesting a verification email");
507
+ const res = await post(
508
+ { apiUrl: this.hooks.apiUrl, publicKey: this.hooks.publicKey, debug: this.hooks.debug },
509
+ "/api/v1/auth-service/email/verify/resend",
510
+ {},
511
+ headers
512
+ );
513
+ if (!res || !res.ok) {
514
+ throw new AuthError("VERIFICATION_RESEND_FAILED", "Failed to send verification email");
515
+ }
516
+ }
517
+ // ============================================================
394
518
  // TOTP MFA — enrollment management (Bearer-authenticated)
395
519
  // ============================================================
396
520
  async enrollMfa() {
@@ -643,7 +767,7 @@ function decodeJwtPayload(token) {
643
767
  }
644
768
 
645
769
  // src/index.ts
646
- var SDK_VERSION = "0.4.0";
770
+ var SDK_VERSION = "0.10.0";
647
771
  var DEFAULT_API_URL = "https://api.sendoracloud.com";
648
772
  var ANON_KEY = "anon_id";
649
773
  var USER_ID_KEY = "user_id";
@@ -692,7 +816,7 @@ var SendoraSDK = class {
692
816
  }
693
817
  /** Initialize the SDK. Must be awaited at app startup before identify/track. */
694
818
  async init(config) {
695
- const rawKey = String(config.publicKey ?? "").trim();
819
+ const rawKey = String(config.apiKey ?? config.publicKey ?? "").trim();
696
820
  if (/^sk_/i.test(rawKey) || /sk_(prod|staging|dev|live|test)_/i.test(rawKey)) {
697
821
  throw new Error(
698
822
  "[sendoracloud] Secret key (sk_*) detected. The client SDK must only receive public keys (pk_*). Secret keys belong on your server."
@@ -737,6 +861,10 @@ var SendoraSDK = class {
737
861
  environment: "prod",
738
862
  consentedByDefault: true,
739
863
  ...config,
864
+ // Store rawKey on BOTH fields so any downstream reader that
865
+ // grabbed the legacy `publicKey` field keeps working, and
866
+ // future code reading `apiKey` does too.
867
+ apiKey: rawKey,
740
868
  publicKey: rawKey
741
869
  };
742
870
  this.auth = new Auth({
package/dist/index.d.cts CHANGED
@@ -137,6 +137,39 @@ declare class Auth {
137
137
  mfaChallengeToken: string;
138
138
  userId: string;
139
139
  }>;
140
+ /**
141
+ * Verify an IdP-issued credential (authorization code OR id_token)
142
+ * and mint a Sendora session. Customer's app handles the IdP
143
+ * dance — typically via expo-auth-session / react-native-app-auth
144
+ * — then hands the result here. See web SDK for full provider
145
+ * matrix; mirrors that surface 1:1.
146
+ */
147
+ loginSocial(input: {
148
+ provider: "google" | "github" | "apple" | "microsoft" | "linkedin" | "facebook" | "twitter" | "discord";
149
+ code?: string;
150
+ idToken?: string;
151
+ redirectUri?: string;
152
+ codeVerifier?: string;
153
+ appleName?: {
154
+ firstName?: string;
155
+ lastName?: string;
156
+ };
157
+ }): Promise<AuthUser>;
158
+ signInWithGoogle(code: string, redirectUri: string): Promise<AuthUser>;
159
+ signInWithGitHub(code: string, redirectUri: string): Promise<AuthUser>;
160
+ signInWithApple(input: {
161
+ idToken?: string;
162
+ code?: string;
163
+ redirectUri?: string;
164
+ appleName?: {
165
+ firstName?: string;
166
+ lastName?: string;
167
+ };
168
+ }): Promise<AuthUser>;
169
+ signInWithMicrosoft(code: string, redirectUri: string): Promise<AuthUser>;
170
+ signInWithLinkedIn(code: string, redirectUri: string): Promise<AuthUser>;
171
+ signInWithFacebook(code: string, redirectUri: string): Promise<AuthUser>;
172
+ signInWithDiscord(code: string, redirectUri: string): Promise<AuthUser>;
140
173
  /**
141
174
  * Exchange the MFA challenge token from `signIn` for a real session
142
175
  * by submitting a TOTP or recovery code from the user's authenticator.
@@ -144,6 +177,16 @@ declare class Auth {
144
177
  challengeMfa(mfaChallengeToken: string, code: string): Promise<AuthUser>;
145
178
  sendMagicLink(email: string): Promise<void>;
146
179
  verifyMagicLink(token: string): Promise<AuthUser>;
180
+ sendEmailOtp(email: string): Promise<void>;
181
+ verifyEmailOtp(email: string, code: string): Promise<AuthUser>;
182
+ /** Trigger password-reset email. Resolves on 204 even when address is unknown (anti-enumeration). */
183
+ requestPasswordReset(email: string): Promise<void>;
184
+ /** Pair the reset-email token with the new password to complete the reset. */
185
+ resetPassword(token: string, newPassword: string): Promise<void>;
186
+ /** Verify the email-address token from the link Sendora sent on signup. */
187
+ verifyEmail(token: string): Promise<void>;
188
+ /** Re-send the email-verification email for the currently-signed-in user. */
189
+ sendVerificationEmail(): Promise<void>;
147
190
  enrollMfa(): Promise<{
148
191
  secret: string;
149
192
  otpauthUrl: string;
@@ -198,9 +241,23 @@ declare class Auth {
198
241
  }
199
242
 
200
243
  interface SendoraConfig {
201
- /** Public API key (pk_live_... or pk_test_...). Secret keys are refused. */
202
- publicKey: string;
203
- /** Org UUID defaults to the key's bound org, but can be overridden for test harnesses. */
244
+ /**
245
+ * Public API key (`pk_live_…` or `pk_test_…`). Secret keys are refused.
246
+ * Canonical name as of 0.10.0 pre-0.10.0 callers using `publicKey`
247
+ * still work via the deprecated alias below.
248
+ */
249
+ apiKey?: string;
250
+ /**
251
+ * @deprecated Use `apiKey` instead (0.10.0+ canonical name across
252
+ * sdk-web / sdk-ios / sdk-android). Kept for back-compat — when
253
+ * both are set, `apiKey` wins.
254
+ */
255
+ publicKey?: string;
256
+ /**
257
+ * @deprecated 0.10.0 — backend now derives org from the API key
258
+ * row server-side. Kept for test harnesses that need to override
259
+ * the bound org explicitly.
260
+ */
204
261
  orgId?: string;
205
262
  /** Backend base URL. Defaults to Sendora's production API. */
206
263
  apiUrl?: string;
package/dist/index.d.ts CHANGED
@@ -137,6 +137,39 @@ declare class Auth {
137
137
  mfaChallengeToken: string;
138
138
  userId: string;
139
139
  }>;
140
+ /**
141
+ * Verify an IdP-issued credential (authorization code OR id_token)
142
+ * and mint a Sendora session. Customer's app handles the IdP
143
+ * dance — typically via expo-auth-session / react-native-app-auth
144
+ * — then hands the result here. See web SDK for full provider
145
+ * matrix; mirrors that surface 1:1.
146
+ */
147
+ loginSocial(input: {
148
+ provider: "google" | "github" | "apple" | "microsoft" | "linkedin" | "facebook" | "twitter" | "discord";
149
+ code?: string;
150
+ idToken?: string;
151
+ redirectUri?: string;
152
+ codeVerifier?: string;
153
+ appleName?: {
154
+ firstName?: string;
155
+ lastName?: string;
156
+ };
157
+ }): Promise<AuthUser>;
158
+ signInWithGoogle(code: string, redirectUri: string): Promise<AuthUser>;
159
+ signInWithGitHub(code: string, redirectUri: string): Promise<AuthUser>;
160
+ signInWithApple(input: {
161
+ idToken?: string;
162
+ code?: string;
163
+ redirectUri?: string;
164
+ appleName?: {
165
+ firstName?: string;
166
+ lastName?: string;
167
+ };
168
+ }): Promise<AuthUser>;
169
+ signInWithMicrosoft(code: string, redirectUri: string): Promise<AuthUser>;
170
+ signInWithLinkedIn(code: string, redirectUri: string): Promise<AuthUser>;
171
+ signInWithFacebook(code: string, redirectUri: string): Promise<AuthUser>;
172
+ signInWithDiscord(code: string, redirectUri: string): Promise<AuthUser>;
140
173
  /**
141
174
  * Exchange the MFA challenge token from `signIn` for a real session
142
175
  * by submitting a TOTP or recovery code from the user's authenticator.
@@ -144,6 +177,16 @@ declare class Auth {
144
177
  challengeMfa(mfaChallengeToken: string, code: string): Promise<AuthUser>;
145
178
  sendMagicLink(email: string): Promise<void>;
146
179
  verifyMagicLink(token: string): Promise<AuthUser>;
180
+ sendEmailOtp(email: string): Promise<void>;
181
+ verifyEmailOtp(email: string, code: string): Promise<AuthUser>;
182
+ /** Trigger password-reset email. Resolves on 204 even when address is unknown (anti-enumeration). */
183
+ requestPasswordReset(email: string): Promise<void>;
184
+ /** Pair the reset-email token with the new password to complete the reset. */
185
+ resetPassword(token: string, newPassword: string): Promise<void>;
186
+ /** Verify the email-address token from the link Sendora sent on signup. */
187
+ verifyEmail(token: string): Promise<void>;
188
+ /** Re-send the email-verification email for the currently-signed-in user. */
189
+ sendVerificationEmail(): Promise<void>;
147
190
  enrollMfa(): Promise<{
148
191
  secret: string;
149
192
  otpauthUrl: string;
@@ -198,9 +241,23 @@ declare class Auth {
198
241
  }
199
242
 
200
243
  interface SendoraConfig {
201
- /** Public API key (pk_live_... or pk_test_...). Secret keys are refused. */
202
- publicKey: string;
203
- /** Org UUID defaults to the key's bound org, but can be overridden for test harnesses. */
244
+ /**
245
+ * Public API key (`pk_live_…` or `pk_test_…`). Secret keys are refused.
246
+ * Canonical name as of 0.10.0 pre-0.10.0 callers using `publicKey`
247
+ * still work via the deprecated alias below.
248
+ */
249
+ apiKey?: string;
250
+ /**
251
+ * @deprecated Use `apiKey` instead (0.10.0+ canonical name across
252
+ * sdk-web / sdk-ios / sdk-android). Kept for back-compat — when
253
+ * both are set, `apiKey` wins.
254
+ */
255
+ publicKey?: string;
256
+ /**
257
+ * @deprecated 0.10.0 — backend now derives org from the API key
258
+ * row server-side. Kept for test harnesses that need to override
259
+ * the bound org explicitly.
260
+ */
204
261
  orgId?: string;
205
262
  /** Backend base URL. Defaults to Sendora's production API. */
206
263
  apiUrl?: string;
package/dist/index.js CHANGED
@@ -315,6 +315,59 @@ var Auth = class {
315
315
  return parsed.data.user;
316
316
  });
317
317
  }
318
+ /**
319
+ * Verify an IdP-issued credential (authorization code OR id_token)
320
+ * and mint a Sendora session. Customer's app handles the IdP
321
+ * dance — typically via expo-auth-session / react-native-app-auth
322
+ * — then hands the result here. See web SDK for full provider
323
+ * matrix; mirrors that surface 1:1.
324
+ */
325
+ loginSocial(input) {
326
+ return this.serialize(async () => {
327
+ if (this.user !== null) await this.wipeLocalIdentity();
328
+ const res = await post(
329
+ { apiUrl: this.hooks.apiUrl, publicKey: this.hooks.publicKey, debug: this.hooks.debug },
330
+ "/api/v1/auth-service/login/social",
331
+ input
332
+ );
333
+ if (!res) throw new AuthError("NETWORK_ERROR", "Network request failed");
334
+ let parsed;
335
+ try {
336
+ parsed = await res.json();
337
+ } catch {
338
+ throw new AuthError("PARSE_ERROR", `HTTP ${res.status}`);
339
+ }
340
+ if (!res.ok || !parsed.success || !isAuthApiResponse(parsed.data)) {
341
+ throw new AuthError(
342
+ parsed.error?.code ?? "SOCIAL_LOGIN_FAILED",
343
+ parsed.error?.message ?? "Social login failed"
344
+ );
345
+ }
346
+ this.persist(parsed.data);
347
+ return parsed.data.user;
348
+ });
349
+ }
350
+ signInWithGoogle(code, redirectUri) {
351
+ return this.loginSocial({ provider: "google", code, redirectUri });
352
+ }
353
+ signInWithGitHub(code, redirectUri) {
354
+ return this.loginSocial({ provider: "github", code, redirectUri });
355
+ }
356
+ signInWithApple(input) {
357
+ return this.loginSocial({ provider: "apple", ...input });
358
+ }
359
+ signInWithMicrosoft(code, redirectUri) {
360
+ return this.loginSocial({ provider: "microsoft", code, redirectUri });
361
+ }
362
+ signInWithLinkedIn(code, redirectUri) {
363
+ return this.loginSocial({ provider: "linkedin", code, redirectUri });
364
+ }
365
+ signInWithFacebook(code, redirectUri) {
366
+ return this.loginSocial({ provider: "facebook", code, redirectUri });
367
+ }
368
+ signInWithDiscord(code, redirectUri) {
369
+ return this.loginSocial({ provider: "discord", code, redirectUri });
370
+ }
318
371
  /**
319
372
  * Exchange the MFA challenge token from `signIn` for a real session
320
373
  * by submitting a TOTP or recovery code from the user's authenticator.
@@ -351,6 +404,77 @@ var Auth = class {
351
404
  });
352
405
  }
353
406
  // ============================================================
407
+ // EMAIL OTP (6-digit cross-device code)
408
+ // ============================================================
409
+ async sendEmailOtp(email) {
410
+ const res = await post(
411
+ { apiUrl: this.hooks.apiUrl, publicKey: this.hooks.publicKey, debug: this.hooks.debug },
412
+ "/api/v1/auth-service/email-otp/request",
413
+ { email }
414
+ );
415
+ if (!res || !res.ok) {
416
+ throw new AuthError("EMAIL_OTP_REQUEST_FAILED", "Failed to send code");
417
+ }
418
+ }
419
+ verifyEmailOtp(email, code) {
420
+ return this.serialize(async () => {
421
+ if (this.user !== null) await this.wipeLocalIdentity();
422
+ const data = await this.callAuth("/api/v1/auth-service/email-otp/verify", { email, code });
423
+ this.persist(data);
424
+ return data.user;
425
+ });
426
+ }
427
+ // ============================================================
428
+ // PASSWORD RESET + EMAIL VERIFICATION
429
+ // ============================================================
430
+ /** Trigger password-reset email. Resolves on 204 even when address is unknown (anti-enumeration). */
431
+ async requestPasswordReset(email) {
432
+ const res = await post(
433
+ { apiUrl: this.hooks.apiUrl, publicKey: this.hooks.publicKey, debug: this.hooks.debug },
434
+ "/api/v1/auth-service/password/forgot",
435
+ { email }
436
+ );
437
+ if (!res || !res.ok) {
438
+ throw new AuthError("PASSWORD_RESET_REQUEST_FAILED", "Failed to send reset email");
439
+ }
440
+ }
441
+ /** Pair the reset-email token with the new password to complete the reset. */
442
+ async resetPassword(token, newPassword) {
443
+ const res = await post(
444
+ { apiUrl: this.hooks.apiUrl, publicKey: this.hooks.publicKey, debug: this.hooks.debug },
445
+ "/api/v1/auth-service/password/reset",
446
+ { token, newPassword }
447
+ );
448
+ if (!res || !res.ok) {
449
+ throw new AuthError("PASSWORD_RESET_FAILED", "Reset failed");
450
+ }
451
+ }
452
+ /** Verify the email-address token from the link Sendora sent on signup. */
453
+ async verifyEmail(token) {
454
+ const res = await post(
455
+ { apiUrl: this.hooks.apiUrl, publicKey: this.hooks.publicKey, debug: this.hooks.debug },
456
+ "/api/v1/auth-service/email/verify",
457
+ { token }
458
+ );
459
+ if (!res || !res.ok) {
460
+ throw new AuthError("EMAIL_VERIFICATION_FAILED", "Verification failed");
461
+ }
462
+ }
463
+ /** Re-send the email-verification email for the currently-signed-in user. */
464
+ async sendVerificationEmail() {
465
+ const headers = this.bearerHeaders();
466
+ if (!headers) throw new AuthError("UNAUTHORIZED", "Sign in before requesting a verification email");
467
+ const res = await post(
468
+ { apiUrl: this.hooks.apiUrl, publicKey: this.hooks.publicKey, debug: this.hooks.debug },
469
+ "/api/v1/auth-service/email/verify/resend",
470
+ {},
471
+ headers
472
+ );
473
+ if (!res || !res.ok) {
474
+ throw new AuthError("VERIFICATION_RESEND_FAILED", "Failed to send verification email");
475
+ }
476
+ }
477
+ // ============================================================
354
478
  // TOTP MFA — enrollment management (Bearer-authenticated)
355
479
  // ============================================================
356
480
  async enrollMfa() {
@@ -603,7 +727,7 @@ function decodeJwtPayload(token) {
603
727
  }
604
728
 
605
729
  // src/index.ts
606
- var SDK_VERSION = "0.4.0";
730
+ var SDK_VERSION = "0.10.0";
607
731
  var DEFAULT_API_URL = "https://api.sendoracloud.com";
608
732
  var ANON_KEY = "anon_id";
609
733
  var USER_ID_KEY = "user_id";
@@ -652,7 +776,7 @@ var SendoraSDK = class {
652
776
  }
653
777
  /** Initialize the SDK. Must be awaited at app startup before identify/track. */
654
778
  async init(config) {
655
- const rawKey = String(config.publicKey ?? "").trim();
779
+ const rawKey = String(config.apiKey ?? config.publicKey ?? "").trim();
656
780
  if (/^sk_/i.test(rawKey) || /sk_(prod|staging|dev|live|test)_/i.test(rawKey)) {
657
781
  throw new Error(
658
782
  "[sendoracloud] Secret key (sk_*) detected. The client SDK must only receive public keys (pk_*). Secret keys belong on your server."
@@ -697,6 +821,10 @@ var SendoraSDK = class {
697
821
  environment: "prod",
698
822
  consentedByDefault: true,
699
823
  ...config,
824
+ // Store rawKey on BOTH fields so any downstream reader that
825
+ // grabbed the legacy `publicKey` field keeps working, and
826
+ // future code reading `apiKey` does too.
827
+ apiKey: rawKey,
700
828
  publicKey: rawKey
701
829
  };
702
830
  this.auth = new Auth({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sendoracloud/sdk-react-native",
3
- "version": "0.8.0",
3
+ "version": "0.10.0",
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",