@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.browser.js
CHANGED
|
@@ -214,6 +214,20 @@ function toAmbaApiError(err) {
|
|
|
214
214
|
return new AmbaApiError("UNKNOWN_ERROR", String(err));
|
|
215
215
|
}
|
|
216
216
|
|
|
217
|
+
//#endregion
|
|
218
|
+
//#region src/path-segment.ts
|
|
219
|
+
/**
|
|
220
|
+
* Encode one caller-supplied id, key, slug or name as a single URL path
|
|
221
|
+
* segment. `encodeURIComponent` escapes `/`, `?` and `#`, so the value stays
|
|
222
|
+
* inside its segment. `.` and `..` are refused because URL resolution treats
|
|
223
|
+
* them as steps to another route, and the empty string is refused because it
|
|
224
|
+
* collapses onto the parent route.
|
|
225
|
+
*/
|
|
226
|
+
function pathSegment(value) {
|
|
227
|
+
if (value === "" || value === "." || value === "..") throw new AmbaApiError("VALIDATION_ERROR", `path segment must not be ${value === "" ? "empty" : `'${value}'`}`);
|
|
228
|
+
return encodeURIComponent(value);
|
|
229
|
+
}
|
|
230
|
+
|
|
217
231
|
//#endregion
|
|
218
232
|
//#region src/realtime.ts
|
|
219
233
|
const INITIAL_BACKOFF_MS = 500;
|
|
@@ -253,7 +267,7 @@ var CollectionSubscription = class {
|
|
|
253
267
|
if (!this.ctx.authHeaders()) throw new Error("Amba.collection().subscribe() requires an authenticated session — call Amba.auth.signIn*/verify* first.");
|
|
254
268
|
const qs = Object.keys(this.filters).length > 0 ? `?where=${encodeURIComponent(JSON.stringify(this.filters))}` : "";
|
|
255
269
|
return openStream({
|
|
256
|
-
url: `${resolveRealtimeBaseUrl(this.ctx.baseUrl)}/v1/client/realtime/collections/${
|
|
270
|
+
url: `${resolveRealtimeBaseUrl(this.ctx.baseUrl)}/v1/client/realtime/collections/${pathSegment(this.name)}${qs}`,
|
|
257
271
|
authHeaders: this.ctx.authHeaders,
|
|
258
272
|
onChange: cb,
|
|
259
273
|
options
|
|
@@ -374,7 +388,7 @@ var ConversationSubscription = class {
|
|
|
374
388
|
subscribe(cb, options = {}) {
|
|
375
389
|
if (!this.ctx.authHeaders()) throw new Error("Amba.messaging.conversation().subscribe() requires an authenticated session — call Amba.auth.signIn*/verify* first.");
|
|
376
390
|
return openStream({
|
|
377
|
-
url: `${resolveRealtimeBaseUrl(this.ctx.baseUrl)}/v1/client/realtime/conversations/${
|
|
391
|
+
url: `${resolveRealtimeBaseUrl(this.ctx.baseUrl)}/v1/client/realtime/conversations/${pathSegment(this.conversationId)}`,
|
|
378
392
|
authHeaders: this.ctx.authHeaders,
|
|
379
393
|
onChange: cb,
|
|
380
394
|
options
|
|
@@ -697,6 +711,19 @@ async function* invokeAiStream(args) {
|
|
|
697
711
|
//#endregion
|
|
698
712
|
//#region src/resilience.ts
|
|
699
713
|
/**
|
|
714
|
+
* Whether `op` belongs to `current`. An op queued under a user id matches only
|
|
715
|
+
* that user id. An op queued before any session matches the same anonymous id,
|
|
716
|
+
* so a guest's offline write lands once that guest's session starts. An op
|
|
717
|
+
* with no recorded owner (queued by an SDK version before owners existed)
|
|
718
|
+
* matches nobody.
|
|
719
|
+
*/
|
|
720
|
+
function outboxOwnerMatches(op, current) {
|
|
721
|
+
const owner = op.owner;
|
|
722
|
+
if (!owner) return false;
|
|
723
|
+
if (owner.appUserId) return owner.appUserId === current.appUserId;
|
|
724
|
+
return owner.anonymousId !== null && owner.anonymousId === current.anonymousId;
|
|
725
|
+
}
|
|
726
|
+
/**
|
|
700
727
|
* Best-effort online probe. In a browser/RN runtime `navigator.onLine`
|
|
701
728
|
* exists; under Node / SSR it doesn't, so we treat "unknown" as online
|
|
702
729
|
* (the executor's own error handling then decides).
|
|
@@ -780,8 +807,18 @@ var OfflineQueue = class {
|
|
|
780
807
|
* SDK surface the loss (debug log) instead of silently discarding.
|
|
781
808
|
*/
|
|
782
809
|
onDropped;
|
|
810
|
+
/**
|
|
811
|
+
* The identity that is signed in right now. When set, enqueue stamps it on
|
|
812
|
+
* each op and flush/pending only touch ops it owns.
|
|
813
|
+
*/
|
|
814
|
+
currentOwner;
|
|
783
815
|
flushing = null;
|
|
784
816
|
/**
|
|
817
|
+
* Bumped by `purge()`. A replay that started before a purge sees the change
|
|
818
|
+
* and does not re-queue its op.
|
|
819
|
+
*/
|
|
820
|
+
purgeGeneration = 0;
|
|
821
|
+
/**
|
|
785
822
|
* Serializes every read-modify-write of the persisted outbox. Each mutation
|
|
786
823
|
* chains onto the previous one's promise, so concurrent `enqueue`s — and an
|
|
787
824
|
* `enqueue` racing the per-op removal inside `flush` — can never read a stale
|
|
@@ -804,6 +841,16 @@ var OfflineQueue = class {
|
|
|
804
841
|
this.executor = opts.executor;
|
|
805
842
|
this.storageKey = opts.storageKey ?? "offline_outbox";
|
|
806
843
|
this.onDropped = opts.onDropped;
|
|
844
|
+
this.currentOwner = opts.currentOwner;
|
|
845
|
+
}
|
|
846
|
+
/**
|
|
847
|
+
* The ops the current owner may see and replay. Without an owner source
|
|
848
|
+
* every op qualifies.
|
|
849
|
+
*/
|
|
850
|
+
visible(ops) {
|
|
851
|
+
if (!this.currentOwner) return ops;
|
|
852
|
+
const current = this.currentOwner();
|
|
853
|
+
return ops.filter((op) => outboxOwnerMatches(op, current));
|
|
807
854
|
}
|
|
808
855
|
/**
|
|
809
856
|
* Run `fn` exclusively against the persisted outbox: it chains after every
|
|
@@ -860,28 +907,55 @@ var OfflineQueue = class {
|
|
|
860
907
|
get isDisposed() {
|
|
861
908
|
return this.disposed;
|
|
862
909
|
}
|
|
863
|
-
/**
|
|
864
|
-
|
|
910
|
+
/**
|
|
911
|
+
* Capture the current owner and purge generation. Take it before a write's
|
|
912
|
+
* live attempt and pass it to {@link enqueue}.
|
|
913
|
+
*/
|
|
914
|
+
ticket() {
|
|
915
|
+
return {
|
|
916
|
+
owner: this.currentOwner?.(),
|
|
917
|
+
generation: this.purgeGeneration
|
|
918
|
+
};
|
|
919
|
+
}
|
|
920
|
+
/**
|
|
921
|
+
* Append an op to the durable outbox. Returns the persisted op. With a
|
|
922
|
+
* `ticket`, the op belongs to the ticket's owner, and the enqueue rejects
|
|
923
|
+
* (code `OFFLINE_QUEUE_PURGED`) when the queue was purged after the ticket
|
|
924
|
+
* was taken, which is what a sign-out during the live attempt does.
|
|
925
|
+
*/
|
|
926
|
+
async enqueue(op, ticket) {
|
|
865
927
|
if (this.disposed) throw new Error("amba offline queue was disposed (SDK reconfigured)");
|
|
928
|
+
const owner = op.owner ?? (ticket ? ticket.owner : this.currentOwner?.());
|
|
866
929
|
const full = {
|
|
867
930
|
...op,
|
|
931
|
+
...owner ? { owner: { ...owner } } : {},
|
|
868
932
|
idempotencyKey: op.idempotencyKey ?? newIdempotencyKey(),
|
|
869
933
|
enqueuedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
870
934
|
};
|
|
871
|
-
return this.mutate((ops) =>
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
935
|
+
return this.mutate((ops) => {
|
|
936
|
+
if (ticket && ticket.generation !== this.purgeGeneration) {
|
|
937
|
+
const err = /* @__PURE__ */ new Error("amba offline queue was cleared by a sign-out while this write was in flight");
|
|
938
|
+
err.code = "OFFLINE_QUEUE_PURGED";
|
|
939
|
+
throw err;
|
|
940
|
+
}
|
|
941
|
+
return {
|
|
942
|
+
next: [...ops, full],
|
|
943
|
+
result: full
|
|
944
|
+
};
|
|
945
|
+
}, { rejectIfDisposed: true });
|
|
875
946
|
}
|
|
876
|
-
/** Number of ops awaiting replay. */
|
|
947
|
+
/** Number of the current owner's ops awaiting replay. */
|
|
877
948
|
async pendingCount() {
|
|
878
|
-
return (await this.load()).length;
|
|
949
|
+
return this.visible(await this.load()).length;
|
|
879
950
|
}
|
|
880
|
-
/**
|
|
951
|
+
/**
|
|
952
|
+
* Snapshot the current owner's pending ops (a copy — mutating it does not
|
|
953
|
+
* affect the queue). Ops queued by another user stay out of the list.
|
|
954
|
+
*/
|
|
881
955
|
async pending() {
|
|
882
|
-
return [...await this.load()];
|
|
956
|
+
return [...this.visible(await this.load())];
|
|
883
957
|
}
|
|
884
|
-
/** Drop every queued op without replaying. */
|
|
958
|
+
/** Drop every queued op, whoever queued it, without replaying. */
|
|
885
959
|
async clear() {
|
|
886
960
|
await this.mutate(() => ({
|
|
887
961
|
next: [],
|
|
@@ -889,10 +963,23 @@ var OfflineQueue = class {
|
|
|
889
963
|
}));
|
|
890
964
|
}
|
|
891
965
|
/**
|
|
892
|
-
*
|
|
893
|
-
*
|
|
894
|
-
*
|
|
895
|
-
|
|
966
|
+
* Drop every queued op, including one whose replay is in flight right now:
|
|
967
|
+
* that op is not re-queued when its replay fails. The SDKs call this on
|
|
968
|
+
* sign-out so nothing the signed-out user queued survives on the device.
|
|
969
|
+
*/
|
|
970
|
+
async purge() {
|
|
971
|
+
this.purgeGeneration += 1;
|
|
972
|
+
await this.clear();
|
|
973
|
+
}
|
|
974
|
+
/**
|
|
975
|
+
* Replay the current owner's ops in FIFO order. Each op is removed only
|
|
976
|
+
* after it succeeds; the first failure stops the flush and retains the
|
|
977
|
+
* remainder (so order is preserved and nothing is dropped mid-batch). Ops
|
|
978
|
+
* another user queued stay held for that user. Ops that can never replay
|
|
979
|
+
* are dropped through `onDropped`: one with no recorded owner (code
|
|
980
|
+
* `OFFLINE_OWNER_UNKNOWN`), and one queued before any session under an
|
|
981
|
+
* anonymous id the install no longer has (code `OFFLINE_OWNER_GONE`).
|
|
982
|
+
* Concurrent calls share one in-flight flush.
|
|
896
983
|
*/
|
|
897
984
|
async flush() {
|
|
898
985
|
if (this.disposed) return {
|
|
@@ -911,8 +998,26 @@ var OfflineQueue = class {
|
|
|
911
998
|
let dropped = 0;
|
|
912
999
|
for (;;) {
|
|
913
1000
|
if (this.disposed) break;
|
|
914
|
-
const
|
|
1001
|
+
const all = await this.load();
|
|
1002
|
+
if (this.currentOwner) {
|
|
1003
|
+
const current = this.currentOwner();
|
|
1004
|
+
const orphan = all.find((o) => !o.owner || o.owner.appUserId === null && current.anonymousId !== null && o.owner.anonymousId !== current.anonymousId);
|
|
1005
|
+
if (orphan) {
|
|
1006
|
+
if (!await this.removeOp(orphan)) break;
|
|
1007
|
+
dropped += 1;
|
|
1008
|
+
this.reportDropped(orphan, orphan.owner ? {
|
|
1009
|
+
code: "OFFLINE_OWNER_GONE",
|
|
1010
|
+
message: "queued before sign-in under an anonymous id this install no longer has; not replayed"
|
|
1011
|
+
} : {
|
|
1012
|
+
code: "OFFLINE_OWNER_UNKNOWN",
|
|
1013
|
+
message: "queued by an earlier SDK version without an owner; not replayed"
|
|
1014
|
+
});
|
|
1015
|
+
continue;
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
const head = this.visible(all)[0];
|
|
915
1019
|
if (!head) break;
|
|
1020
|
+
const generation = this.purgeGeneration;
|
|
916
1021
|
let replayError = void 0;
|
|
917
1022
|
let ok = false;
|
|
918
1023
|
try {
|
|
@@ -924,32 +1029,20 @@ var OfflineQueue = class {
|
|
|
924
1029
|
if (!ok && !isDeterministicError(replayError)) {
|
|
925
1030
|
await this.mutate((ops) => {
|
|
926
1031
|
return {
|
|
927
|
-
next: ops.some((o) => o.idempotencyKey === head.idempotencyKey)
|
|
1032
|
+
next: !ops.some((o) => o.idempotencyKey === head.idempotencyKey) && generation === this.purgeGeneration ? [head, ...ops] : ops,
|
|
928
1033
|
result: void 0
|
|
929
1034
|
};
|
|
930
1035
|
});
|
|
931
1036
|
break;
|
|
932
1037
|
}
|
|
933
|
-
if (!await this.
|
|
934
|
-
const idx = ops.findIndex((o) => o.idempotencyKey === head.idempotencyKey);
|
|
935
|
-
if (idx === -1) return {
|
|
936
|
-
next: ops,
|
|
937
|
-
result: false
|
|
938
|
-
};
|
|
939
|
-
return {
|
|
940
|
-
next: ops.slice(0, idx).concat(ops.slice(idx + 1)),
|
|
941
|
-
result: true
|
|
942
|
-
};
|
|
943
|
-
})) break;
|
|
1038
|
+
if (!await this.removeOp(head)) break;
|
|
944
1039
|
if (ok) flushed += 1;
|
|
945
1040
|
else {
|
|
946
1041
|
dropped += 1;
|
|
947
|
-
|
|
948
|
-
this.onDropped?.(head, replayError);
|
|
949
|
-
} catch {}
|
|
1042
|
+
this.reportDropped(head, replayError);
|
|
950
1043
|
}
|
|
951
1044
|
}
|
|
952
|
-
const remaining = (await this.load()).length;
|
|
1045
|
+
const remaining = this.visible(await this.load()).length;
|
|
953
1046
|
return {
|
|
954
1047
|
flushed,
|
|
955
1048
|
dropped,
|
|
@@ -957,6 +1050,29 @@ var OfflineQueue = class {
|
|
|
957
1050
|
};
|
|
958
1051
|
}
|
|
959
1052
|
/**
|
|
1053
|
+
* Remove exactly `op` (by idempotency key) under the lock. Robust against a
|
|
1054
|
+
* concurrent enqueue having shifted the array. Resolves false when the op
|
|
1055
|
+
* is already gone.
|
|
1056
|
+
*/
|
|
1057
|
+
removeOp(op) {
|
|
1058
|
+
return this.mutate((ops) => {
|
|
1059
|
+
const idx = ops.findIndex((o) => o.idempotencyKey === op.idempotencyKey);
|
|
1060
|
+
if (idx === -1) return {
|
|
1061
|
+
next: ops,
|
|
1062
|
+
result: false
|
|
1063
|
+
};
|
|
1064
|
+
return {
|
|
1065
|
+
next: ops.slice(0, idx).concat(ops.slice(idx + 1)),
|
|
1066
|
+
result: true
|
|
1067
|
+
};
|
|
1068
|
+
});
|
|
1069
|
+
}
|
|
1070
|
+
reportDropped(op, err) {
|
|
1071
|
+
try {
|
|
1072
|
+
this.onDropped?.(op, err);
|
|
1073
|
+
} catch {}
|
|
1074
|
+
}
|
|
1075
|
+
/**
|
|
960
1076
|
* Install an auto-flush listener on the runtime's `online` event so the
|
|
961
1077
|
* queue drains the moment connectivity returns. Idempotent — a second
|
|
962
1078
|
* call is a no-op. Returns a detach function.
|
|
@@ -1390,6 +1506,13 @@ let _currentSessionExpiresAt = null;
|
|
|
1390
1506
|
let _currentUser = null;
|
|
1391
1507
|
const BROWSER_IDENTITY_VERSION = 1;
|
|
1392
1508
|
const DEFAULT_SESSION_TTL_MS = 3600 * 1e3;
|
|
1509
|
+
/**
|
|
1510
|
+
* `AmbaConfig.sessionPersistence` for the current configuration. In
|
|
1511
|
+
* `"memory"` mode no identity snapshot (tokens, user id, anonymous id) is read
|
|
1512
|
+
* from or written to `localStorage`.
|
|
1513
|
+
*/
|
|
1514
|
+
let _sessionPersistence = "local";
|
|
1515
|
+
const SESSION_PERSISTENCE_MODES = ["local", "memory"];
|
|
1393
1516
|
function identityStorageKey(projectId) {
|
|
1394
1517
|
return `identity:v${BROWSER_IDENTITY_VERSION}:project:${encodeURIComponent(projectId)}`;
|
|
1395
1518
|
}
|
|
@@ -1429,8 +1552,26 @@ function readPersistedIdentity(persistence, projectId) {
|
|
|
1429
1552
|
return null;
|
|
1430
1553
|
}
|
|
1431
1554
|
}
|
|
1555
|
+
/**
|
|
1556
|
+
* The identity the current core holds, for a `"memory"` re-configure of the
|
|
1557
|
+
* same project: the new core starts from it, so a second `configure()` in the
|
|
1558
|
+
* same page keeps the session.
|
|
1559
|
+
*/
|
|
1560
|
+
function liveIdentity(projectId) {
|
|
1561
|
+
if (!coreInstance || _configuredProjectId !== projectId) return null;
|
|
1562
|
+
return {
|
|
1563
|
+
version: BROWSER_IDENTITY_VERSION,
|
|
1564
|
+
project_id: projectId,
|
|
1565
|
+
anonymous_id: coreInstance.anonymousId(),
|
|
1566
|
+
app_user_id: coreInstance.appUserId() ?? null,
|
|
1567
|
+
session_token: _currentSessionToken,
|
|
1568
|
+
refresh_token: _currentRefreshToken,
|
|
1569
|
+
expires_at: _currentSessionExpiresAt,
|
|
1570
|
+
user: _currentUser
|
|
1571
|
+
};
|
|
1572
|
+
}
|
|
1432
1573
|
function writeIdentitySnapshot() {
|
|
1433
|
-
if (!_persistence?.available || !_configuredProjectId || !coreInstance) return;
|
|
1574
|
+
if (_sessionPersistence === "memory" || !_persistence?.available || !_configuredProjectId || !coreInstance) return;
|
|
1434
1575
|
const snapshot = {
|
|
1435
1576
|
version: BROWSER_IDENTITY_VERSION,
|
|
1436
1577
|
project_id: _configuredProjectId,
|
|
@@ -1584,12 +1725,29 @@ function getOfflineQueue() {
|
|
|
1584
1725
|
store: offlineStore(),
|
|
1585
1726
|
storageKey: `offline_outbox:${_configuredProjectId ?? "default"}`,
|
|
1586
1727
|
executor: replayOutboxOp,
|
|
1728
|
+
currentOwner: () => ({
|
|
1729
|
+
appUserId: _currentSessionToken || _currentRefreshToken ? coreInstance?.appUserId() ?? null : null,
|
|
1730
|
+
anonymousId: coreInstance?.anonymousId() ?? null
|
|
1731
|
+
}),
|
|
1587
1732
|
onDropped: (op, err) => {
|
|
1588
1733
|
if (typeof console !== "undefined") console.warn(`[amba] dropped un-replayable offline ${op.kind} (${op.collection ?? op.event ?? "?"}):`, err);
|
|
1589
1734
|
}
|
|
1590
1735
|
});
|
|
1591
1736
|
return _offlineQueue;
|
|
1592
1737
|
}
|
|
1738
|
+
/**
|
|
1739
|
+
* Empty the offline outbox on sign-out, including writes queued on earlier
|
|
1740
|
+
* page loads, so nothing the signed-out user queued replays under the next
|
|
1741
|
+
* session.
|
|
1742
|
+
*/
|
|
1743
|
+
async function purgeOfflineOutbox() {
|
|
1744
|
+
if (!_persistence) return;
|
|
1745
|
+
try {
|
|
1746
|
+
await getOfflineQueue().purge();
|
|
1747
|
+
} catch (err) {
|
|
1748
|
+
if (typeof console !== "undefined") console.warn("[amba] signOut could not clear the offline outbox", err);
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1593
1751
|
async function insertDirect(name, row) {
|
|
1594
1752
|
return await coreCall((c) => c.collectionsInsert(name, encodeJson(row)));
|
|
1595
1753
|
}
|
|
@@ -1713,8 +1871,8 @@ function buildAiStreamRequest(path, request) {
|
|
|
1713
1871
|
* resolved default-host id is cached for subsequent invokes.
|
|
1714
1872
|
*/
|
|
1715
1873
|
async function resolveFunctionUrl(name) {
|
|
1716
|
-
if (_configuredFunctionsBaseUrl) return `${_configuredFunctionsBaseUrl.replace(/\/$/, "")}/${
|
|
1717
|
-
return `https://${await resolveFunctionsProjectId()}.${DEFAULT_FUNCTIONS_HOST}/${
|
|
1874
|
+
if (_configuredFunctionsBaseUrl) return `${_configuredFunctionsBaseUrl.replace(/\/$/, "")}/${pathSegment(name)}`;
|
|
1875
|
+
return `https://${await resolveFunctionsProjectId()}.${DEFAULT_FUNCTIONS_HOST}/${pathSegment(name)}`;
|
|
1718
1876
|
}
|
|
1719
1877
|
async function resolveFunctionsProjectId() {
|
|
1720
1878
|
if (_resolvedFunctionsProjectId) return _resolvedFunctionsProjectId;
|
|
@@ -1873,8 +2031,10 @@ const Amba = {
|
|
|
1873
2031
|
if (explicitWasmInput !== void 0) await initCore({ module_or_path: explicitWasmInput });
|
|
1874
2032
|
else await initCore();
|
|
1875
2033
|
}
|
|
2034
|
+
const sessionPersistence = config.sessionPersistence ?? "local";
|
|
2035
|
+
if (!SESSION_PERSISTENCE_MODES.includes(sessionPersistence)) throw new AmbaApiError("INVALID_CONFIG", "Amba.configure: sessionPersistence must be \"local\" or \"memory\"");
|
|
1876
2036
|
const persistence = new LocalStoragePersistence(config.storagePrefix ?? "amba");
|
|
1877
|
-
const restoredIdentity = readPersistedIdentity(persistence, config.projectId);
|
|
2037
|
+
const restoredIdentity = sessionPersistence === "memory" ? liveIdentity(config.projectId) : readPersistedIdentity(persistence, config.projectId);
|
|
1878
2038
|
let nextCore;
|
|
1879
2039
|
try {
|
|
1880
2040
|
const { wasmClass } = await getCoreRuntime();
|
|
@@ -1899,6 +2059,8 @@ const Amba = {
|
|
|
1899
2059
|
}
|
|
1900
2060
|
await teardownOffline();
|
|
1901
2061
|
_persistence = persistence;
|
|
2062
|
+
_sessionPersistence = sessionPersistence;
|
|
2063
|
+
if (sessionPersistence === "memory") persistence.delete(identityStorageKey(config.projectId));
|
|
1902
2064
|
coreInstance = nextCore;
|
|
1903
2065
|
_configuredProjectId = config.projectId;
|
|
1904
2066
|
_configuredApiUrl = config.apiUrl ?? DEFAULT_API_URL;
|
|
@@ -1931,6 +2093,7 @@ const Amba = {
|
|
|
1931
2093
|
},
|
|
1932
2094
|
events: {
|
|
1933
2095
|
track: async (event, properties, options) => {
|
|
2096
|
+
const outboxTicket = _offlineEnabled ? getOfflineQueue().ticket() : void 0;
|
|
1934
2097
|
try {
|
|
1935
2098
|
await coreCall((c) => c.track(event, properties ? encodeJson(properties) : void 0, options?.telemetry));
|
|
1936
2099
|
} catch (err) {
|
|
@@ -1941,7 +2104,7 @@ const Amba = {
|
|
|
1941
2104
|
properties,
|
|
1942
2105
|
telemetry: options?.telemetry,
|
|
1943
2106
|
occurredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1944
|
-
});
|
|
2107
|
+
}, outboxTicket);
|
|
1945
2108
|
return;
|
|
1946
2109
|
}
|
|
1947
2110
|
throw err;
|
|
@@ -2068,6 +2231,7 @@ const Amba = {
|
|
|
2068
2231
|
_currentSessionExpiresAt = null;
|
|
2069
2232
|
_currentUser = null;
|
|
2070
2233
|
writeIdentitySnapshot();
|
|
2234
|
+
await purgeOfflineOutbox();
|
|
2071
2235
|
notifyAuthSubscribers(null);
|
|
2072
2236
|
},
|
|
2073
2237
|
refresh: async () => {
|
|
@@ -2143,6 +2307,7 @@ const Amba = {
|
|
|
2143
2307
|
...row,
|
|
2144
2308
|
idempotency_key: idempotencyKey
|
|
2145
2309
|
};
|
|
2310
|
+
const outboxTicket = _offlineEnabled ? getOfflineQueue().ticket() : void 0;
|
|
2146
2311
|
try {
|
|
2147
2312
|
return await coreCall((c) => c.collectionsInsert(name, encodeJson(wireRow)));
|
|
2148
2313
|
} catch (err) {
|
|
@@ -2152,7 +2317,7 @@ const Amba = {
|
|
|
2152
2317
|
collection: name,
|
|
2153
2318
|
payload: row,
|
|
2154
2319
|
idempotencyKey
|
|
2155
|
-
});
|
|
2320
|
+
}, outboxTicket);
|
|
2156
2321
|
return {
|
|
2157
2322
|
...row,
|
|
2158
2323
|
_amba_pending: true
|
|
@@ -2164,39 +2329,45 @@ const Amba = {
|
|
|
2164
2329
|
return await coreCall((c) => c.collectionsInsert(name, encodeJson(row)));
|
|
2165
2330
|
},
|
|
2166
2331
|
update: async (name, id, set) => {
|
|
2167
|
-
if (_offlineEnabled)
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2332
|
+
if (_offlineEnabled) {
|
|
2333
|
+
const outboxTicket = _offlineEnabled ? getOfflineQueue().ticket() : void 0;
|
|
2334
|
+
try {
|
|
2335
|
+
return await coreCall((c) => c.collectionsUpdate(name, id, encodeJson(set)));
|
|
2336
|
+
} catch (err) {
|
|
2337
|
+
if (isOfflineError(err)) {
|
|
2338
|
+
await getOfflineQueue().enqueue({
|
|
2339
|
+
kind: "collection.update",
|
|
2340
|
+
collection: name,
|
|
2341
|
+
id,
|
|
2342
|
+
payload: set
|
|
2343
|
+
}, outboxTicket);
|
|
2344
|
+
return {
|
|
2345
|
+
id,
|
|
2346
|
+
...set,
|
|
2347
|
+
_amba_pending: true
|
|
2348
|
+
};
|
|
2349
|
+
}
|
|
2350
|
+
throw err;
|
|
2182
2351
|
}
|
|
2183
|
-
throw err;
|
|
2184
2352
|
}
|
|
2185
2353
|
return await coreCall((c) => c.collectionsUpdate(name, id, encodeJson(set)));
|
|
2186
2354
|
},
|
|
2187
2355
|
delete: async (name, id) => {
|
|
2188
|
-
if (_offlineEnabled)
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2356
|
+
if (_offlineEnabled) {
|
|
2357
|
+
const outboxTicket = _offlineEnabled ? getOfflineQueue().ticket() : void 0;
|
|
2358
|
+
try {
|
|
2359
|
+
return await coreCall((c) => c.collectionsDelete(name, id));
|
|
2360
|
+
} catch (err) {
|
|
2361
|
+
if (isOfflineError(err)) {
|
|
2362
|
+
await getOfflineQueue().enqueue({
|
|
2363
|
+
kind: "collection.delete",
|
|
2364
|
+
collection: name,
|
|
2365
|
+
id
|
|
2366
|
+
}, outboxTicket);
|
|
2367
|
+
return { data: { deleted: true } };
|
|
2368
|
+
}
|
|
2369
|
+
throw err;
|
|
2198
2370
|
}
|
|
2199
|
-
throw err;
|
|
2200
2371
|
}
|
|
2201
2372
|
return await coreCall((c) => c.collectionsDelete(name, id));
|
|
2202
2373
|
},
|
|
@@ -2339,7 +2510,7 @@ const Amba = {
|
|
|
2339
2510
|
media: { catalog: async (slug) => {
|
|
2340
2511
|
if (!_configuredClientKey) throw new AmbaApiError("NOT_INITIALIZED", "amba SDK not configured. Call Amba.configure({ projectId, clientKey }) before reading a media catalog.");
|
|
2341
2512
|
if (!slug || slug.trim() === "") throw new AmbaApiError("INVALID_ARGUMENT", "Amba.media.catalog: a catalog slug is required.");
|
|
2342
|
-
const url = `${_configuredApiUrl.replace(/\/$/, "")}/v1/client/media/catalogs/${
|
|
2513
|
+
const url = `${_configuredApiUrl.replace(/\/$/, "")}/v1/client/media/catalogs/${pathSegment(slug)}`;
|
|
2343
2514
|
let resp;
|
|
2344
2515
|
try {
|
|
2345
2516
|
resp = await fetch(url, {
|
|
@@ -2674,7 +2845,7 @@ function getCoreWasm() {
|
|
|
2674
2845
|
* `verify-sdk-version-coherence` GH action gates every publish on the
|
|
2675
2846
|
* pair agreeing (and matching the `web-v<X.Y.Z>` tag when tag-triggered).
|
|
2676
2847
|
*/
|
|
2677
|
-
const SDK_VERSION = "4.0.
|
|
2848
|
+
const SDK_VERSION = "4.0.11";
|
|
2678
2849
|
|
|
2679
2850
|
//#endregion
|
|
2680
2851
|
//#region src/browser.ts
|