@layers/amba-react-native 3.0.2 → 4.0.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
@@ -1,13 +1,39 @@
1
1
 
2
2
  //#region src/async-storage-adapter.ts
3
- let cachedStorage = null;
3
+ let cachedStorage = void 0;
4
+ /**
5
+ * Late-imports `@react-native-async-storage/async-storage` and returns
6
+ * it ONLY if the resolved module exposes the three methods we use
7
+ * (`getItem` / `setItem` / `removeItem`). Otherwise returns null and
8
+ * the persistence layer falls back to memory-only.
9
+ *
10
+ * Why the shape check: pnpm with auto-install-peers (default since
11
+ * pnpm 7) installs declared peer deps even in non-RN scratch
12
+ * directories (e.g., this SDK's docs-smoke harness). The installed
13
+ * package's Node entry point doesn't expose AsyncStorage's methods
14
+ * — it's a React Native native module — so the `await import(...)`
15
+ * succeeds (no catch fires) but the returned object has no
16
+ * `getItem`. Calling it throws `TypeError: storage.getItem is not a
17
+ * function` from inside `read()` (verified at PR #246 docs-react-native
18
+ * / docs-expo run on 547a1ff4). Checking method presence here turns
19
+ * that into the same fall-through as "peer dep not installed at all."
20
+ */
21
+ function isAsyncStorageLike(mod) {
22
+ return typeof mod === "object" && mod !== null && typeof mod.getItem === "function" && typeof mod.setItem === "function" && typeof mod.removeItem === "function";
23
+ }
4
24
  async function getStorage() {
5
- if (cachedStorage !== null) return cachedStorage;
25
+ if (cachedStorage !== void 0) return cachedStorage;
6
26
  try {
7
27
  const mod = await import("@react-native-async-storage/async-storage");
8
- cachedStorage = mod.default ?? mod;
28
+ const candidate = mod.default ?? mod;
29
+ if (!isAsyncStorageLike(candidate)) {
30
+ cachedStorage = null;
31
+ return null;
32
+ }
33
+ cachedStorage = candidate;
9
34
  return cachedStorage;
10
35
  } catch {
36
+ cachedStorage = null;
11
37
  return null;
12
38
  }
13
39
  }
