@layers/amba-web 4.0.8 → 4.0.10
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.browser.d.ts +89 -9
- package/dist/index.browser.d.ts.map +1 -1
- package/dist/index.browser.js +287 -53
- package/dist/index.browser.js.map +1 -1
- package/dist/index.d.ts +89 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +286 -41
- package/dist/index.js.map +1 -1
- package/dist-rn/index.d.ts +89 -9
- package/dist-rn/index.js +286 -41
- package/package.json +2 -2
package/dist-rn/index.d.ts
CHANGED
|
@@ -94,10 +94,9 @@ interface AuthResult {
|
|
|
94
94
|
/**
|
|
95
95
|
* Snapshot of the current session as visible to the SDK consumer.
|
|
96
96
|
*
|
|
97
|
-
* `sessionToken` / `refreshToken`
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
* introspect should branch on `user` / `Amba.isAuthenticated`.
|
|
97
|
+
* `sessionToken` / `refreshToken` are live credentials from the SDK-managed
|
|
98
|
+
* durable browser session. Do not log them or duplicate them into app-owned
|
|
99
|
+
* storage. Callers that only need auth state should use `Amba.isAuthenticated`.
|
|
101
100
|
*/
|
|
102
101
|
interface Session {
|
|
103
102
|
sessionToken: string;
|
|
@@ -656,12 +655,17 @@ interface GroupCreate {
|
|
|
656
655
|
name: string;
|
|
657
656
|
description?: string;
|
|
658
657
|
avatar_url?: string;
|
|
658
|
+
/** Set false for a private group that can own group-scoped collection rows. */
|
|
659
|
+
is_public?: boolean;
|
|
660
|
+
max_members?: number;
|
|
659
661
|
metadata?: unknown;
|
|
660
662
|
}
|
|
661
663
|
interface GroupUpdate {
|
|
662
664
|
name?: string;
|
|
663
665
|
description?: string;
|
|
664
666
|
avatar_url?: string;
|
|
667
|
+
is_public?: boolean;
|
|
668
|
+
max_members?: number;
|
|
665
669
|
metadata?: unknown;
|
|
666
670
|
}
|
|
667
671
|
type ConversationType = "direct" | "group";
|
|
@@ -1396,6 +1400,69 @@ interface InvokeOptions {
|
|
|
1396
1400
|
*/
|
|
1397
1401
|
onTraceId?: (traceId: string | null) => void;
|
|
1398
1402
|
}
|
|
1403
|
+
/**
|
|
1404
|
+
* Immutable browser collection scope for a server-verified group context.
|
|
1405
|
+
* Each method carries its own `groupId` into the Rust request path; no mutable
|
|
1406
|
+
* global header state is used, so parallel group A/group B calls cannot leak.
|
|
1407
|
+
*
|
|
1408
|
+
* Group-scoped writes deliberately bypass the global offline outbox. Replaying
|
|
1409
|
+
* a queued group mutation without its membership context would be unsafe;
|
|
1410
|
+
* callers receive the real network outcome and can retry in the same scope.
|
|
1411
|
+
*/
|
|
1412
|
+
declare class AmbaGroupScopedClient {
|
|
1413
|
+
readonly groupId: string;
|
|
1414
|
+
constructor(groupId: string);
|
|
1415
|
+
asGroup(groupId: string): AmbaGroupScopedClient;
|
|
1416
|
+
inGroup(groupId: string): AmbaGroupScopedClient;
|
|
1417
|
+
collection<T = unknown>(name: string): CollectionSubscription<T>;
|
|
1418
|
+
collections: {
|
|
1419
|
+
find: <T = unknown>(name: string, options?: FindOptions) => Promise<FindResponse<T>>;
|
|
1420
|
+
findOne: <T = unknown>(name: string, id: string) => Promise<T>;
|
|
1421
|
+
insert: <T = unknown>(name: string, row: Record<string, unknown>) => Promise<T>;
|
|
1422
|
+
update: <T = unknown>(name: string, id: string, set: Record<string, unknown>) => Promise<T>;
|
|
1423
|
+
delete: (name: string, id: string) => Promise<{
|
|
1424
|
+
data: {
|
|
1425
|
+
deleted: boolean;
|
|
1426
|
+
};
|
|
1427
|
+
}>;
|
|
1428
|
+
upsert: <T = unknown>(name: string, row: Record<string, unknown>, options: UpsertOptions) => Promise<T>;
|
|
1429
|
+
deleteWhere: (name: string, filter: Filter) => Promise<DeleteManyResult>;
|
|
1430
|
+
restore: <T = unknown>(name: string, id: string) => Promise<T>;
|
|
1431
|
+
transaction: (operations: TransactionOp[]) => Promise<TransactionOpResult[]>;
|
|
1432
|
+
findNearest: <T = unknown>(name: string, options: NearestOptions) => Promise<NearestResponse<T>>;
|
|
1433
|
+
count: (name: string, filter?: Filter) => Promise<number>;
|
|
1434
|
+
optimisticInsert: <T = unknown, TApply = unknown>(name: string, row: Record<string, unknown>, view: {
|
|
1435
|
+
apply: () => TApply;
|
|
1436
|
+
rollback: (applied: TApply) => void;
|
|
1437
|
+
}) => Promise<T>;
|
|
1438
|
+
optimisticUpdate: <T = unknown, TApply = unknown>(name: string, id: string, set: Record<string, unknown>, view: {
|
|
1439
|
+
apply: () => TApply;
|
|
1440
|
+
rollback: (applied: TApply) => void;
|
|
1441
|
+
}) => Promise<T>;
|
|
1442
|
+
optimisticDelete: <TApply = unknown>(name: string, id: string, view: {
|
|
1443
|
+
apply: () => TApply;
|
|
1444
|
+
rollback: (applied: TApply) => void;
|
|
1445
|
+
}) => Promise<{
|
|
1446
|
+
data: {
|
|
1447
|
+
deleted: boolean;
|
|
1448
|
+
};
|
|
1449
|
+
}>;
|
|
1450
|
+
where: {
|
|
1451
|
+
eq: (column: string, value: FilterValue) => Filter;
|
|
1452
|
+
ne: (column: string, value: FilterValue) => Filter;
|
|
1453
|
+
gt: (column: string, value: FilterValue) => Filter;
|
|
1454
|
+
gte: (column: string, value: FilterValue) => Filter;
|
|
1455
|
+
lt: (column: string, value: FilterValue) => Filter;
|
|
1456
|
+
lte: (column: string, value: FilterValue) => Filter;
|
|
1457
|
+
in: (column: string, values: FilterValue[]) => Filter;
|
|
1458
|
+
isNull: (column: string) => Filter;
|
|
1459
|
+
isNotNull: (column: string) => Filter;
|
|
1460
|
+
and: (...filters: Filter[]) => Filter;
|
|
1461
|
+
or: (...filters: Filter[]) => Filter;
|
|
1462
|
+
not: (filter: Filter) => Filter;
|
|
1463
|
+
};
|
|
1464
|
+
};
|
|
1465
|
+
}
|
|
1399
1466
|
/**
|
|
1400
1467
|
* Top-level SDK namespace.
|
|
1401
1468
|
*
|
|
@@ -1623,9 +1690,9 @@ declare const Amba: {
|
|
|
1623
1690
|
me: () => Promise<User>;
|
|
1624
1691
|
/**
|
|
1625
1692
|
* Snapshot the current session, or `null` if no session is live.
|
|
1626
|
-
*
|
|
1627
|
-
*
|
|
1628
|
-
*
|
|
1693
|
+
* Tokens are returned from the browser's durable session snapshot. Treat
|
|
1694
|
+
* them as credentials: do not log them or copy them into app-managed
|
|
1695
|
+
* storage.
|
|
1629
1696
|
*/
|
|
1630
1697
|
getSession: () => Promise<Session | null>;
|
|
1631
1698
|
/**
|
|
@@ -1680,6 +1747,15 @@ declare const Amba: {
|
|
|
1680
1747
|
*/
|
|
1681
1748
|
revoke: (sessionId: string) => Promise<void>;
|
|
1682
1749
|
};
|
|
1750
|
+
/**
|
|
1751
|
+
* Scope collection calls to a group the current session belongs to.
|
|
1752
|
+
* Membership and role are verified by the API; an unknown/unauthorized
|
|
1753
|
+
* group is rejected server-side. The returned caller is immutable and safe
|
|
1754
|
+
* to use concurrently with callers scoped to other groups.
|
|
1755
|
+
*/
|
|
1756
|
+
asGroup: (groupId: string) => AmbaGroupScopedClient;
|
|
1757
|
+
/** Alias of {@link Amba.asGroup}. */
|
|
1758
|
+
inGroup: (groupId: string) => AmbaGroupScopedClient;
|
|
1683
1759
|
/**
|
|
1684
1760
|
* Live collection subscriptions (SDK 4.0). Returns a builder — chain
|
|
1685
1761
|
* `.where(column, value)` to narrow, then `.subscribe(cb)` to start
|
|
@@ -2063,6 +2139,10 @@ declare const Amba: {
|
|
|
2063
2139
|
* hand-rolling `fetch` against a function URL, which leaves the call
|
|
2064
2140
|
* unauthenticated (a footgun when the function holds secrets).
|
|
2065
2141
|
*
|
|
2142
|
+
* The function host is selected in order from `functionsBaseUrl`, a
|
|
2143
|
+
* UUID-shaped configured project id, or a deduped diagnostics ping that
|
|
2144
|
+
* supplies the authoritative server-side project id.
|
|
2145
|
+
*
|
|
2066
2146
|
* The return value is unchanged (the parsed body, typed `T`). To
|
|
2067
2147
|
* correlate a run with server logs, read the trace id additively:
|
|
2068
2148
|
* pass `options.onTraceId` to receive the `X-Amba-Request-Id` on every
|
|
@@ -2431,7 +2511,7 @@ declare function getCoreWasm(): AmbaCoreWasm;
|
|
|
2431
2511
|
* `verify-sdk-version-coherence` GH action gates every publish on the
|
|
2432
2512
|
* pair agreeing (and matching the `web-v<X.Y.Z>` tag when tag-triggered).
|
|
2433
2513
|
*/
|
|
2434
|
-
declare const SDK_VERSION = "4.0.
|
|
2514
|
+
declare const SDK_VERSION = "4.0.10";
|
|
2435
2515
|
//#endregion
|
|
2436
|
-
export { Achievement, AchievementProgress, AiContentBlock, AiImageInput, AiMessage, AiMessageRequest, AiMessageResponse, type AiStreamDelta, type AiStreamDone, type AiStreamEvent, type AiStreamOptions, AiUsage, Amba, AmbaApiError, type AmbaApiErrorCode, AmbaConfig, AndFilter, AppSession, AuthResult, CatalogItem, Challenge, ChallengeProgress, CollectionSubscription, ConditionFilter, ConfigBundle, ConsumeRequest, ContentItem, Conversation, ConversationParticipant, ConversationSubscription, ConversationType, CreateConversationRequest, CurrencyBalance, CurrencyTransaction, DeepLink, DeepLinkCreate, DeleteManyResult, DiagnosticsEnvironment, EntitlementPeriodType, EntitlementStore, EventExplainResult, ExplainMatchedRule, FeedItem, FeedResponse, Filter, FilterValue, FindOptions, FindResponse, FlagAssignment, type FlushResult, Friendship, FriendshipState, type GamificationChange, type GamificationKind, GamificationSubscription, Group, GroupCreate, GroupMember, GroupUpdate, IncludeClause, InventoryItem, InvokeOptions, type KvStore, Leaderboard, LeaderboardEntry, LeagueCohort, LeagueCohortMember, LeagueCohortView, LeagueMembership, LeagueTier, LinkedIdentifier, ListMessagesOptions, LocalNotificationHandle, LocalNotificationTrigger, type LocalNotificationsNamespace, MarkReadResult, MediaAsset, MediaCatalog, MediaCatalogItem, MediaListResponse, Message, type MessageChange, MessageType, NearestOptions, NearestResponse, NotFilter, Offering, OfferingPackage, OfferingProduct, OfferingsResult, OnboardingStatus, OrFilter, OrderBy, type OutboxOp, type OutboxOpKind, PingResult, PresignData, PullChangesCursor, PullChangesResult, PurchaseOption, PurchaseRequest, PurchaseResult, PushChangesResult, PushPlatform, PushToken, type RealtimeChange, ReferralClaim, ReferralCode, Report, ReportRequest, RestoreResult, Review, ReviewCreate, ReviewUpdate, Role, type RowChangeOp, SDK_VERSION, ScheduleLocalNotificationInput, ScheduledLocalNotification, SendMessageRequest, Session, SocialProvider, Store, Streak, type SubscribeOptions, SyncChange, SyncConflict, TransactionOp, TransactionOpResult, type Unsubscribe, UpsertOptions, User, UserEntitlement, XpBalance, XpTransaction, getCoreWasm, withOptimistic };
|
|
2516
|
+
export { Achievement, AchievementProgress, AiContentBlock, AiImageInput, AiMessage, AiMessageRequest, AiMessageResponse, type AiStreamDelta, type AiStreamDone, type AiStreamEvent, type AiStreamOptions, AiUsage, Amba, AmbaApiError, type AmbaApiErrorCode, AmbaConfig, AmbaGroupScopedClient, AndFilter, AppSession, AuthResult, CatalogItem, Challenge, ChallengeProgress, CollectionSubscription, ConditionFilter, ConfigBundle, ConsumeRequest, ContentItem, Conversation, ConversationParticipant, ConversationSubscription, ConversationType, CreateConversationRequest, CurrencyBalance, CurrencyTransaction, DeepLink, DeepLinkCreate, DeleteManyResult, DiagnosticsEnvironment, EntitlementPeriodType, EntitlementStore, EventExplainResult, ExplainMatchedRule, FeedItem, FeedResponse, Filter, FilterValue, FindOptions, FindResponse, FlagAssignment, type FlushResult, Friendship, FriendshipState, type GamificationChange, type GamificationKind, GamificationSubscription, Group, GroupCreate, GroupMember, GroupUpdate, IncludeClause, InventoryItem, InvokeOptions, type KvStore, Leaderboard, LeaderboardEntry, LeagueCohort, LeagueCohortMember, LeagueCohortView, LeagueMembership, LeagueTier, LinkedIdentifier, ListMessagesOptions, LocalNotificationHandle, LocalNotificationTrigger, type LocalNotificationsNamespace, MarkReadResult, MediaAsset, MediaCatalog, MediaCatalogItem, MediaListResponse, Message, type MessageChange, MessageType, NearestOptions, NearestResponse, NotFilter, Offering, OfferingPackage, OfferingProduct, OfferingsResult, OnboardingStatus, OrFilter, OrderBy, type OutboxOp, type OutboxOpKind, PingResult, PresignData, PullChangesCursor, PullChangesResult, PurchaseOption, PurchaseRequest, PurchaseResult, PushChangesResult, PushPlatform, PushToken, type RealtimeChange, ReferralClaim, ReferralCode, Report, ReportRequest, RestoreResult, Review, ReviewCreate, ReviewUpdate, Role, type RowChangeOp, SDK_VERSION, ScheduleLocalNotificationInput, ScheduledLocalNotification, SendMessageRequest, Session, SocialProvider, Store, Streak, type SubscribeOptions, SyncChange, SyncConflict, TransactionOp, TransactionOpResult, type Unsubscribe, UpsertOptions, User, UserEntitlement, XpBalance, XpTransaction, getCoreWasm, withOptimistic };
|
|
2437
2517
|
//# sourceMappingURL=index.d.ts.map
|
package/dist-rn/index.js
CHANGED
|
@@ -2,28 +2,46 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Browser localStorage adapter for SDK identity persistence.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* (eventual) identity cache.
|
|
5
|
+
* Browsers can deny or throw on localStorage access (privacy mode, sandboxed
|
|
6
|
+
* iframes, quota exhaustion). Persistence is best-effort: storage failures
|
|
7
|
+
* must never turn a successful auth/network operation into an SDK failure.
|
|
9
8
|
*/
|
|
10
9
|
var LocalStoragePersistence = class {
|
|
11
10
|
constructor(prefix) {
|
|
12
11
|
this.prefix = prefix;
|
|
13
12
|
}
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
get storage() {
|
|
14
|
+
try {
|
|
15
|
+
return globalThis.localStorage ?? null;
|
|
16
|
+
} catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
get available() {
|
|
21
|
+
return this.storage !== null;
|
|
22
|
+
}
|
|
16
23
|
read(key) {
|
|
17
|
-
|
|
18
|
-
|
|
24
|
+
const storage = this.storage;
|
|
25
|
+
if (!storage) return null;
|
|
26
|
+
try {
|
|
27
|
+
return storage.getItem(`${this.prefix}:${key}`);
|
|
28
|
+
} catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
19
31
|
}
|
|
20
32
|
write(key, value) {
|
|
21
|
-
|
|
22
|
-
|
|
33
|
+
const storage = this.storage;
|
|
34
|
+
if (!storage) return;
|
|
35
|
+
try {
|
|
36
|
+
storage.setItem(`${this.prefix}:${key}`, value);
|
|
37
|
+
} catch {}
|
|
23
38
|
}
|
|
24
39
|
delete(key) {
|
|
25
|
-
|
|
26
|
-
|
|
40
|
+
const storage = this.storage;
|
|
41
|
+
if (!storage) return;
|
|
42
|
+
try {
|
|
43
|
+
storage.removeItem(`${this.prefix}:${key}`);
|
|
44
|
+
} catch {}
|
|
27
45
|
}
|
|
28
46
|
};
|
|
29
47
|
|
|
@@ -1340,11 +1358,10 @@ const DEFAULT_API_URL = "https://api.amba.dev";
|
|
|
1340
1358
|
/**
|
|
1341
1359
|
* Default host that serves deployed functions. `Amba.functions.invoke(name)`
|
|
1342
1360
|
* targets `https://<projectId>.<DEFAULT_FUNCTIONS_HOST>/<name>` unless a
|
|
1343
|
-
* `functionsBaseUrl` override is configured.
|
|
1344
|
-
* server-resolved id (from `diagnostics.ping`), which the function router
|
|
1345
|
-
* accepts directly.
|
|
1361
|
+
* `functionsBaseUrl` override is configured.
|
|
1346
1362
|
*/
|
|
1347
1363
|
const DEFAULT_FUNCTIONS_HOST = "fn.amba.host";
|
|
1364
|
+
const PROJECT_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1348
1365
|
/**
|
|
1349
1366
|
* Internal state — one instance per page. Created lazily by `Amba.configure()`.
|
|
1350
1367
|
*/
|
|
@@ -1366,15 +1383,73 @@ let _configuredProjectId = null;
|
|
|
1366
1383
|
let _configuredApiUrl = DEFAULT_API_URL;
|
|
1367
1384
|
let _configuredClientKey = null;
|
|
1368
1385
|
let _currentSessionToken = null;
|
|
1386
|
+
let _currentRefreshToken = null;
|
|
1387
|
+
let _currentSessionExpiresAt = null;
|
|
1388
|
+
let _currentUser = null;
|
|
1389
|
+
const BROWSER_IDENTITY_VERSION = 1;
|
|
1390
|
+
const DEFAULT_SESSION_TTL_MS = 3600 * 1e3;
|
|
1391
|
+
function identityStorageKey(projectId) {
|
|
1392
|
+
return `identity:v${BROWSER_IDENTITY_VERSION}:project:${encodeURIComponent(projectId)}`;
|
|
1393
|
+
}
|
|
1394
|
+
function isNonEmptyString(value) {
|
|
1395
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
1396
|
+
}
|
|
1397
|
+
function containsControlCharacter(value) {
|
|
1398
|
+
return Array.from(value).some((char) => {
|
|
1399
|
+
const code = char.charCodeAt(0);
|
|
1400
|
+
return code <= 31 || code === 127;
|
|
1401
|
+
});
|
|
1402
|
+
}
|
|
1403
|
+
/** Read + validate a project-bound identity snapshot. Corrupt or cross-project
|
|
1404
|
+
* data is removed instead of being handed to the core. */
|
|
1405
|
+
function readPersistedIdentity(persistence, projectId) {
|
|
1406
|
+
const key = identityStorageKey(projectId);
|
|
1407
|
+
const raw = persistence.read(key);
|
|
1408
|
+
if (!raw) return null;
|
|
1409
|
+
try {
|
|
1410
|
+
const value = JSON.parse(raw);
|
|
1411
|
+
const hasTokens = value.session_token != null || value.refresh_token != null;
|
|
1412
|
+
if (value.version !== BROWSER_IDENTITY_VERSION || value.project_id !== projectId || !isNonEmptyString(value.anonymous_id) || hasTokens && !isNonEmptyString(value.app_user_id) || value.session_token != null && !isNonEmptyString(value.refresh_token) || value.session_token != null && !isNonEmptyString(value.session_token) || value.refresh_token != null && !isNonEmptyString(value.refresh_token) || value.expires_at != null && (!isNonEmptyString(value.expires_at) || !Number.isFinite(Date.parse(value.expires_at)))) throw new Error("invalid identity snapshot");
|
|
1413
|
+
const expiresAt = isNonEmptyString(value.expires_at) ? Date.parse(value.expires_at) : NaN;
|
|
1414
|
+
const sessionExpired = value.session_token != null && Number.isFinite(expiresAt) && expiresAt <= Date.now();
|
|
1415
|
+
return {
|
|
1416
|
+
version: BROWSER_IDENTITY_VERSION,
|
|
1417
|
+
project_id: projectId,
|
|
1418
|
+
anonymous_id: value.anonymous_id,
|
|
1419
|
+
app_user_id: isNonEmptyString(value.app_user_id) ? value.app_user_id : null,
|
|
1420
|
+
session_token: sessionExpired ? null : value.session_token ?? null,
|
|
1421
|
+
refresh_token: value.refresh_token ?? null,
|
|
1422
|
+
expires_at: sessionExpired ? null : value.expires_at ?? null,
|
|
1423
|
+
user: value.user && typeof value.user === "object" && isNonEmptyString(value.user.id) ? value.user : null
|
|
1424
|
+
};
|
|
1425
|
+
} catch {
|
|
1426
|
+
persistence.delete(key);
|
|
1427
|
+
return null;
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
function writeIdentitySnapshot() {
|
|
1431
|
+
if (!_persistence?.available || !_configuredProjectId || !coreInstance) return;
|
|
1432
|
+
const snapshot = {
|
|
1433
|
+
version: BROWSER_IDENTITY_VERSION,
|
|
1434
|
+
project_id: _configuredProjectId,
|
|
1435
|
+
anonymous_id: coreInstance.anonymousId(),
|
|
1436
|
+
app_user_id: coreInstance.appUserId() ?? null,
|
|
1437
|
+
session_token: _currentSessionToken,
|
|
1438
|
+
refresh_token: _currentRefreshToken,
|
|
1439
|
+
expires_at: _currentSessionExpiresAt,
|
|
1440
|
+
user: _currentUser
|
|
1441
|
+
};
|
|
1442
|
+
_persistence.write(identityStorageKey(_configuredProjectId), JSON.stringify(snapshot));
|
|
1443
|
+
}
|
|
1369
1444
|
/**
|
|
1370
1445
|
* Functions invocation state. `_configuredFunctionsBaseUrl` is the optional
|
|
1371
|
-
* override from `Amba.configure({ functionsBaseUrl })`.
|
|
1372
|
-
*
|
|
1373
|
-
*
|
|
1374
|
-
* round-trip. Both reset on every `configure()`.
|
|
1446
|
+
* override from `Amba.configure({ functionsBaseUrl })`. The default-host path
|
|
1447
|
+
* caches its resolved project id and shares an in-flight diagnostics fallback
|
|
1448
|
+
* across concurrent invokes. All three reset on every `configure()`.
|
|
1375
1449
|
*/
|
|
1376
1450
|
let _configuredFunctionsBaseUrl = null;
|
|
1377
1451
|
let _resolvedFunctionsProjectId = null;
|
|
1452
|
+
let _functionsProjectIdInflight = null;
|
|
1378
1453
|
/**
|
|
1379
1454
|
* Offline-resilience state. `_persistence` is the localStorage-backed store
|
|
1380
1455
|
* captured at configure-time; `_offlineQueue` is lazily built the first time
|
|
@@ -1442,7 +1517,7 @@ async function replayOutboxOp(op) {
|
|
|
1442
1517
|
switch (op.kind) {
|
|
1443
1518
|
case "collection.insert":
|
|
1444
1519
|
await coreCall((c) => c.collectionsInsert(op.collection, encodeJson({
|
|
1445
|
-
...op.payload
|
|
1520
|
+
...op.payload,
|
|
1446
1521
|
idempotency_key: op.idempotencyKey
|
|
1447
1522
|
})));
|
|
1448
1523
|
return;
|
|
@@ -1563,12 +1638,12 @@ function toWireTransactionOp(op) {
|
|
|
1563
1638
|
return wire;
|
|
1564
1639
|
}
|
|
1565
1640
|
function snapshotSession(user) {
|
|
1566
|
-
if (!user) return null;
|
|
1641
|
+
if (!user || !_currentSessionToken) return null;
|
|
1567
1642
|
return {
|
|
1568
|
-
sessionToken:
|
|
1569
|
-
refreshToken: "",
|
|
1643
|
+
sessionToken: _currentSessionToken,
|
|
1644
|
+
refreshToken: _currentRefreshToken ?? "",
|
|
1570
1645
|
user,
|
|
1571
|
-
expiresAt: ""
|
|
1646
|
+
expiresAt: _currentSessionExpiresAt ?? ""
|
|
1572
1647
|
};
|
|
1573
1648
|
}
|
|
1574
1649
|
function notifyAuthSubscribers(session) {
|
|
@@ -1579,7 +1654,11 @@ function notifyAuthSubscribers(session) {
|
|
|
1579
1654
|
}
|
|
1580
1655
|
}
|
|
1581
1656
|
async function notifyFromAuthResult(result) {
|
|
1582
|
-
|
|
1657
|
+
_currentSessionToken = result.session_token;
|
|
1658
|
+
_currentRefreshToken = result.refresh_token;
|
|
1659
|
+
_currentSessionExpiresAt = result.expires_at && Number.isFinite(Date.parse(result.expires_at)) ? result.expires_at : new Date(Date.now() + DEFAULT_SESSION_TTL_MS).toISOString();
|
|
1660
|
+
_currentUser = result.user;
|
|
1661
|
+
writeIdentitySnapshot();
|
|
1583
1662
|
notifyAuthSubscribers(snapshotSession(result.user));
|
|
1584
1663
|
}
|
|
1585
1664
|
/**
|
|
@@ -1626,22 +1705,156 @@ function buildAiStreamRequest(path, request) {
|
|
|
1626
1705
|
/**
|
|
1627
1706
|
* Resolve the full invocation URL for a deployed function.
|
|
1628
1707
|
*
|
|
1629
|
-
*
|
|
1630
|
-
*
|
|
1631
|
-
*
|
|
1632
|
-
*
|
|
1633
|
-
* project id as the host label directly.
|
|
1708
|
+
* Resolution checks `functionsBaseUrl` first, then uses a UUID-shaped
|
|
1709
|
+
* configured project id directly, and finally falls back to a deduped
|
|
1710
|
+
* `diagnostics.ping` for the authoritative server-side project id. The
|
|
1711
|
+
* resolved default-host id is cached for subsequent invokes.
|
|
1634
1712
|
*/
|
|
1635
1713
|
async function resolveFunctionUrl(name) {
|
|
1636
1714
|
if (_configuredFunctionsBaseUrl) return `${_configuredFunctionsBaseUrl.replace(/\/$/, "")}/${encodeURIComponent(name)}`;
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1715
|
+
return `https://${await resolveFunctionsProjectId()}.${DEFAULT_FUNCTIONS_HOST}/${encodeURIComponent(name)}`;
|
|
1716
|
+
}
|
|
1717
|
+
async function resolveFunctionsProjectId() {
|
|
1718
|
+
if (_resolvedFunctionsProjectId) return _resolvedFunctionsProjectId;
|
|
1719
|
+
if (_configuredProjectId && PROJECT_ID_PATTERN.test(_configuredProjectId)) {
|
|
1720
|
+
_resolvedFunctionsProjectId = _configuredProjectId.toLowerCase();
|
|
1721
|
+
return _resolvedFunctionsProjectId;
|
|
1641
1722
|
}
|
|
1642
|
-
|
|
1723
|
+
if (!_functionsProjectIdInflight) {
|
|
1724
|
+
const configuredProjectId = _configuredProjectId;
|
|
1725
|
+
const resolution = (async () => {
|
|
1726
|
+
const ping = await Amba.diagnostics.ping();
|
|
1727
|
+
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);
|
|
1728
|
+
if (_configuredProjectId === configuredProjectId && !_configuredFunctionsBaseUrl) _resolvedFunctionsProjectId = ping.server_project_id;
|
|
1729
|
+
return ping.server_project_id;
|
|
1730
|
+
})();
|
|
1731
|
+
_functionsProjectIdInflight = resolution;
|
|
1732
|
+
resolution.then(() => {
|
|
1733
|
+
if (_functionsProjectIdInflight === resolution) _functionsProjectIdInflight = null;
|
|
1734
|
+
}, () => {
|
|
1735
|
+
if (_functionsProjectIdInflight === resolution) _functionsProjectIdInflight = null;
|
|
1736
|
+
});
|
|
1737
|
+
}
|
|
1738
|
+
return _functionsProjectIdInflight;
|
|
1643
1739
|
}
|
|
1644
1740
|
/**
|
|
1741
|
+
* Immutable browser collection scope for a server-verified group context.
|
|
1742
|
+
* Each method carries its own `groupId` into the Rust request path; no mutable
|
|
1743
|
+
* global header state is used, so parallel group A/group B calls cannot leak.
|
|
1744
|
+
*
|
|
1745
|
+
* Group-scoped writes deliberately bypass the global offline outbox. Replaying
|
|
1746
|
+
* a queued group mutation without its membership context would be unsafe;
|
|
1747
|
+
* callers receive the real network outcome and can retry in the same scope.
|
|
1748
|
+
*/
|
|
1749
|
+
var AmbaGroupScopedClient = class AmbaGroupScopedClient {
|
|
1750
|
+
groupId;
|
|
1751
|
+
constructor(groupId) {
|
|
1752
|
+
const normalized = groupId.trim();
|
|
1753
|
+
if (!normalized || containsControlCharacter(normalized)) throw new AmbaApiError("VALIDATION_ERROR", "Amba.asGroup: groupId must be non-empty and contain no control characters");
|
|
1754
|
+
this.groupId = normalized;
|
|
1755
|
+
}
|
|
1756
|
+
asGroup(groupId) {
|
|
1757
|
+
return new AmbaGroupScopedClient(groupId);
|
|
1758
|
+
}
|
|
1759
|
+
inGroup(groupId) {
|
|
1760
|
+
return this.asGroup(groupId);
|
|
1761
|
+
}
|
|
1762
|
+
collection(name) {
|
|
1763
|
+
const groupId = this.groupId;
|
|
1764
|
+
return new CollectionSubscription({
|
|
1765
|
+
baseUrl: _configuredApiUrl,
|
|
1766
|
+
authHeaders: () => {
|
|
1767
|
+
const headers = realtimeAuthHeaders();
|
|
1768
|
+
return headers ? {
|
|
1769
|
+
...headers,
|
|
1770
|
+
"X-Group-Context": groupId
|
|
1771
|
+
} : null;
|
|
1772
|
+
}
|
|
1773
|
+
}, name);
|
|
1774
|
+
}
|
|
1775
|
+
collections = {
|
|
1776
|
+
find: async (name, options = {}) => await coreCall((c) => c.collectionsFind(name, encodeJson(options), this.groupId)),
|
|
1777
|
+
findOne: async (name, id) => await coreCall((c) => c.collectionsFindOne(name, id, this.groupId)),
|
|
1778
|
+
insert: async (name, row) => await coreCall((c) => c.collectionsInsert(name, encodeJson(row), this.groupId)),
|
|
1779
|
+
update: async (name, id, set) => await coreCall((c) => c.collectionsUpdate(name, id, encodeJson(set), this.groupId)),
|
|
1780
|
+
delete: async (name, id) => await coreCall((c) => c.collectionsDelete(name, id, this.groupId)),
|
|
1781
|
+
upsert: async (name, row, options) => await coreCall((c) => c.collectionsUpsert(name, encodeJson(row), encodeJson({
|
|
1782
|
+
on_conflict: options.onConflict,
|
|
1783
|
+
conflict_target: options.conflictTarget
|
|
1784
|
+
}), this.groupId)),
|
|
1785
|
+
deleteWhere: async (name, filter) => await coreCall((c) => c.collectionsDeleteWhere(name, encodeJson(filter), this.groupId)),
|
|
1786
|
+
restore: async (name, id) => await coreCall((c) => c.collectionsRestore(name, id, this.groupId)),
|
|
1787
|
+
transaction: async (operations) => {
|
|
1788
|
+
return (await coreCall((c) => c.collectionsTransaction(encodeJson(operations.map(toWireTransactionOp)), this.groupId))).data;
|
|
1789
|
+
},
|
|
1790
|
+
findNearest: async (name, options) => await coreCall((c) => c.collectionsFindNearest(name, encodeJson(options), this.groupId)),
|
|
1791
|
+
count: async (name, filter) => await coreCall((c) => c.collectionsCount(name, filter ? encodeJson(filter) : void 0, this.groupId)),
|
|
1792
|
+
optimisticInsert: async (name, row, view) => withOptimistic({
|
|
1793
|
+
apply: view.apply,
|
|
1794
|
+
mutate: () => coreCall((c) => c.collectionsInsert(name, encodeJson(row), this.groupId)),
|
|
1795
|
+
rollback: view.rollback
|
|
1796
|
+
}),
|
|
1797
|
+
optimisticUpdate: async (name, id, set, view) => withOptimistic({
|
|
1798
|
+
apply: view.apply,
|
|
1799
|
+
mutate: () => coreCall((c) => c.collectionsUpdate(name, id, encodeJson(set), this.groupId)),
|
|
1800
|
+
rollback: view.rollback
|
|
1801
|
+
}),
|
|
1802
|
+
optimisticDelete: async (name, id, view) => withOptimistic({
|
|
1803
|
+
apply: view.apply,
|
|
1804
|
+
mutate: () => coreCall((c) => c.collectionsDelete(name, id, this.groupId)),
|
|
1805
|
+
rollback: view.rollback
|
|
1806
|
+
}),
|
|
1807
|
+
where: {
|
|
1808
|
+
eq: (column, value) => ({
|
|
1809
|
+
column,
|
|
1810
|
+
op: "eq",
|
|
1811
|
+
value
|
|
1812
|
+
}),
|
|
1813
|
+
ne: (column, value) => ({
|
|
1814
|
+
column,
|
|
1815
|
+
op: "ne",
|
|
1816
|
+
value
|
|
1817
|
+
}),
|
|
1818
|
+
gt: (column, value) => ({
|
|
1819
|
+
column,
|
|
1820
|
+
op: "gt",
|
|
1821
|
+
value
|
|
1822
|
+
}),
|
|
1823
|
+
gte: (column, value) => ({
|
|
1824
|
+
column,
|
|
1825
|
+
op: "gte",
|
|
1826
|
+
value
|
|
1827
|
+
}),
|
|
1828
|
+
lt: (column, value) => ({
|
|
1829
|
+
column,
|
|
1830
|
+
op: "lt",
|
|
1831
|
+
value
|
|
1832
|
+
}),
|
|
1833
|
+
lte: (column, value) => ({
|
|
1834
|
+
column,
|
|
1835
|
+
op: "lte",
|
|
1836
|
+
value
|
|
1837
|
+
}),
|
|
1838
|
+
in: (column, values) => ({
|
|
1839
|
+
column,
|
|
1840
|
+
op: "in",
|
|
1841
|
+
value: values
|
|
1842
|
+
}),
|
|
1843
|
+
isNull: (column) => ({
|
|
1844
|
+
column,
|
|
1845
|
+
op: "is_null"
|
|
1846
|
+
}),
|
|
1847
|
+
isNotNull: (column) => ({
|
|
1848
|
+
column,
|
|
1849
|
+
op: "is_not_null"
|
|
1850
|
+
}),
|
|
1851
|
+
and: (...filters) => ({ and: filters }),
|
|
1852
|
+
or: (...filters) => ({ or: filters }),
|
|
1853
|
+
not: (filter) => ({ not: filter })
|
|
1854
|
+
}
|
|
1855
|
+
};
|
|
1856
|
+
};
|
|
1857
|
+
/**
|
|
1645
1858
|
* Top-level SDK namespace.
|
|
1646
1859
|
*
|
|
1647
1860
|
* Stateless from the JS side — all state lives in the SDK runtime. Calling
|
|
@@ -1659,7 +1872,7 @@ const Amba = {
|
|
|
1659
1872
|
else await initCore(); // Hermes-stripped Node-init block (DX-15) — see scripts/build-rn.mjs
|
|
1660
1873
|
}
|
|
1661
1874
|
const persistence = new LocalStoragePersistence(config.storagePrefix ?? "amba");
|
|
1662
|
-
persistence.
|
|
1875
|
+
const restoredIdentity = readPersistedIdentity(persistence, config.projectId);
|
|
1663
1876
|
let nextCore;
|
|
1664
1877
|
try {
|
|
1665
1878
|
const { wasmClass } = await getCoreRuntime();
|
|
@@ -1672,6 +1885,13 @@ const Amba = {
|
|
|
1672
1885
|
consent_required: config.consentRequired,
|
|
1673
1886
|
debug: config.debug
|
|
1674
1887
|
}), globalThis.fetch.bind(globalThis));
|
|
1888
|
+
if (restoredIdentity) nextCore.hydrateIdentity(encodeJson({
|
|
1889
|
+
anonymous_id: restoredIdentity.anonymous_id,
|
|
1890
|
+
app_user_id: restoredIdentity.app_user_id,
|
|
1891
|
+
session_token: restoredIdentity.session_token,
|
|
1892
|
+
refresh_token: restoredIdentity.refresh_token,
|
|
1893
|
+
expires_at: restoredIdentity.expires_at
|
|
1894
|
+
}));
|
|
1675
1895
|
} catch (err) {
|
|
1676
1896
|
throw toAmbaApiError(err);
|
|
1677
1897
|
}
|
|
@@ -1681,8 +1901,15 @@ const Amba = {
|
|
|
1681
1901
|
_configuredProjectId = config.projectId;
|
|
1682
1902
|
_configuredApiUrl = config.apiUrl ?? DEFAULT_API_URL;
|
|
1683
1903
|
_configuredClientKey = config.clientKey;
|
|
1904
|
+
_currentSessionToken = nextCore.isAuthenticated() ? restoredIdentity?.session_token ?? null : null;
|
|
1905
|
+
_currentRefreshToken = restoredIdentity?.refresh_token ?? null;
|
|
1906
|
+
_currentSessionExpiresAt = nextCore.isAuthenticated() ? restoredIdentity?.expires_at ?? null : null;
|
|
1907
|
+
_currentUser = nextCore.isAuthenticated() ? restoredIdentity?.user ?? null : null;
|
|
1684
1908
|
_configuredFunctionsBaseUrl = config.functionsBaseUrl ?? null;
|
|
1685
1909
|
_resolvedFunctionsProjectId = null;
|
|
1910
|
+
_functionsProjectIdInflight = null;
|
|
1911
|
+
if (!_configuredFunctionsBaseUrl && !PROJECT_ID_PATTERN.test(config.projectId)) resolveFunctionsProjectId().catch(() => {});
|
|
1912
|
+
writeIdentitySnapshot();
|
|
1686
1913
|
},
|
|
1687
1914
|
get projectId() {
|
|
1688
1915
|
if (!_configuredProjectId) throw new AmbaApiError("NOT_INITIALIZED", "amba SDK not configured. Call Amba.configure({ projectId, clientKey }) at app start before any other API.");
|
|
@@ -1835,6 +2062,10 @@ const Amba = {
|
|
|
1835
2062
|
signOut: async (rotateAnonymousId = false) => {
|
|
1836
2063
|
await coreCall((c) => c.signOut(rotateAnonymousId));
|
|
1837
2064
|
_currentSessionToken = null;
|
|
2065
|
+
_currentRefreshToken = null;
|
|
2066
|
+
_currentSessionExpiresAt = null;
|
|
2067
|
+
_currentUser = null;
|
|
2068
|
+
writeIdentitySnapshot();
|
|
1838
2069
|
notifyAuthSubscribers(null);
|
|
1839
2070
|
},
|
|
1840
2071
|
refresh: async () => {
|
|
@@ -1843,11 +2074,17 @@ const Amba = {
|
|
|
1843
2074
|
return r;
|
|
1844
2075
|
},
|
|
1845
2076
|
me: async () => {
|
|
1846
|
-
|
|
2077
|
+
const user = await coreCall((c) => c.me());
|
|
2078
|
+
_currentUser = user;
|
|
2079
|
+
writeIdentitySnapshot();
|
|
2080
|
+
return user;
|
|
1847
2081
|
},
|
|
1848
2082
|
getSession: async () => {
|
|
1849
2083
|
if (!requireCore().isAuthenticated()) return null;
|
|
1850
|
-
|
|
2084
|
+
const user = await coreCall((c) => c.me());
|
|
2085
|
+
_currentUser = user;
|
|
2086
|
+
writeIdentitySnapshot();
|
|
2087
|
+
return snapshotSession(user);
|
|
1851
2088
|
},
|
|
1852
2089
|
getAnonymousId: async () => {
|
|
1853
2090
|
return requireCore().anonymousId();
|
|
@@ -1878,6 +2115,14 @@ const Amba = {
|
|
|
1878
2115
|
await coreCall((c) => c.sessionsRevoke(sessionId));
|
|
1879
2116
|
}
|
|
1880
2117
|
},
|
|
2118
|
+
asGroup: (groupId) => {
|
|
2119
|
+
requireCore();
|
|
2120
|
+
return new AmbaGroupScopedClient(groupId);
|
|
2121
|
+
},
|
|
2122
|
+
inGroup: (groupId) => {
|
|
2123
|
+
requireCore();
|
|
2124
|
+
return new AmbaGroupScopedClient(groupId);
|
|
2125
|
+
},
|
|
1881
2126
|
collection: (name) => new CollectionSubscription({
|
|
1882
2127
|
baseUrl: _configuredApiUrl,
|
|
1883
2128
|
authHeaders: realtimeAuthHeaders
|
|
@@ -2216,7 +2461,7 @@ const Amba = {
|
|
|
2216
2461
|
const method = options?.method ?? "POST";
|
|
2217
2462
|
const headers = {
|
|
2218
2463
|
"X-Api-Key": _configuredClientKey,
|
|
2219
|
-
...options?.headers
|
|
2464
|
+
...options?.headers
|
|
2220
2465
|
};
|
|
2221
2466
|
if (_currentSessionToken) headers["Authorization"] = `Bearer ${_currentSessionToken}`;
|
|
2222
2467
|
let requestBody;
|
|
@@ -2427,8 +2672,8 @@ function getCoreWasm() {
|
|
|
2427
2672
|
* `verify-sdk-version-coherence` GH action gates every publish on the
|
|
2428
2673
|
* pair agreeing (and matching the `web-v<X.Y.Z>` tag when tag-triggered).
|
|
2429
2674
|
*/
|
|
2430
|
-
const SDK_VERSION = "4.0.
|
|
2675
|
+
const SDK_VERSION = "4.0.10";
|
|
2431
2676
|
|
|
2432
2677
|
//#endregion
|
|
2433
|
-
export { Amba, AmbaApiError, CollectionSubscription, ConversationSubscription, GamificationSubscription, SDK_VERSION, getCoreWasm, withOptimistic };
|
|
2678
|
+
export { Amba, AmbaApiError, AmbaGroupScopedClient, CollectionSubscription, ConversationSubscription, GamificationSubscription, SDK_VERSION, getCoreWasm, withOptimistic };
|
|
2434
2679
|
//# sourceMappingURL=index.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@layers/amba-web",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.10",
|
|
4
4
|
"description": "amba SDK for browser apps — full SDK 4.0 surface (34 modules: auth + magic link, users, sessions, sync, collections + vector search, storage + list/delete/download, push, entitlements, flags, config, deep links, onboarding, content, messaging, friends, groups, feeds, reviews, moderation, roles, referrals, achievements, xp, streaks, challenges, leaderboards, leagues, currencies, inventory, stores, catalog, ai, diagnostics).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
},
|
|
33
33
|
"sideEffects": false,
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@layers/amba-core": "^4.0.
|
|
35
|
+
"@layers/amba-core": "^4.0.10"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@types/node": "^22.13.14",
|