@layers/amba-web 4.0.9 → 4.0.11
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/README.md +11 -10
- package/dist/index.browser.d.ts +47 -4
- package/dist/index.browser.d.ts.map +1 -1
- package/dist/index.browser.js +240 -69
- package/dist/index.browser.js.map +1 -1
- package/dist/index.d.ts +47 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +240 -69
- package/dist/index.js.map +1 -1
- package/dist-rn/index.d.ts +47 -4
- package/dist-rn/index.js +240 -69
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -192,6 +192,20 @@ function toAmbaApiError(err) {
|
|
|
192
192
|
return new AmbaApiError("UNKNOWN_ERROR", String(err));
|
|
193
193
|
}
|
|
194
194
|
|
|
195
|
+
//#endregion
|
|
196
|
+
//#region src/path-segment.ts
|
|
197
|
+
/**
|
|
198
|
+
* Encode one caller-supplied id, key, slug or name as a single URL path
|
|
199
|
+
* segment. `encodeURIComponent` escapes `/`, `?` and `#`, so the value stays
|
|
200
|
+
* inside its segment. `.` and `..` are refused because URL resolution treats
|
|
201
|
+
* them as steps to another route, and the empty string is refused because it
|
|
202
|
+
* collapses onto the parent route.
|
|
203
|
+
*/
|
|
204
|
+
function pathSegment(value) {
|
|
205
|
+
if (value === "" || value === "." || value === "..") throw new AmbaApiError("VALIDATION_ERROR", `path segment must not be ${value === "" ? "empty" : `'${value}'`}`);
|
|
206
|
+
return encodeURIComponent(value);
|
|
207
|
+
}
|
|
208
|
+
|
|
195
209
|
//#endregion
|
|
196
210
|
//#region src/realtime.ts
|
|
197
211
|
const INITIAL_BACKOFF_MS = 500;
|
|
@@ -231,7 +245,7 @@ var CollectionSubscription = class {
|
|
|
231
245
|
if (!this.ctx.authHeaders()) throw new Error("Amba.collection().subscribe() requires an authenticated session — call Amba.auth.signIn*/verify* first.");
|
|
232
246
|
const qs = Object.keys(this.filters).length > 0 ? `?where=${encodeURIComponent(JSON.stringify(this.filters))}` : "";
|
|
233
247
|
return openStream({
|
|
234
|
-
url: `${resolveRealtimeBaseUrl(this.ctx.baseUrl)}/v1/client/realtime/collections/${
|
|
248
|
+
url: `${resolveRealtimeBaseUrl(this.ctx.baseUrl)}/v1/client/realtime/collections/${pathSegment(this.name)}${qs}`,
|
|
235
249
|
authHeaders: this.ctx.authHeaders,
|
|
236
250
|
onChange: cb,
|
|
237
251
|
options
|
|
@@ -352,7 +366,7 @@ var ConversationSubscription = class {
|
|
|
352
366
|
subscribe(cb, options = {}) {
|
|
353
367
|
if (!this.ctx.authHeaders()) throw new Error("Amba.messaging.conversation().subscribe() requires an authenticated session — call Amba.auth.signIn*/verify* first.");
|
|
354
368
|
return openStream({
|
|
355
|
-
url: `${resolveRealtimeBaseUrl(this.ctx.baseUrl)}/v1/client/realtime/conversations/${
|
|
369
|
+
url: `${resolveRealtimeBaseUrl(this.ctx.baseUrl)}/v1/client/realtime/conversations/${pathSegment(this.conversationId)}`,
|
|
356
370
|
authHeaders: this.ctx.authHeaders,
|
|
357
371
|
onChange: cb,
|
|
358
372
|
options
|
|
@@ -675,6 +689,19 @@ async function* invokeAiStream(args) {
|
|
|
675
689
|
//#endregion
|
|
676
690
|
//#region src/resilience.ts
|
|
677
691
|
/**
|
|
692
|
+
* Whether `op` belongs to `current`. An op queued under a user id matches only
|
|
693
|
+
* that user id. An op queued before any session matches the same anonymous id,
|
|
694
|
+
* so a guest's offline write lands once that guest's session starts. An op
|
|
695
|
+
* with no recorded owner (queued by an SDK version before owners existed)
|
|
696
|
+
* matches nobody.
|
|
697
|
+
*/
|
|
698
|
+
function outboxOwnerMatches(op, current) {
|
|
699
|
+
const owner = op.owner;
|
|
700
|
+
if (!owner) return false;
|
|
701
|
+
if (owner.appUserId) return owner.appUserId === current.appUserId;
|
|
702
|
+
return owner.anonymousId !== null && owner.anonymousId === current.anonymousId;
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
678
705
|
* Best-effort online probe. In a browser/RN runtime `navigator.onLine`
|
|
679
706
|
* exists; under Node / SSR it doesn't, so we treat "unknown" as online
|
|
680
707
|
* (the executor's own error handling then decides).
|
|
@@ -758,8 +785,18 @@ var OfflineQueue = class {
|
|
|
758
785
|
* SDK surface the loss (debug log) instead of silently discarding.
|
|
759
786
|
*/
|
|
760
787
|
onDropped;
|
|
788
|
+
/**
|
|
789
|
+
* The identity that is signed in right now. When set, enqueue stamps it on
|
|
790
|
+
* each op and flush/pending only touch ops it owns.
|
|
791
|
+
*/
|
|
792
|
+
currentOwner;
|
|
761
793
|
flushing = null;
|
|
762
794
|
/**
|
|
795
|
+
* Bumped by `purge()`. A replay that started before a purge sees the change
|
|
796
|
+
* and does not re-queue its op.
|
|
797
|
+
*/
|
|
798
|
+
purgeGeneration = 0;
|
|
799
|
+
/**
|
|
763
800
|
* Serializes every read-modify-write of the persisted outbox. Each mutation
|
|
764
801
|
* chains onto the previous one's promise, so concurrent `enqueue`s — and an
|
|
765
802
|
* `enqueue` racing the per-op removal inside `flush` — can never read a stale
|
|
@@ -782,6 +819,16 @@ var OfflineQueue = class {
|
|
|
782
819
|
this.executor = opts.executor;
|
|
783
820
|
this.storageKey = opts.storageKey ?? "offline_outbox";
|
|
784
821
|
this.onDropped = opts.onDropped;
|
|
822
|
+
this.currentOwner = opts.currentOwner;
|
|
823
|
+
}
|
|
824
|
+
/**
|
|
825
|
+
* The ops the current owner may see and replay. Without an owner source
|
|
826
|
+
* every op qualifies.
|
|
827
|
+
*/
|
|
828
|
+
visible(ops) {
|
|
829
|
+
if (!this.currentOwner) return ops;
|
|
830
|
+
const current = this.currentOwner();
|
|
831
|
+
return ops.filter((op) => outboxOwnerMatches(op, current));
|
|
785
832
|
}
|
|
786
833
|
/**
|
|
787
834
|
* Run `fn` exclusively against the persisted outbox: it chains after every
|
|
@@ -838,28 +885,55 @@ var OfflineQueue = class {
|
|
|
838
885
|
get isDisposed() {
|
|
839
886
|
return this.disposed;
|
|
840
887
|
}
|
|
841
|
-
/**
|
|
842
|
-
|
|
888
|
+
/**
|
|
889
|
+
* Capture the current owner and purge generation. Take it before a write's
|
|
890
|
+
* live attempt and pass it to {@link enqueue}.
|
|
891
|
+
*/
|
|
892
|
+
ticket() {
|
|
893
|
+
return {
|
|
894
|
+
owner: this.currentOwner?.(),
|
|
895
|
+
generation: this.purgeGeneration
|
|
896
|
+
};
|
|
897
|
+
}
|
|
898
|
+
/**
|
|
899
|
+
* Append an op to the durable outbox. Returns the persisted op. With a
|
|
900
|
+
* `ticket`, the op belongs to the ticket's owner, and the enqueue rejects
|
|
901
|
+
* (code `OFFLINE_QUEUE_PURGED`) when the queue was purged after the ticket
|
|
902
|
+
* was taken, which is what a sign-out during the live attempt does.
|
|
903
|
+
*/
|
|
904
|
+
async enqueue(op, ticket) {
|
|
843
905
|
if (this.disposed) throw new Error("amba offline queue was disposed (SDK reconfigured)");
|
|
906
|
+
const owner = op.owner ?? (ticket ? ticket.owner : this.currentOwner?.());
|
|
844
907
|
const full = {
|
|
845
908
|
...op,
|
|
909
|
+
...owner ? { owner: { ...owner } } : {},
|
|
846
910
|
idempotencyKey: op.idempotencyKey ?? newIdempotencyKey(),
|
|
847
911
|
enqueuedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
848
912
|
};
|
|
849
|
-
return this.mutate((ops) =>
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
913
|
+
return this.mutate((ops) => {
|
|
914
|
+
if (ticket && ticket.generation !== this.purgeGeneration) {
|
|
915
|
+
const err = /* @__PURE__ */ new Error("amba offline queue was cleared by a sign-out while this write was in flight");
|
|
916
|
+
err.code = "OFFLINE_QUEUE_PURGED";
|
|
917
|
+
throw err;
|
|
918
|
+
}
|
|
919
|
+
return {
|
|
920
|
+
next: [...ops, full],
|
|
921
|
+
result: full
|
|
922
|
+
};
|
|
923
|
+
}, { rejectIfDisposed: true });
|
|
853
924
|
}
|
|
854
|
-
/** Number of ops awaiting replay. */
|
|
925
|
+
/** Number of the current owner's ops awaiting replay. */
|
|
855
926
|
async pendingCount() {
|
|
856
|
-
return (await this.load()).length;
|
|
927
|
+
return this.visible(await this.load()).length;
|
|
857
928
|
}
|
|
858
|
-
/**
|
|
929
|
+
/**
|
|
930
|
+
* Snapshot the current owner's pending ops (a copy — mutating it does not
|
|
931
|
+
* affect the queue). Ops queued by another user stay out of the list.
|
|
932
|
+
*/
|
|
859
933
|
async pending() {
|
|
860
|
-
return [...await this.load()];
|
|
934
|
+
return [...this.visible(await this.load())];
|
|
861
935
|
}
|
|
862
|
-
/** Drop every queued op without replaying. */
|
|
936
|
+
/** Drop every queued op, whoever queued it, without replaying. */
|
|
863
937
|
async clear() {
|
|
864
938
|
await this.mutate(() => ({
|
|
865
939
|
next: [],
|
|
@@ -867,10 +941,23 @@ var OfflineQueue = class {
|
|
|
867
941
|
}));
|
|
868
942
|
}
|
|
869
943
|
/**
|
|
870
|
-
*
|
|
871
|
-
*
|
|
872
|
-
*
|
|
873
|
-
|
|
944
|
+
* Drop every queued op, including one whose replay is in flight right now:
|
|
945
|
+
* that op is not re-queued when its replay fails. The SDKs call this on
|
|
946
|
+
* sign-out so nothing the signed-out user queued survives on the device.
|
|
947
|
+
*/
|
|
948
|
+
async purge() {
|
|
949
|
+
this.purgeGeneration += 1;
|
|
950
|
+
await this.clear();
|
|
951
|
+
}
|
|
952
|
+
/**
|
|
953
|
+
* Replay the current owner's ops in FIFO order. Each op is removed only
|
|
954
|
+
* after it succeeds; the first failure stops the flush and retains the
|
|
955
|
+
* remainder (so order is preserved and nothing is dropped mid-batch). Ops
|
|
956
|
+
* another user queued stay held for that user. Ops that can never replay
|
|
957
|
+
* are dropped through `onDropped`: one with no recorded owner (code
|
|
958
|
+
* `OFFLINE_OWNER_UNKNOWN`), and one queued before any session under an
|
|
959
|
+
* anonymous id the install no longer has (code `OFFLINE_OWNER_GONE`).
|
|
960
|
+
* Concurrent calls share one in-flight flush.
|
|
874
961
|
*/
|
|
875
962
|
async flush() {
|
|
876
963
|
if (this.disposed) return {
|
|
@@ -889,8 +976,26 @@ var OfflineQueue = class {
|
|
|
889
976
|
let dropped = 0;
|
|
890
977
|
for (;;) {
|
|
891
978
|
if (this.disposed) break;
|
|
892
|
-
const
|
|
979
|
+
const all = await this.load();
|
|
980
|
+
if (this.currentOwner) {
|
|
981
|
+
const current = this.currentOwner();
|
|
982
|
+
const orphan = all.find((o) => !o.owner || o.owner.appUserId === null && current.anonymousId !== null && o.owner.anonymousId !== current.anonymousId);
|
|
983
|
+
if (orphan) {
|
|
984
|
+
if (!await this.removeOp(orphan)) break;
|
|
985
|
+
dropped += 1;
|
|
986
|
+
this.reportDropped(orphan, orphan.owner ? {
|
|
987
|
+
code: "OFFLINE_OWNER_GONE",
|
|
988
|
+
message: "queued before sign-in under an anonymous id this install no longer has; not replayed"
|
|
989
|
+
} : {
|
|
990
|
+
code: "OFFLINE_OWNER_UNKNOWN",
|
|
991
|
+
message: "queued by an earlier SDK version without an owner; not replayed"
|
|
992
|
+
});
|
|
993
|
+
continue;
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
const head = this.visible(all)[0];
|
|
893
997
|
if (!head) break;
|
|
998
|
+
const generation = this.purgeGeneration;
|
|
894
999
|
let replayError = void 0;
|
|
895
1000
|
let ok = false;
|
|
896
1001
|
try {
|
|
@@ -902,32 +1007,20 @@ var OfflineQueue = class {
|
|
|
902
1007
|
if (!ok && !isDeterministicError(replayError)) {
|
|
903
1008
|
await this.mutate((ops) => {
|
|
904
1009
|
return {
|
|
905
|
-
next: ops.some((o) => o.idempotencyKey === head.idempotencyKey)
|
|
1010
|
+
next: !ops.some((o) => o.idempotencyKey === head.idempotencyKey) && generation === this.purgeGeneration ? [head, ...ops] : ops,
|
|
906
1011
|
result: void 0
|
|
907
1012
|
};
|
|
908
1013
|
});
|
|
909
1014
|
break;
|
|
910
1015
|
}
|
|
911
|
-
if (!await this.
|
|
912
|
-
const idx = ops.findIndex((o) => o.idempotencyKey === head.idempotencyKey);
|
|
913
|
-
if (idx === -1) return {
|
|
914
|
-
next: ops,
|
|
915
|
-
result: false
|
|
916
|
-
};
|
|
917
|
-
return {
|
|
918
|
-
next: ops.slice(0, idx).concat(ops.slice(idx + 1)),
|
|
919
|
-
result: true
|
|
920
|
-
};
|
|
921
|
-
})) break;
|
|
1016
|
+
if (!await this.removeOp(head)) break;
|
|
922
1017
|
if (ok) flushed += 1;
|
|
923
1018
|
else {
|
|
924
1019
|
dropped += 1;
|
|
925
|
-
|
|
926
|
-
this.onDropped?.(head, replayError);
|
|
927
|
-
} catch {}
|
|
1020
|
+
this.reportDropped(head, replayError);
|
|
928
1021
|
}
|
|
929
1022
|
}
|
|
930
|
-
const remaining = (await this.load()).length;
|
|
1023
|
+
const remaining = this.visible(await this.load()).length;
|
|
931
1024
|
return {
|
|
932
1025
|
flushed,
|
|
933
1026
|
dropped,
|
|
@@ -935,6 +1028,29 @@ var OfflineQueue = class {
|
|
|
935
1028
|
};
|
|
936
1029
|
}
|
|
937
1030
|
/**
|
|
1031
|
+
* Remove exactly `op` (by idempotency key) under the lock. Robust against a
|
|
1032
|
+
* concurrent enqueue having shifted the array. Resolves false when the op
|
|
1033
|
+
* is already gone.
|
|
1034
|
+
*/
|
|
1035
|
+
removeOp(op) {
|
|
1036
|
+
return this.mutate((ops) => {
|
|
1037
|
+
const idx = ops.findIndex((o) => o.idempotencyKey === op.idempotencyKey);
|
|
1038
|
+
if (idx === -1) return {
|
|
1039
|
+
next: ops,
|
|
1040
|
+
result: false
|
|
1041
|
+
};
|
|
1042
|
+
return {
|
|
1043
|
+
next: ops.slice(0, idx).concat(ops.slice(idx + 1)),
|
|
1044
|
+
result: true
|
|
1045
|
+
};
|
|
1046
|
+
});
|
|
1047
|
+
}
|
|
1048
|
+
reportDropped(op, err) {
|
|
1049
|
+
try {
|
|
1050
|
+
this.onDropped?.(op, err);
|
|
1051
|
+
} catch {}
|
|
1052
|
+
}
|
|
1053
|
+
/**
|
|
938
1054
|
* Install an auto-flush listener on the runtime's `online` event so the
|
|
939
1055
|
* queue drains the moment connectivity returns. Idempotent — a second
|
|
940
1056
|
* call is a no-op. Returns a detach function.
|
|
@@ -1388,6 +1504,13 @@ let _currentSessionExpiresAt = null;
|
|
|
1388
1504
|
let _currentUser = null;
|
|
1389
1505
|
const BROWSER_IDENTITY_VERSION = 1;
|
|
1390
1506
|
const DEFAULT_SESSION_TTL_MS = 3600 * 1e3;
|
|
1507
|
+
/**
|
|
1508
|
+
* `AmbaConfig.sessionPersistence` for the current configuration. In
|
|
1509
|
+
* `"memory"` mode no identity snapshot (tokens, user id, anonymous id) is read
|
|
1510
|
+
* from or written to `localStorage`.
|
|
1511
|
+
*/
|
|
1512
|
+
let _sessionPersistence = "local";
|
|
1513
|
+
const SESSION_PERSISTENCE_MODES = ["local", "memory"];
|
|
1391
1514
|
function identityStorageKey(projectId) {
|
|
1392
1515
|
return `identity:v${BROWSER_IDENTITY_VERSION}:project:${encodeURIComponent(projectId)}`;
|
|
1393
1516
|
}
|
|
@@ -1427,8 +1550,26 @@ function readPersistedIdentity(persistence, projectId) {
|
|
|
1427
1550
|
return null;
|
|
1428
1551
|
}
|
|
1429
1552
|
}
|
|
1553
|
+
/**
|
|
1554
|
+
* The identity the current core holds, for a `"memory"` re-configure of the
|
|
1555
|
+
* same project: the new core starts from it, so a second `configure()` in the
|
|
1556
|
+
* same page keeps the session.
|
|
1557
|
+
*/
|
|
1558
|
+
function liveIdentity(projectId) {
|
|
1559
|
+
if (!coreInstance || _configuredProjectId !== projectId) return null;
|
|
1560
|
+
return {
|
|
1561
|
+
version: BROWSER_IDENTITY_VERSION,
|
|
1562
|
+
project_id: projectId,
|
|
1563
|
+
anonymous_id: coreInstance.anonymousId(),
|
|
1564
|
+
app_user_id: coreInstance.appUserId() ?? null,
|
|
1565
|
+
session_token: _currentSessionToken,
|
|
1566
|
+
refresh_token: _currentRefreshToken,
|
|
1567
|
+
expires_at: _currentSessionExpiresAt,
|
|
1568
|
+
user: _currentUser
|
|
1569
|
+
};
|
|
1570
|
+
}
|
|
1430
1571
|
function writeIdentitySnapshot() {
|
|
1431
|
-
if (!_persistence?.available || !_configuredProjectId || !coreInstance) return;
|
|
1572
|
+
if (_sessionPersistence === "memory" || !_persistence?.available || !_configuredProjectId || !coreInstance) return;
|
|
1432
1573
|
const snapshot = {
|
|
1433
1574
|
version: BROWSER_IDENTITY_VERSION,
|
|
1434
1575
|
project_id: _configuredProjectId,
|
|
@@ -1582,12 +1723,29 @@ function getOfflineQueue() {
|
|
|
1582
1723
|
store: offlineStore(),
|
|
1583
1724
|
storageKey: `offline_outbox:${_configuredProjectId ?? "default"}`,
|
|
1584
1725
|
executor: replayOutboxOp,
|
|
1726
|
+
currentOwner: () => ({
|
|
1727
|
+
appUserId: _currentSessionToken || _currentRefreshToken ? coreInstance?.appUserId() ?? null : null,
|
|
1728
|
+
anonymousId: coreInstance?.anonymousId() ?? null
|
|
1729
|
+
}),
|
|
1585
1730
|
onDropped: (op, err) => {
|
|
1586
1731
|
if (typeof console !== "undefined") console.warn(`[amba] dropped un-replayable offline ${op.kind} (${op.collection ?? op.event ?? "?"}):`, err);
|
|
1587
1732
|
}
|
|
1588
1733
|
});
|
|
1589
1734
|
return _offlineQueue;
|
|
1590
1735
|
}
|
|
1736
|
+
/**
|
|
1737
|
+
* Empty the offline outbox on sign-out, including writes queued on earlier
|
|
1738
|
+
* page loads, so nothing the signed-out user queued replays under the next
|
|
1739
|
+
* session.
|
|
1740
|
+
*/
|
|
1741
|
+
async function purgeOfflineOutbox() {
|
|
1742
|
+
if (!_persistence) return;
|
|
1743
|
+
try {
|
|
1744
|
+
await getOfflineQueue().purge();
|
|
1745
|
+
} catch (err) {
|
|
1746
|
+
if (typeof console !== "undefined") console.warn("[amba] signOut could not clear the offline outbox", err);
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1591
1749
|
async function insertDirect(name, row) {
|
|
1592
1750
|
return await coreCall((c) => c.collectionsInsert(name, encodeJson(row)));
|
|
1593
1751
|
}
|
|
@@ -1711,8 +1869,8 @@ function buildAiStreamRequest(path, request) {
|
|
|
1711
1869
|
* resolved default-host id is cached for subsequent invokes.
|
|
1712
1870
|
*/
|
|
1713
1871
|
async function resolveFunctionUrl(name) {
|
|
1714
|
-
if (_configuredFunctionsBaseUrl) return `${_configuredFunctionsBaseUrl.replace(/\/$/, "")}/${
|
|
1715
|
-
return `https://${await resolveFunctionsProjectId()}.${DEFAULT_FUNCTIONS_HOST}/${
|
|
1872
|
+
if (_configuredFunctionsBaseUrl) return `${_configuredFunctionsBaseUrl.replace(/\/$/, "")}/${pathSegment(name)}`;
|
|
1873
|
+
return `https://${await resolveFunctionsProjectId()}.${DEFAULT_FUNCTIONS_HOST}/${pathSegment(name)}`;
|
|
1716
1874
|
}
|
|
1717
1875
|
async function resolveFunctionsProjectId() {
|
|
1718
1876
|
if (_resolvedFunctionsProjectId) return _resolvedFunctionsProjectId;
|
|
@@ -1882,8 +2040,10 @@ const Amba = {
|
|
|
1882
2040
|
await initCore({ module_or_path: await readFile(fileURLToPath(wasmJsUrl)) });
|
|
1883
2041
|
} else await initCore();
|
|
1884
2042
|
}
|
|
2043
|
+
const sessionPersistence = config.sessionPersistence ?? "local";
|
|
2044
|
+
if (!SESSION_PERSISTENCE_MODES.includes(sessionPersistence)) throw new AmbaApiError("INVALID_CONFIG", "Amba.configure: sessionPersistence must be \"local\" or \"memory\"");
|
|
1885
2045
|
const persistence = new LocalStoragePersistence(config.storagePrefix ?? "amba");
|
|
1886
|
-
const restoredIdentity = readPersistedIdentity(persistence, config.projectId);
|
|
2046
|
+
const restoredIdentity = sessionPersistence === "memory" ? liveIdentity(config.projectId) : readPersistedIdentity(persistence, config.projectId);
|
|
1887
2047
|
let nextCore;
|
|
1888
2048
|
try {
|
|
1889
2049
|
const { wasmClass } = await getCoreRuntime();
|
|
@@ -1908,6 +2068,8 @@ const Amba = {
|
|
|
1908
2068
|
}
|
|
1909
2069
|
await teardownOffline();
|
|
1910
2070
|
_persistence = persistence;
|
|
2071
|
+
_sessionPersistence = sessionPersistence;
|
|
2072
|
+
if (sessionPersistence === "memory") persistence.delete(identityStorageKey(config.projectId));
|
|
1911
2073
|
coreInstance = nextCore;
|
|
1912
2074
|
_configuredProjectId = config.projectId;
|
|
1913
2075
|
_configuredApiUrl = config.apiUrl ?? DEFAULT_API_URL;
|
|
@@ -1940,6 +2102,7 @@ const Amba = {
|
|
|
1940
2102
|
},
|
|
1941
2103
|
events: {
|
|
1942
2104
|
track: async (event, properties, options) => {
|
|
2105
|
+
const outboxTicket = _offlineEnabled ? getOfflineQueue().ticket() : void 0;
|
|
1943
2106
|
try {
|
|
1944
2107
|
await coreCall((c) => c.track(event, properties ? encodeJson(properties) : void 0, options?.telemetry));
|
|
1945
2108
|
} catch (err) {
|
|
@@ -1950,7 +2113,7 @@ const Amba = {
|
|
|
1950
2113
|
properties,
|
|
1951
2114
|
telemetry: options?.telemetry,
|
|
1952
2115
|
occurredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1953
|
-
});
|
|
2116
|
+
}, outboxTicket);
|
|
1954
2117
|
return;
|
|
1955
2118
|
}
|
|
1956
2119
|
throw err;
|
|
@@ -2077,6 +2240,7 @@ const Amba = {
|
|
|
2077
2240
|
_currentSessionExpiresAt = null;
|
|
2078
2241
|
_currentUser = null;
|
|
2079
2242
|
writeIdentitySnapshot();
|
|
2243
|
+
await purgeOfflineOutbox();
|
|
2080
2244
|
notifyAuthSubscribers(null);
|
|
2081
2245
|
},
|
|
2082
2246
|
refresh: async () => {
|
|
@@ -2152,6 +2316,7 @@ const Amba = {
|
|
|
2152
2316
|
...row,
|
|
2153
2317
|
idempotency_key: idempotencyKey
|
|
2154
2318
|
};
|
|
2319
|
+
const outboxTicket = _offlineEnabled ? getOfflineQueue().ticket() : void 0;
|
|
2155
2320
|
try {
|
|
2156
2321
|
return await coreCall((c) => c.collectionsInsert(name, encodeJson(wireRow)));
|
|
2157
2322
|
} catch (err) {
|
|
@@ -2161,7 +2326,7 @@ const Amba = {
|
|
|
2161
2326
|
collection: name,
|
|
2162
2327
|
payload: row,
|
|
2163
2328
|
idempotencyKey
|
|
2164
|
-
});
|
|
2329
|
+
}, outboxTicket);
|
|
2165
2330
|
return {
|
|
2166
2331
|
...row,
|
|
2167
2332
|
_amba_pending: true
|
|
@@ -2173,39 +2338,45 @@ const Amba = {
|
|
|
2173
2338
|
return await coreCall((c) => c.collectionsInsert(name, encodeJson(row)));
|
|
2174
2339
|
},
|
|
2175
2340
|
update: async (name, id, set) => {
|
|
2176
|
-
if (_offlineEnabled)
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2341
|
+
if (_offlineEnabled) {
|
|
2342
|
+
const outboxTicket = _offlineEnabled ? getOfflineQueue().ticket() : void 0;
|
|
2343
|
+
try {
|
|
2344
|
+
return await coreCall((c) => c.collectionsUpdate(name, id, encodeJson(set)));
|
|
2345
|
+
} catch (err) {
|
|
2346
|
+
if (isOfflineError(err)) {
|
|
2347
|
+
await getOfflineQueue().enqueue({
|
|
2348
|
+
kind: "collection.update",
|
|
2349
|
+
collection: name,
|
|
2350
|
+
id,
|
|
2351
|
+
payload: set
|
|
2352
|
+
}, outboxTicket);
|
|
2353
|
+
return {
|
|
2354
|
+
id,
|
|
2355
|
+
...set,
|
|
2356
|
+
_amba_pending: true
|
|
2357
|
+
};
|
|
2358
|
+
}
|
|
2359
|
+
throw err;
|
|
2191
2360
|
}
|
|
2192
|
-
throw err;
|
|
2193
2361
|
}
|
|
2194
2362
|
return await coreCall((c) => c.collectionsUpdate(name, id, encodeJson(set)));
|
|
2195
2363
|
},
|
|
2196
2364
|
delete: async (name, id) => {
|
|
2197
|
-
if (_offlineEnabled)
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2365
|
+
if (_offlineEnabled) {
|
|
2366
|
+
const outboxTicket = _offlineEnabled ? getOfflineQueue().ticket() : void 0;
|
|
2367
|
+
try {
|
|
2368
|
+
return await coreCall((c) => c.collectionsDelete(name, id));
|
|
2369
|
+
} catch (err) {
|
|
2370
|
+
if (isOfflineError(err)) {
|
|
2371
|
+
await getOfflineQueue().enqueue({
|
|
2372
|
+
kind: "collection.delete",
|
|
2373
|
+
collection: name,
|
|
2374
|
+
id
|
|
2375
|
+
}, outboxTicket);
|
|
2376
|
+
return { data: { deleted: true } };
|
|
2377
|
+
}
|
|
2378
|
+
throw err;
|
|
2207
2379
|
}
|
|
2208
|
-
throw err;
|
|
2209
2380
|
}
|
|
2210
2381
|
return await coreCall((c) => c.collectionsDelete(name, id));
|
|
2211
2382
|
},
|
|
@@ -2348,7 +2519,7 @@ const Amba = {
|
|
|
2348
2519
|
media: { catalog: async (slug) => {
|
|
2349
2520
|
if (!_configuredClientKey) throw new AmbaApiError("NOT_INITIALIZED", "amba SDK not configured. Call Amba.configure({ projectId, clientKey }) before reading a media catalog.");
|
|
2350
2521
|
if (!slug || slug.trim() === "") throw new AmbaApiError("INVALID_ARGUMENT", "Amba.media.catalog: a catalog slug is required.");
|
|
2351
|
-
const url = `${_configuredApiUrl.replace(/\/$/, "")}/v1/client/media/catalogs/${
|
|
2522
|
+
const url = `${_configuredApiUrl.replace(/\/$/, "")}/v1/client/media/catalogs/${pathSegment(slug)}`;
|
|
2352
2523
|
let resp;
|
|
2353
2524
|
try {
|
|
2354
2525
|
resp = await fetch(url, {
|
|
@@ -2683,7 +2854,7 @@ function getCoreWasm() {
|
|
|
2683
2854
|
* `verify-sdk-version-coherence` GH action gates every publish on the
|
|
2684
2855
|
* pair agreeing (and matching the `web-v<X.Y.Z>` tag when tag-triggered).
|
|
2685
2856
|
*/
|
|
2686
|
-
const SDK_VERSION = "4.0.
|
|
2857
|
+
const SDK_VERSION = "4.0.11";
|
|
2687
2858
|
|
|
2688
2859
|
//#endregion
|
|
2689
2860
|
export { Amba, AmbaApiError, AmbaGroupScopedClient, CollectionSubscription, ConversationSubscription, GamificationSubscription, SDK_VERSION, getCoreWasm, withOptimistic };
|