@layers/amba-react-native 4.0.1 → 4.0.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 CHANGED
@@ -91,12 +91,14 @@ var AmbaHttpClient = class {
91
91
  sdkVersionHeader;
92
92
  identity;
93
93
  fetchImpl;
94
+ ensureSession;
94
95
  constructor(opts) {
95
96
  this.apiKey = opts.apiKey;
96
97
  this.baseUrl = opts.baseUrl.replace(/\/+$/, "");
97
98
  this.sdkPlatform = opts.sdkPlatform;
98
99
  this.sdkVersionHeader = opts.sdkVersionHeader;
99
100
  this.identity = opts.identity;
101
+ this.ensureSession = opts.ensureSession;
100
102
  this.fetchImpl = opts.fetchImpl ?? globalThis.fetch.bind(globalThis);
101
103
  }
102
104
  /** Build the absolute URL for an API path, optionally with a query string. */
@@ -139,6 +141,7 @@ var AmbaHttpClient = class {
139
141
  return this.request("DELETE", path);
140
142
  }
141
143
  async request(method, path, body, query) {
144
+ if (this.ensureSession && !this.identity.getSessionToken()) await this.ensureSession(path);
142
145
  const url = this.url(path, query);
143
146
  const init = {
144
147
  method,
@@ -351,7 +354,13 @@ function freshSeed() {
351
354
  //#endregion
352
355
  //#region src/index.ts
353
356
  /** SDK semver — burned in at build time. Bump alongside package.json. */
354
- const SDK_VERSION = "4.0.1";
357
+ const SDK_VERSION = "4.0.3";
358
+ /**
359
+ * Default host that serves deployed functions. `Amba.functions.invoke(name)`
360
+ * targets `https://<projectId>.<DEFAULT_FUNCTIONS_HOST>/<name>` unless a
361
+ * `functionsBaseUrl` override is configured.
362
+ */
363
+ const DEFAULT_FUNCTIONS_HOST = "fn.amba.host";
355
364
  let state = null;
356
365
  function getState() {
357
366
  if (!state) throw new AmbaApiError("NOT_INITIALIZED", "@layers/amba-react-native: SDK not configured. Call Amba.configure({ projectId, clientKey }) at app start.");
@@ -395,6 +404,26 @@ async function postAuth(state_, path, body) {
395
404
  await notifyFromAuthResult(result);
396
405
  return result;
397
406
  }
407
+ /** Paths that must NOT trigger lazy anonymous sign-in. */
408
+ function pathSkipsLazySession(path) {
409
+ if (path.startsWith("/v1/client/auth/")) return true;
410
+ if (path === P.events || path.startsWith(`${P.events}/`)) return true;
411
+ if (path === P.diagnosticsPing) return true;
412
+ if (path === P.config || path.startsWith(`${P.config}/`)) return true;
413
+ if (path === P.flags || path.startsWith(`${P.flags}/`)) return true;
414
+ return false;
415
+ }
416
+ async function ensureAnonymousSession(state_) {
417
+ if (state_.identity.getSessionToken()) return;
418
+ if (!state_.anonSignInInflight) state_.anonSignInInflight = (async () => {
419
+ try {
420
+ await postAuth(state_, P.authAnonymous, { anonymous_id: state_.identity.getAnonymousId() });
421
+ } finally {
422
+ state_.anonSignInInflight = null;
423
+ }
424
+ })();
425
+ await state_.anonSignInInflight;
426
+ }
398
427
  /**
399
428
  * POST helper for endpoints that return `{ data: { user } }` WITHOUT
400
429
  * minting new tokens. Used by `linkSmsOtp` / `linkEmailOtp` / `unlink*`
@@ -491,6 +520,22 @@ function encodePathSegment(s) {
491
520
  return encodeURIComponent(s);
492
521
  }
493
522
  /**
523
+ * Resolve the full invocation URL for a deployed function. With
524
+ * `functionsBaseUrl` configured we POST to `<base>/<name>` verbatim;
525
+ * otherwise we resolve the server-side project id once (via
526
+ * `diagnostics.ping`, cached) and target
527
+ * `https://<projectId>.fn.amba.host/<name>`.
528
+ */
529
+ async function resolveFunctionUrl(s, name) {
530
+ if (s.functionsBaseUrl) return `${s.functionsBaseUrl.replace(/\/$/, "")}/${encodeURIComponent(name)}`;
531
+ if (!s.resolvedFunctionsProjectId) {
532
+ const ping = await Amba.diagnostics.ping();
533
+ if (!ping.ok || !ping.server_project_id) throw new AmbaApiError("FUNCTIONS_HOST_UNRESOLVED", "Could not resolve the functions host for this project. Pass `functionsBaseUrl` to Amba.configure() to invoke functions explicitly.", ping);
534
+ s.resolvedFunctionsProjectId = ping.server_project_id;
535
+ }
536
+ return `https://${s.resolvedFunctionsProjectId}.${DEFAULT_FUNCTIONS_HOST}/${encodeURIComponent(name)}`;
537
+ }
538
+ /**
494
539
  * Top-level SDK namespace for React Native.
495
540
  */
496
541
  const Amba = {
@@ -508,11 +553,17 @@ const Amba = {
508
553
  baseUrl,
509
554
  sdkPlatform: "react-native",
510
555
  sdkVersionHeader,
511
- identity
556
+ identity,
557
+ ensureSession: config.autoAnonymousSession === false ? void 0 : async (path) => {
558
+ if (pathSkipsLazySession(path)) return;
559
+ await ensureAnonymousSession(getState());
560
+ }
512
561
  }),
513
562
  identity,
514
563
  debug: config.debug ?? false,
515
- projectId: config.projectId
564
+ projectId: config.projectId,
565
+ functionsBaseUrl: config.functionsBaseUrl ?? null,
566
+ resolvedFunctionsProjectId: null
516
567
  };
517
568
  },
518
569
  _reset: () => {
@@ -651,6 +702,19 @@ const Amba = {
651
702
  session_token: sessionToken
652
703
  });
653
704
  },
705
+ linkEmailPassword: async (email, password) => {
706
+ requireEmail(email);
707
+ requirePassword(password);
708
+ const s = getState();
709
+ const sessionToken = s.identity.getSessionToken();
710
+ if (!sessionToken) throw new AmbaApiError("UNAUTHORIZED", "no active session — call signIn* first");
711
+ return postUserNoRotate(s, P.authLink, {
712
+ provider: "email_password",
713
+ email,
714
+ password,
715
+ session_token: sessionToken
716
+ });
717
+ },
654
718
  unlinkSmsOtp: async () => unlinkProvider("sms_otp"),
655
719
  unlinkEmailOtp: async () => unlinkProvider("email_otp"),
656
720
  unlinkApple: async () => unlinkProvider("apple"),
@@ -899,8 +963,41 @@ const Amba = {
899
963
  }
900
964
  },
901
965
  ai: { anthropic: { messages: { create: async (request) => {
902
- return (await getState().http.post(P.aiAnthropic, request)).data;
966
+ const env = await getState().http.post(P.aiAnthropic, request);
967
+ if (!env || typeof env !== "object" || env.data === void 0) throw new AmbaApiError("MALFORMED_RESPONSE", "AI response was not wrapped in the expected { data } envelope", env);
968
+ return env.data;
903
969
  } } } },
970
+ functions: { invoke: async (name, body, options) => {
971
+ const s = getState();
972
+ if (!name || name.trim() === "") throw new AmbaApiError("INVALID_ARGUMENT", "Amba.functions.invoke: a function name is required.");
973
+ const url = await resolveFunctionUrl(s, name);
974
+ const method = options?.method ?? "POST";
975
+ const headers = s.http.buildHeaders(options?.headers);
976
+ let requestBody;
977
+ if (body !== void 0 && method !== "GET") requestBody = JSON.stringify(body);
978
+ else delete headers["Content-Type"];
979
+ let response;
980
+ try {
981
+ response = await (globalThis.fetch ?? fetch)(url, {
982
+ method,
983
+ headers,
984
+ body: requestBody
985
+ });
986
+ } catch (err) {
987
+ throw new AmbaApiError("NETWORK_ERROR", `Amba.functions.invoke("${name}") network error: ${err instanceof Error ? err.message : String(err)}`, err);
988
+ }
989
+ const text = await response.text();
990
+ if (!response.ok) {
991
+ const ct = response.headers.get("content-type") ?? "";
992
+ throw mapHttpError(response, text, ct.includes("application/json"));
993
+ }
994
+ if (!text) return void 0;
995
+ try {
996
+ return JSON.parse(text);
997
+ } catch {
998
+ return text;
999
+ }
1000
+ } },
904
1001
  config: { fetch: async () => {
905
1002
  const s = getState();
906
1003
  const url = s.http.url(P.config);
@@ -1210,19 +1307,19 @@ const Amba = {
1210
1307
  return (await getState().http.get(`${P.content}/today/${encodePathSegment(channel)}`)).data ?? null;
1211
1308
  },
1212
1309
  library: async (channel = "default", options) => {
1213
- return (await getState().http.get(`${P.content}/library/${encodePathSegment(channel)}`, {
1310
+ return (await getState().http.get(`${P.content}/libraries/${encodePathSegment(channel)}`, {
1214
1311
  limit: options?.limit,
1215
1312
  cursor: options?.cursor
1216
1313
  })).data;
1217
1314
  },
1218
1315
  item: async (id) => {
1219
- return (await getState().http.get(`${P.content}/${encodePathSegment(id)}`)).data;
1316
+ return (await getState().http.get(`${P.content}/items/${encodePathSegment(id)}`)).data;
1220
1317
  },
1221
1318
  updateItem: async (id, state_) => {
1222
- return (await getState().http.post(`${P.content}/${encodePathSegment(id)}/state`, state_)).data;
1319
+ return (await getState().http.patch(`${P.content}/items/${encodePathSegment(id)}`, state_)).data;
1223
1320
  },
1224
1321
  createItem: async (channel, item) => {
1225
- return (await getState().http.post(`${P.content}/${encodePathSegment(channel)}`, item)).data;
1322
+ return (await getState().http.post(`${P.content}/libraries/${encodePathSegment(channel)}/items`, item)).data;
1226
1323
  }
1227
1324
  },
1228
1325
  deepLinks: {