@@ -325,7 +351,7 @@ function freshSeed() {
325
351
  //#endregion
326
352
  //#region src/index.ts
327
353
  /** SDK semver — burned in at build time. Bump alongside package.json. */
328
- const SDK_VERSION = "3.0.0";
354
+ const SDK_VERSION = "4.0.0";
329
355
  let state = null;
330
356
  function getState() {
331
357
  if (!state) throw new AmbaApiError("NOT_INITIALIZED", "@layers/amba-react-native: SDK not configured. Call Amba.configure({ projectId, clientKey }) at app start.");
@@ -377,15 +403,34 @@ function requireEmail(email) {
377
403
  function requirePassword(password) {
378
404
  if (password.length < 8) throw new AmbaApiError("VALIDATION_ERROR", "password must be at least 8 characters");
379
405
  }
406
+ function requireOtpCode(code) {
407
+ if (code.length !== 6 || !/^[0-9]{6}$/.test(code)) throw new AmbaApiError("VALIDATION_ERROR", "code must be 6 digits");
408
+ }
409
+ const E164_RE = /^\+[1-9][0-9]{6,14}$/;
410
+ function requireE164Phone(phone) {
411
+ if (!E164_RE.test(phone)) throw new AmbaApiError("VALIDATION_ERROR", "phone must be E.164 (starts with +, 8–15 digits)");
412
+ }
380
413
  const P = {
381
414
  authAnonymous: "/v1/client/auth/anonymous",
382
415
  authEmailLogin: "/v1/client/auth/email/login",
383
416
  authEmailSignup: "/v1/client/auth/email/signup",
417
+ authEmailOtpRequest: "/v1/client/auth/email-otp/request",
418
+ authSmsOtpRequest: "/v1/client/auth/sms-otp/request",
419
+ authSmsOtpVerify: "/v1/client/auth/sms-otp/verify",
420
+ authEmailOtpVerify: "/v1/client/auth/email-otp/verify",
384
421
  authSocial: "/v1/client/auth/social",
422
+ authMagicLinkRequest: "/v1/client/auth/magic-link/request",
423
+ authMagicLinkVerify: "/v1/client/auth/magic-link/verify",
424
+ authLink: "/v1/client/auth/link",
385
425
  authRefresh: "/v1/client/auth/refresh",
386
426
  authLogout: "/v1/client/auth/logout",
387
427
  authMe: "/v1/client/auth/me",
388
428
  events: "/v1/client/events",
429
+ users: "/v1/client/users",
430
+ usersMe: "/v1/client/users/me",
431
+ sessions: "/v1/client/sessions",
432
+ sync: "/v1/client/sync",
433
+ leagues: "/v1/client/leagues",
389
434
  collections: "/v1/client/collections",
390
435
  storage: "/v1/client/media",
391
436
  storagePresign: "/v1/client/media/presign",
@@ -400,6 +445,7 @@ const P = {
400
445
  aiAnthropic: "/v1/client/ai/anthropic/messages",
401
446
  config: "/v1/client/config",
402
447
  flags: "/v1/client/flags",
448
+ diagnosticsPing: "/v1/client/diagnostics/ping",
403
449
  achievements: "/v1/client/achievements",
404
450
  challenges: "/v1/client/challenges",
405
451
  catalog: "/v1/client/catalog",
@@ -418,7 +464,7 @@ const P = {
418
464
  roles: "/v1/client/roles",
419
465
  referrals: "/v1/client/referrals",
420
466
  content: "/v1/client/content",
421
- deepLinks: "/v1/client/deeplinks",
467
+ deepLinks: "/v1/client/deep-links",
422
468
  onboarding: "/v1/client/onboarding"
423
469
  };
424
470
  function encodePathSegment(s) {
@@ -468,14 +514,16 @@ const Amba = {
468
514
  setDebug: (enabled) => {
469
515
  getState().debug = enabled;
470
516
  },
471
- events: { track: async (event, properties) => {
517
+ events: { track: async (event, properties, options) => {
472
518
  const s = getState();
473
- await s.http.post(P.events, {
474
- name: event,
519
+ const body = {
520
+ event,
475
521
  properties: properties ?? void 0,
476
522
  anonymous_id: s.identity.getAnonymousId(),
477
523
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
478
- });
524
+ };
525
+ if (options?.telemetry) body["telemetry"] = true;
526
+ await s.http.post(P.events, body);
479
527
  } },
480
528
  auth: {
481
529
  signInAnonymously: async () => {
@@ -498,13 +546,65 @@ const Amba = {
498
546
  password
499
547
  });
500
548
  },
501
- signInWithSocial: async (provider, idToken) => {
549
+ signInWithApple: async (identityToken) => {
550
+ if (identityToken.trim() === "") throw new AmbaApiError("VALIDATION_ERROR", "identity_token must not be empty");
551
+ return postAuth(getState(), P.authSocial, {
552
+ provider: "apple",
553
+ id_token: identityToken
554
+ });
555
+ },
556
+ signInWithGoogle: async (idToken) => {
502
557
  if (idToken.trim() === "") throw new AmbaApiError("VALIDATION_ERROR", "id_token must not be empty");
503
558
  return postAuth(getState(), P.authSocial, {
504
- provider,
559
+ provider: "google",
505
560
  id_token: idToken
506
561
  });
507
562
  },
563
+ requestEmailOtp: async (email) => {
564
+ requireEmail(email);
565
+ await getState().http.post(P.authEmailOtpRequest, { email });
566
+ },
567
+ verifyEmailOtp: async (email, code) => {
568
+ requireEmail(email);
569
+ requireOtpCode(code);
570
+ return postAuth(getState(), P.authEmailOtpVerify, {
571
+ email,
572
+ code
573
+ });
574
+ },
575
+ requestSmsOtp: async (phone) => {
576
+ requireE164Phone(phone);
577
+ await getState().http.post(P.authSmsOtpRequest, { phone });
578
+ },
579
+ verifySmsOtp: async (phone, code) => {
580
+ requireE164Phone(phone);
581
+ requireOtpCode(code);
582
+ return postAuth(getState(), P.authSmsOtpVerify, {
583
+ phone,
584
+ code
585
+ });
586
+ },
587
+ requestMagicLink: async (email) => {
588
+ requireEmail(email);
589
+ await getState().http.post(P.authMagicLinkRequest, { email });
590
+ },
591
+ verifyMagicLink: async (token) => {
592
+ if (token.trim() === "") throw new AmbaApiError("VALIDATION_ERROR", "token must not be empty");
593
+ return postAuth(getState(), P.authMagicLinkVerify, { token });
594
+ },
595
+ linkAccount: async (provider, credential) => {
596
+ const providerTrimmed = provider?.trim() ?? "";
597
+ if (providerTrimmed === "") throw new AmbaApiError("VALIDATION_ERROR", "provider must not be empty");
598
+ if (typeof credential !== "string" || credential.trim() === "") throw new AmbaApiError("VALIDATION_ERROR", "credential must not be empty");
599
+ const s = getState();
600
+ const sessionToken = s.identity.getSessionToken();
601
+ if (!sessionToken) throw new AmbaApiError("UNAUTHORIZED", "no active session — call signIn* first");
602
+ return postAuth(s, P.authLink, {
603
+ provider: providerTrimmed,
604
+ token: credential,
605
+ session_token: sessionToken
606
+ });
607
+ },
508
608
  signOut: async (rotateAnonymousId = false) => {
509
609
  const s = getState();
510
610
  if (s.identity.getSessionToken()) try {
@@ -541,6 +641,41 @@ const Amba = {
541
641
  };
542
642
  }
543
643
  },
644
+ users: {
645
+ get: async (userId) => {
646
+ const s = getState();
647
+ if (!s.identity.getSessionToken()) throw new AmbaApiError("UNAUTHORIZED", "no active session — call signIn* first");
648
+ const path = userId === void 0 || userId === null || userId === "" ? P.usersMe : `${P.users}/${encodePathSegment(userId)}`;
649
+ return (await s.http.get(path)).data;
650
+ },
651
+ update: ((arg1, arg2) => {
652
+ const s = getState();
653
+ if (!s.identity.getSessionToken()) throw new AmbaApiError("UNAUTHORIZED", "no active session — call signIn* first");
654
+ const isById = typeof arg1 === "string";
655
+ const patch = isById ? arg2 ?? {} : arg1;
656
+ const path = isById ? `${P.users}/${encodePathSegment(arg1)}` : `${P.users}/me`;
657
+ return s.http.patch(path, patch).then((env) => env.data);
658
+ })
659
+ },
660
+ sessions: {
661
+ list: async () => {
662
+ return (await getState().http.get(P.sessions)).data;
663
+ },
664
+ revoke: async (sessionId) => {
665
+ await getState().http.post(`${P.sessions}/${encodePathSegment(sessionId)}/end`, {});
666
+ }
667
+ },
668
+ sync: {
669
+ pushChanges: async (changes) => {
670
+ return (await getState().http.post(P.sync, { changes })).data;
671
+ },
672
+ pullChanges: async (since) => {
673
+ if (!since.entity_type || since.entity_type.trim() === "") throw new AmbaApiError("VALIDATION_ERROR", "pullChanges requires a non-empty entity_type");
674
+ const query = { entity_type: since.entity_type };
675
+ if (since.checkpoint_token !== void 0) query["checkpoint_token"] = since.checkpoint_token;
676
+ return (await getState().http.get(P.sync, query)).data;
677
+ }
678
+ },
544
679
  collections: {
545
680
  find: async (name, options = {}) => {
546
681
  return getState().http.post(`${P.collections}/${encodePathSegment(name)}/find`, options);
@@ -548,6 +683,20 @@ const Amba = {
548
683
  findOne: async (name, id) => {
549
684
  return (await getState().http.get(`${P.collections}/${encodePathSegment(name)}/${encodePathSegment(id)}`)).data;
550
685
  },
686
+ findNearest: async (name, vector, k, where) => {
687
+ const s = getState();
688
+ const body = {
689
+ to_vector: vector,
690
+ k
691
+ };
692
+ if (where !== void 0) body.where = where;
693
+ return (await s.http.post(`${P.collections}/${encodePathSegment(name)}/find-nearest`, body)).data;
694
+ },
695
+ count: async (name, where) => {
696
+ const s = getState();
697
+ const query = where !== void 0 ? { query: JSON.stringify({ where }) } : void 0;
698
+ return (await s.http.get(`${P.collections}/${encodePathSegment(name)}/count`, query)).data.count;
699
+ },
551
700
  insert: async (name, row) => {
552
701
  return (await getState().http.post(`${P.collections}/${encodePathSegment(name)}`, row)).data;
553
702
  },
@@ -636,6 +785,33 @@ const Amba = {
636
785
  upload_id: uploadId,
637
786
  asset_id: assetId
638
787
  })).data;
788
+ },
789
+ list: async (prefix) => {
790
+ const env = await getState().http.get(P.storage, prefix !== void 0 ? { prefix } : void 0);
791
+ return {
792
+ data: env.data ?? [],
793
+ next_cursor: env.next_cursor ?? null
794
+ };
795
+ },
796
+ delete: async (assetId) => {
797
+ await getState().http.delete(`${P.storage}/${encodePathSegment(assetId)}`);
798
+ },
799
+ download: async (assetId) => {
800
+ const assetEnv = await getState().http.get(`${P.storage}/${encodePathSegment(assetId)}`);
801
+ const cdnUrl = assetEnv.data.url ?? assetEnv.data.storage_url ?? "";
802
+ if (!cdnUrl) throw new AmbaApiError("NOT_FOUND", "asset has no resolvable CDN url");
803
+ let response;
804
+ try {
805
+ response = await (globalThis.fetch ?? fetch)(cdnUrl, { method: "GET" });
806
+ } catch (err) {
807
+ throw new AmbaApiError("NETWORK_ERROR", `Network error: ${err instanceof Error ? err.message : String(err)}`, err);
808
+ }
809
+ if (!response.ok) {
810
+ const text = await response.text();
811
+ const ct = response.headers.get("content-type") ?? "";
812
+ throw mapHttpError(response, text, ct.includes("application/json"));
813
+ }
814
+ return response.blob();
639
815
  }
640
816
  },
641
817
  push: {
@@ -706,8 +882,21 @@ const Amba = {
706
882
  values: parsed.data ?? {}
707
883
  };
708
884
  } },
709
- flags: { fetch: async () => {
710
- return (await getState().http.get(P.flags)).data;
885
+ flags: {
886
+ fetch: async () => {
887
+ return (await getState().http.get(P.flags)).data;
888
+ },
889
+ get: async (key) => {
890
+ try {
891
+ return (await getState().http.get(`${P.flags}/${encodePathSegment(key)}`)).data;
892
+ } catch (err) {
893
+ if (err instanceof AmbaApiError && err.code === "NOT_FOUND") return null;
894
+ throw err;
895
+ }
896
+ }
897
+ },
898
+ diagnostics: { ping: async () => {
899
+ return await getState().http.post(P.diagnosticsPing, {});
711
900
  } },
712
901
  achievements: {
713
902
  getAll: async () => {
@@ -736,7 +925,7 @@ const Amba = {
736
925
  return (await getState().http.get(P.currencies)).data;
737
926
  },
738
927
  getTransactions: async (currencyKey) => {
739
- return (await getState().http.get(`${P.currencies}/${encodePathSegment(currencyKey)}/transactions`)).data;
928
+ return (await getState().http.get(`${P.currencies}/transactions`, { currency_code: currencyKey })).data;
740
929
  }
741
930
  },
742
931
  inventory: {
@@ -750,7 +939,8 @@ const Amba = {
750
939
  return (await getState().http.post(`${P.inventory}/purchase`, request)).data;
751
940
  },
752
941
  consume: async (request) => {
753
- return (await getState().http.post(`${P.inventory}/consume`, request)).data;
942
+ const { item_id,...body } = request;
943
+ return (await getState().http.post(`${P.inventory}/${encodePathSegment(item_id)}/consume`, body)).data;
754
944
  }
755
945
  },
756
946
  leaderboards: {
@@ -764,6 +954,14 @@ const Amba = {
764
954
  return (await getState().http.get(`${P.leaderboards}/${encodePathSegment(key)}/me`)).data;
765
955
  }
766
956
  },
957
+ leagues: {
958
+ me: async () => {
959
+ return (await getState().http.get(`${P.leagues}/me`)).data;
960
+ },
961
+ cohort: async () => {
962
+ return (await getState().http.get(`${P.leagues}/me/cohort`)).data;
963
+ }
964
+ },
767
965
  stores: {
768
966
  list: async () => {
769
967
  return (await getState().http.get(P.stores)).data;
@@ -774,7 +972,7 @@ const Amba = {
774
972
  purchase: async (storeKey, purchaseOptionId, receipt) => {
775
973
  return (await getState().http.post(`${P.stores}/${encodePathSegment(storeKey)}/purchase`, {
776
974
  purchase_option_id: purchaseOptionId,
777
- receipt
975
+ receipt: receipt ?? {}
778
976
  })).data;
779
977
  }
780
978
  },
@@ -808,6 +1006,15 @@ const Amba = {
808
1006
  getFriends: async () => {
809
1007
  return (await getState().http.get(P.friends, { state: "accepted" })).data;
810
1008
  },
1009
+ sendRequest: async (userId) => {
1010
+ return (await getState().http.post(`${P.friends}/request`, { user_id: userId })).data;
1011
+ },
1012
+ acceptRequest: async (friendshipId) => {
1013
+ return (await getState().http.post(`${P.friends}/${encodePathSegment(friendshipId)}/accept`, {})).data;
1014
+ },
1015
+ declineRequest: async (friendshipId) => {
1016
+ await getState().http.post(`${P.friends}/${encodePathSegment(friendshipId)}/reject`, {});
1017
+ },
811
1018
  blockUser: async (userId) => {
812
1019
  return (await getState().http.post(`${P.friends}/block`, { user_id: userId })).data;
813
1020
  },
@@ -816,6 +1023,9 @@ const Amba = {
816
1023
  },
817
1024
  removeBlock: async (friendshipId) => {
818
1025
  await getState().http.delete(`${P.friends}/${encodePathSegment(friendshipId)}`);
1026
+ },
1027
+ removeFriend: async (userId) => {
1028
+ await getState().http.delete(`${P.friends}/by-user/${encodePathSegment(userId)}`);
819
1029
  }
820
1030
  },
821
1031
  groups: {
@@ -848,10 +1058,7 @@ const Amba = {
848
1058
  createConversation: async (request) => {
849
1059
  return (await getState().http.post(`${P.messaging}/conversations`, request)).data;
850
1060
  },
851
- getConversations: async () => {
852
- return (await getState().http.get(`${P.messaging}/conversations`)).data;
853
- },
854
- listConversations: async () => {
1061
+ conversations: async () => {
855
1062
  return (await getState().http.get(`${P.messaging}/conversations`)).data;
856
1063
  },
857
1064
  sendMessage: async (conversationId, request) => {
@@ -865,6 +1072,24 @@ const Amba = {
865
1072
  },
866
1073
  markRead: async (conversationId) => {
867
1074
  return (await getState().http.post(`${P.messaging}/conversations/${encodePathSegment(conversationId)}/read`, {})).data;
1075
+ },
1076
+ getMessage: async (conversationId, messageId) => {
1077
+ const PAGE_SIZE = 100;
1078
+ const MAX_PAGES = 50;
1079
+ const s = getState();
1080
+ const path = `${P.messaging}/conversations/${encodePathSegment(conversationId)}/messages`;
1081
+ let offset = 0;
1082
+ for (let i = 0; i < MAX_PAGES; i++) {
1083
+ const page = (await s.http.get(path, {
1084
+ limit: PAGE_SIZE,
1085
+ offset
1086
+ })).data;
1087
+ const hit = page.find((m) => m.id === messageId);
1088
+ if (hit) return hit;
1089
+ if (page.length < PAGE_SIZE) return null;
1090
+ offset += PAGE_SIZE;
1091
+ }
1092
+ return null;
868
1093
  }
869
1094
  },
870
1095
  moderation: {
@@ -905,7 +1130,7 @@ const Amba = {
905
1130
  },
906
1131
  referrals: {
907
1132
  getReferralCode: async () => {
908
- return (await getState().http.get(`${P.referrals}/me`)).data;
1133
+ return (await getState().http.get(`${P.referrals}/my-code`)).data;
909
1134
  },
910
1135
  claimReferral: async (code) => {
911
1136
  return (await getState().http.post(`${P.referrals}/claim`, { code })).data;
@@ -917,20 +1142,25 @@ const Amba = {
917
1142
  return (await getState().http.post(P.referrals, body)).data;
918
1143
  }
919
1144
  },
920
- catalog: { list: async () => {
921
- return (await getState().http.get(P.catalog)).data;
922
- } },
1145
+ catalog: {
1146
+ list: async () => {
1147
+ return (await getState().http.get(P.catalog)).data;
1148
+ },
1149
+ get: async (id) => {
1150
+ return (await getState().http.get(`${P.catalog}/${encodePathSegment(id)}`)).data;
1151
+ }
1152
+ },
923
1153
  content: {
924
- getToday: async (channel = "default") => {
1154
+ today: async (channel = "default") => {
925
1155
  return (await getState().http.get(`${P.content}/today/${encodePathSegment(channel)}`)).data ?? null;
926
1156
  },
927
- getLibrary: async (channel = "default", options) => {
1157
+ library: async (channel = "default", options) => {
928
1158
  return (await getState().http.get(`${P.content}/library/${encodePathSegment(channel)}`, {
929
1159
  limit: options?.limit,
930
1160
  cursor: options?.cursor
931
1161
  })).data;
932
1162
  },
933
- getItem: async (id) => {
1163
+ item: async (id) => {
934
1164
  return (await getState().http.get(`${P.content}/${encodePathSegment(id)}`)).data;
935
1165
  },
936
1166
  updateItem: async (id, state_) => {
@@ -942,7 +1172,7 @@ const Amba = {
942
1172
  },
943
1173
  deepLinks: {
944
1174
  get: async (shortCode) => {
945
- return (await getState().http.get(`${P.deepLinks}/${encodePathSegment(shortCode)}`)).data;
1175
+ return (await getState().http.get(`${P.deepLinks}/resolve/${encodePathSegment(shortCode)}`)).data;
946
1176
  },
947
1177
  create: async (params) => {
948
1178
  return (await getState().http.post(P.deepLinks, params)).data;