@layers/amba-web 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.d.ts +161 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +196 -3
- package/dist/index.js.map +1 -1
- package/dist-rn/index.d.ts +161 -2
- package/dist-rn/index.js +196 -3
- package/package.json +2 -2
package/dist-rn/index.d.ts
CHANGED
|
@@ -505,6 +505,18 @@ interface MarkReadResult {
|
|
|
505
505
|
app_user_id?: string | null;
|
|
506
506
|
last_read_at?: string | null;
|
|
507
507
|
}
|
|
508
|
+
/**
|
|
509
|
+
* A `conversation_participants` row — returned by
|
|
510
|
+
* `Amba.messaging.addParticipant`. All fields are optional so older
|
|
511
|
+
* servers that trim columns still decode.
|
|
512
|
+
*/
|
|
513
|
+
interface ConversationParticipant {
|
|
514
|
+
id?: string | null;
|
|
515
|
+
conversation_id?: string | null;
|
|
516
|
+
app_user_id?: string | null;
|
|
517
|
+
joined_at?: string | null;
|
|
518
|
+
last_read_at?: string | null;
|
|
519
|
+
}
|
|
508
520
|
interface Report {
|
|
509
521
|
id: string;
|
|
510
522
|
reporter_id: string;
|
|
@@ -716,6 +728,104 @@ interface NearestResponse<T> {
|
|
|
716
728
|
data: T[];
|
|
717
729
|
}
|
|
718
730
|
//#endregion
|
|
731
|
+
//#region src/realtime.d.ts
|
|
732
|
+
/**
|
|
733
|
+
* Live collection subscriptions for `@layers/amba-web` (PROTOTYPE — T13).
|
|
734
|
+
*
|
|
735
|
+
* Public surface is intentionally tiny and provider-agnostic:
|
|
736
|
+
*
|
|
737
|
+
* const unsub = Amba.collection("messages")
|
|
738
|
+
* .where("circle_id", circleId)
|
|
739
|
+
* .subscribe((change) => {
|
|
740
|
+
* // change.op is "INSERT" | "UPDATE" | "DELETE"
|
|
741
|
+
* // change.row is the affected row
|
|
742
|
+
* });
|
|
743
|
+
* unsub(); // stop listening
|
|
744
|
+
*
|
|
745
|
+
* The transport (a streamed HTTP response) and the server-side fan-out
|
|
746
|
+
* mechanism are deliberately NOT named here — customers only ever see
|
|
747
|
+
* `.subscribe()`. Internally this opens a long-lived streamed GET to the
|
|
748
|
+
* realtime endpoint and reconnects with backoff if the connection drops.
|
|
749
|
+
*
|
|
750
|
+
* Delivery mirrors `Amba.collection.find()` exactly: you receive only rows
|
|
751
|
+
* your session owns, optionally narrowed by `.where(...)` equality filters.
|
|
752
|
+
*/
|
|
753
|
+
type RowChangeOp = "INSERT" | "UPDATE" | "DELETE";
|
|
754
|
+
interface RealtimeChange<T = unknown> {
|
|
755
|
+
/** Which kind of write happened. */
|
|
756
|
+
op: RowChangeOp;
|
|
757
|
+
/**
|
|
758
|
+
* The affected row. For deletes this is a partial row (at minimum `id`).
|
|
759
|
+
* If the row was too large to deliver inline, `truncated` is `true` and
|
|
760
|
+
* `row` carries just `id` — re-fetch with `Amba.collection.find()` /
|
|
761
|
+
* `findOne()` when you need the full body.
|
|
762
|
+
*/
|
|
763
|
+
row: T;
|
|
764
|
+
truncated?: boolean;
|
|
765
|
+
}
|
|
766
|
+
interface SubscribeOptions {
|
|
767
|
+
/** Called once when the live connection is (re)established. */
|
|
768
|
+
onOpen?: () => void;
|
|
769
|
+
/** Called on a connection error before the SDK retries. */
|
|
770
|
+
onError?: (err: unknown) => void;
|
|
771
|
+
}
|
|
772
|
+
/** Unsubscribe handle returned by `.subscribe(...)`. Call it to stop. */
|
|
773
|
+
type Unsubscribe = () => void;
|
|
774
|
+
/**
|
|
775
|
+
* Resolved at call time so the latest session token is used. Returns the
|
|
776
|
+
* auth headers, or `null` when there's no live session (subscribe throws).
|
|
777
|
+
*/
|
|
778
|
+
interface RealtimeContext {
|
|
779
|
+
baseUrl: string;
|
|
780
|
+
authHeaders: () => Record<string, string> | null;
|
|
781
|
+
}
|
|
782
|
+
/**
|
|
783
|
+
* Builder returned by `Amba.collection(name)`. Chain `.where(...)` to
|
|
784
|
+
* narrow, then `.subscribe(cb)` to start streaming.
|
|
785
|
+
*/
|
|
786
|
+
declare class CollectionSubscription<T = unknown> {
|
|
787
|
+
private readonly ctx;
|
|
788
|
+
private readonly name;
|
|
789
|
+
private readonly filters;
|
|
790
|
+
constructor(ctx: RealtimeContext, name: string);
|
|
791
|
+
/** Add an equality filter. Chainable. All filters must match (AND). */
|
|
792
|
+
where(column: string, value: unknown): this;
|
|
793
|
+
/**
|
|
794
|
+
* Start listening. `cb` fires once per matching row change. Returns an
|
|
795
|
+
* unsubscribe handle — call it to close the connection.
|
|
796
|
+
*/
|
|
797
|
+
subscribe(cb: (change: RealtimeChange<T>) => void, options?: SubscribeOptions): Unsubscribe;
|
|
798
|
+
}
|
|
799
|
+
/**
|
|
800
|
+
* A live message change in a conversation. `op` is "INSERT" for a new
|
|
801
|
+
* message; moderation removals arrive as "DELETE". For a delete (or when a
|
|
802
|
+
* message was too large to deliver inline) `row` may be partial — re-fetch
|
|
803
|
+
* with `Amba.messaging.listMessages(conversationId)` when you need the full
|
|
804
|
+
* body.
|
|
805
|
+
*/
|
|
806
|
+
type MessageChange<T = unknown> = RealtimeChange<T>;
|
|
807
|
+
/**
|
|
808
|
+
* Builder returned by `Amba.messaging.conversation(id)`. Call `.subscribe(cb)`
|
|
809
|
+
* to receive new messages live instead of polling.
|
|
810
|
+
*
|
|
811
|
+
* const unsub = Amba.messaging
|
|
812
|
+
* .conversation(conversationId)
|
|
813
|
+
* .subscribe((change) => {
|
|
814
|
+
* // change.op === "INSERT"; change.row is the new message
|
|
815
|
+
* });
|
|
816
|
+
* unsub(); // stop listening
|
|
817
|
+
*
|
|
818
|
+
* Delivery is scoped to conversation participants — you receive messages for
|
|
819
|
+
* a conversation only if your session is a member of it. Reconnects with the
|
|
820
|
+
* same exponential backoff as collection subscriptions.
|
|
821
|
+
*/
|
|
822
|
+
declare class ConversationSubscription<T = unknown> {
|
|
823
|
+
private readonly ctx;
|
|
824
|
+
private readonly conversationId;
|
|
825
|
+
constructor(ctx: RealtimeContext, conversationId: string);
|
|
826
|
+
subscribe(cb: (change: MessageChange<T>) => void, options?: SubscribeOptions): Unsubscribe;
|
|
827
|
+
}
|
|
828
|
+
//#endregion
|
|
719
829
|
//#region src/error.d.ts
|
|
720
830
|
/**
|
|
721
831
|
* Typed error class for the amba SDK + helper that converts arbitrary
|
|
@@ -983,6 +1093,23 @@ declare const Amba: {
|
|
|
983
1093
|
*/
|
|
984
1094
|
revoke: (sessionId: string) => Promise<void>;
|
|
985
1095
|
};
|
|
1096
|
+
/**
|
|
1097
|
+
* Live collection subscriptions (SDK 4.0). Returns a builder — chain
|
|
1098
|
+
* `.where(column, value)` to narrow, then `.subscribe(cb)` to start
|
|
1099
|
+
* receiving row changes. `subscribe` returns an unsubscribe handle.
|
|
1100
|
+
*
|
|
1101
|
+
* @example
|
|
1102
|
+
* const unsub = Amba.collection("messages")
|
|
1103
|
+
* .where("circle_id", circleId)
|
|
1104
|
+
* .subscribe((change) => {
|
|
1105
|
+
* // change.op: "INSERT" | "UPDATE" | "DELETE"; change.row: the row
|
|
1106
|
+
* });
|
|
1107
|
+
* // later: unsub();
|
|
1108
|
+
*
|
|
1109
|
+
* Delivery mirrors `Amba.collections.find()` — you only receive rows your
|
|
1110
|
+
* session owns. Requires an authenticated session.
|
|
1111
|
+
*/
|
|
1112
|
+
collection: <T = unknown>(name: string) => CollectionSubscription<T>;
|
|
986
1113
|
/** Typed collection CRUD with auto-RLS. */
|
|
987
1114
|
collections: {
|
|
988
1115
|
find: <T = unknown>(name: string, options?: FindOptions) => Promise<FindResponse<T>>;
|
|
@@ -1315,12 +1442,44 @@ declare const Amba: {
|
|
|
1315
1442
|
* native SDKs were already trying to call.)
|
|
1316
1443
|
*/
|
|
1317
1444
|
getMessage: (conversationId: string, messageId: string) => Promise<Message | null>;
|
|
1445
|
+
/**
|
|
1446
|
+
* `POST /v1/client/messaging/conversations/:conversationId/participants`
|
|
1447
|
+
* — add a participant to an existing conversation. The caller must
|
|
1448
|
+
* already be a participant (the server returns `403
|
|
1449
|
+
* NOT_A_PARTICIPANT` otherwise) — this is the peer-invite path.
|
|
1450
|
+
* Idempotent: re-adding an existing member returns the existing
|
|
1451
|
+
* participant row, not an error. Enables dynamic membership growth
|
|
1452
|
+
* (e.g. a circle whose members qualify over time). Server-side
|
|
1453
|
+
* membership management (admin/matchmaker) uses the admin
|
|
1454
|
+
* add-participant endpoint instead.
|
|
1455
|
+
*/
|
|
1456
|
+
addParticipant: (conversationId: string, userId: string) => Promise<ConversationParticipant>;
|
|
1318
1457
|
/**
|
|
1319
1458
|
* `POST /v1/client/messaging/conversations/:conversationId/read`
|
|
1320
1459
|
* — bump the caller's `last_read_at` for the conversation. Returns
|
|
1321
1460
|
* the updated participant row.
|
|
1322
1461
|
*/
|
|
1323
1462
|
markRead: (conversationId: string) => Promise<MarkReadResult>;
|
|
1463
|
+
/**
|
|
1464
|
+
* Live message subscriptions (SDK 4.0). Returns a builder — call
|
|
1465
|
+
* `.subscribe(cb)` to receive new messages in a conversation live
|
|
1466
|
+
* instead of polling `listMessages`. `subscribe` returns an
|
|
1467
|
+
* unsubscribe handle.
|
|
1468
|
+
*
|
|
1469
|
+
* @example
|
|
1470
|
+
* const unsub = Amba.messaging
|
|
1471
|
+
* .conversation(conversationId)
|
|
1472
|
+
* .subscribe((change) => {
|
|
1473
|
+
* // change.op === "INSERT"; change.row is the new message
|
|
1474
|
+
* appendMessageToThread(change.row);
|
|
1475
|
+
* });
|
|
1476
|
+
* // later: unsub();
|
|
1477
|
+
*
|
|
1478
|
+
* Delivery is scoped to conversation participants — you receive a
|
|
1479
|
+
* conversation's messages only if your session is a member of it.
|
|
1480
|
+
* Requires an authenticated session.
|
|
1481
|
+
*/
|
|
1482
|
+
conversation: <T = Message>(conversationId: string) => ConversationSubscription<T>;
|
|
1324
1483
|
};
|
|
1325
1484
|
/** Report users / content for moderation. */
|
|
1326
1485
|
moderation: {
|
|
@@ -1400,7 +1559,7 @@ declare function getCoreWasm(): AmbaCoreWasm;
|
|
|
1400
1559
|
* `verify-sdk-version-coherence` GH action gates every publish on the
|
|
1401
1560
|
* pair agreeing (and matching the `web-v<X.Y.Z>` tag when tag-triggered).
|
|
1402
1561
|
*/
|
|
1403
|
-
declare const SDK_VERSION = "4.0.
|
|
1562
|
+
declare const SDK_VERSION = "4.0.3";
|
|
1404
1563
|
//#endregion
|
|
1405
|
-
export { Achievement, AchievementProgress, AiMessage, AiMessageRequest, AiMessageResponse, AiUsage, Amba, AmbaApiError, type AmbaApiErrorCode, AmbaConfig, AndFilter, AppSession, AuthResult, CatalogItem, Challenge, ChallengeProgress, ConditionFilter, ConfigBundle, ConsumeRequest, ContentItem, Conversation, ConversationType, CreateConversationRequest, CurrencyBalance, CurrencyTransaction, DeepLink, DeepLinkCreate, DiagnosticsEnvironment, EntitlementSource, FeedItem, FeedResponse, Filter, FilterValue, FindOptions, FindResponse, FlagAssignment, Friendship, FriendshipState, Group, GroupCreate, GroupMember, GroupUpdate, InventoryItem, Leaderboard, LeaderboardEntry, LeagueCohort, LeagueCohortMember, LeagueCohortView, LeagueMembership, LeagueTier, LinkedIdentifier, ListMessagesOptions, MarkReadResult, MediaAsset, MediaListResponse, Message, MessageType, NearestOptions, NearestResponse, NotFilter, OnboardingStatus, OrFilter, OrderBy, PingResult, PresignData, PullChangesCursor, PullChangesResult, PurchaseOption, PurchaseRequest, PurchaseResult, PushChangesResult, PushPlatform, PushToken, ReferralClaim, ReferralCode, Report, ReportRequest, Review, ReviewCreate, ReviewUpdate, Role, SDK_VERSION, SendMessageRequest, Session, SocialProvider, Store, Streak, SyncChange, SyncConflict, User, UserEntitlement, XpBalance, XpTransaction, getCoreWasm };
|
|
1564
|
+
export { Achievement, AchievementProgress, AiMessage, AiMessageRequest, AiMessageResponse, 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, DiagnosticsEnvironment, EntitlementSource, FeedItem, FeedResponse, Filter, FilterValue, FindOptions, FindResponse, FlagAssignment, Friendship, FriendshipState, Group, GroupCreate, GroupMember, GroupUpdate, InventoryItem, Leaderboard, LeaderboardEntry, LeagueCohort, LeagueCohortMember, LeagueCohortView, LeagueMembership, LeagueTier, LinkedIdentifier, ListMessagesOptions, MarkReadResult, MediaAsset, MediaListResponse, Message, type MessageChange, MessageType, NearestOptions, NearestResponse, NotFilter, OnboardingStatus, OrFilter, OrderBy, PingResult, PresignData, PullChangesCursor, PullChangesResult, PurchaseOption, PurchaseRequest, PurchaseResult, PushChangesResult, PushPlatform, PushToken, type RealtimeChange, ReferralClaim, ReferralCode, Report, ReportRequest, Review, ReviewCreate, ReviewUpdate, Role, type RowChangeOp, SDK_VERSION, SendMessageRequest, Session, SocialProvider, Store, Streak, type SubscribeOptions, SyncChange, SyncConflict, type Unsubscribe, User, UserEntitlement, XpBalance, XpTransaction, getCoreWasm };
|
|
1406
1565
|
//# sourceMappingURL=index.d.ts.map
|
package/dist-rn/index.js
CHANGED
|
@@ -99,8 +99,165 @@ function toAmbaApiError(err) {
|
|
|
99
99
|
return new AmbaApiError("UNKNOWN_ERROR", String(err));
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
//#endregion
|
|
103
|
+
//#region src/realtime.ts
|
|
104
|
+
const INITIAL_BACKOFF_MS = 500;
|
|
105
|
+
const MAX_BACKOFF_MS = 15e3;
|
|
106
|
+
/**
|
|
107
|
+
* Builder returned by `Amba.collection(name)`. Chain `.where(...)` to
|
|
108
|
+
* narrow, then `.subscribe(cb)` to start streaming.
|
|
109
|
+
*/
|
|
110
|
+
var CollectionSubscription = class {
|
|
111
|
+
filters = {};
|
|
112
|
+
constructor(ctx, name) {
|
|
113
|
+
this.ctx = ctx;
|
|
114
|
+
this.name = name;
|
|
115
|
+
}
|
|
116
|
+
/** Add an equality filter. Chainable. All filters must match (AND). */
|
|
117
|
+
where(column, value) {
|
|
118
|
+
this.filters[column] = value;
|
|
119
|
+
return this;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Start listening. `cb` fires once per matching row change. Returns an
|
|
123
|
+
* unsubscribe handle — call it to close the connection.
|
|
124
|
+
*/
|
|
125
|
+
subscribe(cb, options = {}) {
|
|
126
|
+
if (!this.ctx.authHeaders()) throw new Error("Amba.collection().subscribe() requires an authenticated session — call Amba.auth.signIn*/verify* first.");
|
|
127
|
+
const qs = Object.keys(this.filters).length > 0 ? `?where=${encodeURIComponent(JSON.stringify(this.filters))}` : "";
|
|
128
|
+
return openStream({
|
|
129
|
+
url: `${this.ctx.baseUrl}/v1/client/realtime/collections/${encodeURIComponent(this.name)}${qs}`,
|
|
130
|
+
authHeaders: this.ctx.authHeaders,
|
|
131
|
+
onChange: cb,
|
|
132
|
+
options
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
/**
|
|
137
|
+
* Open a streamed GET to a realtime endpoint and dispatch row-change events
|
|
138
|
+
* to `onChange`. Reconnects with exponential backoff until the returned
|
|
139
|
+
* unsubscribe handle is invoked. Shared by both collection and conversation
|
|
140
|
+
* subscriptions — the only thing that differs between them is the URL.
|
|
141
|
+
*
|
|
142
|
+
* Uses `fetch` + a streamed response body (rather than the browser's
|
|
143
|
+
* native `EventSource`) because `EventSource` cannot set request headers —
|
|
144
|
+
* which would force the session token into the URL query string, where it
|
|
145
|
+
* would leak into access logs. Streaming `fetch` keeps auth in headers.
|
|
146
|
+
*/
|
|
147
|
+
function openStream(args) {
|
|
148
|
+
let stopped = false;
|
|
149
|
+
let controller = null;
|
|
150
|
+
let backoff = INITIAL_BACKOFF_MS;
|
|
151
|
+
const run = async () => {
|
|
152
|
+
while (!stopped) {
|
|
153
|
+
controller = new AbortController();
|
|
154
|
+
try {
|
|
155
|
+
const headers = args.authHeaders();
|
|
156
|
+
if (!headers) throw new Error("no active session");
|
|
157
|
+
const resp = await fetch(args.url, {
|
|
158
|
+
method: "GET",
|
|
159
|
+
headers: {
|
|
160
|
+
...headers,
|
|
161
|
+
Accept: "text/event-stream"
|
|
162
|
+
},
|
|
163
|
+
signal: controller.signal
|
|
164
|
+
});
|
|
165
|
+
if (!resp.ok || !resp.body) throw new Error(`realtime connect failed: HTTP ${resp.status}`);
|
|
166
|
+
await consume(resp.body, args, () => {
|
|
167
|
+
backoff = INITIAL_BACKOFF_MS;
|
|
168
|
+
args.options.onOpen?.();
|
|
169
|
+
});
|
|
170
|
+
} catch (err) {
|
|
171
|
+
if (stopped) break;
|
|
172
|
+
args.options.onError?.(err);
|
|
173
|
+
}
|
|
174
|
+
if (stopped) break;
|
|
175
|
+
await delay(backoff);
|
|
176
|
+
backoff = Math.min(backoff * 2, MAX_BACKOFF_MS);
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
run();
|
|
180
|
+
return () => {
|
|
181
|
+
stopped = true;
|
|
182
|
+
controller?.abort();
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
async function consume(body, args, onFirstFrame) {
|
|
186
|
+
const reader = body.getReader();
|
|
187
|
+
const decoder = new TextDecoder();
|
|
188
|
+
let buffer = "";
|
|
189
|
+
let sawFrame = false;
|
|
190
|
+
for (;;) {
|
|
191
|
+
const { done, value } = await reader.read();
|
|
192
|
+
if (done) return;
|
|
193
|
+
buffer += decoder.decode(value, { stream: true });
|
|
194
|
+
let idx;
|
|
195
|
+
while ((idx = buffer.indexOf("\n\n")) !== -1) {
|
|
196
|
+
const frame = buffer.slice(0, idx);
|
|
197
|
+
buffer = buffer.slice(idx + 2);
|
|
198
|
+
if (!sawFrame) {
|
|
199
|
+
sawFrame = true;
|
|
200
|
+
onFirstFrame();
|
|
201
|
+
}
|
|
202
|
+
dispatchFrame(frame, args.onChange);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
function dispatchFrame(frame, onChange) {
|
|
207
|
+
let event = "";
|
|
208
|
+
const dataLines = [];
|
|
209
|
+
for (const line of frame.split("\n")) {
|
|
210
|
+
if (line.startsWith(":")) continue;
|
|
211
|
+
if (line.startsWith("event:")) event = line.slice(6).trim();
|
|
212
|
+
else if (line.startsWith("data:")) dataLines.push(line.slice(5).trim());
|
|
213
|
+
}
|
|
214
|
+
if (event !== "change") return;
|
|
215
|
+
try {
|
|
216
|
+
const payload = JSON.parse(dataLines.join("\n"));
|
|
217
|
+
onChange({
|
|
218
|
+
op: payload.op,
|
|
219
|
+
row: payload.row,
|
|
220
|
+
truncated: payload.truncated
|
|
221
|
+
});
|
|
222
|
+
} catch {}
|
|
223
|
+
}
|
|
224
|
+
function delay(ms) {
|
|
225
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Builder returned by `Amba.messaging.conversation(id)`. Call `.subscribe(cb)`
|
|
229
|
+
* to receive new messages live instead of polling.
|
|
230
|
+
*
|
|
231
|
+
* const unsub = Amba.messaging
|
|
232
|
+
* .conversation(conversationId)
|
|
233
|
+
* .subscribe((change) => {
|
|
234
|
+
* // change.op === "INSERT"; change.row is the new message
|
|
235
|
+
* });
|
|
236
|
+
* unsub(); // stop listening
|
|
237
|
+
*
|
|
238
|
+
* Delivery is scoped to conversation participants — you receive messages for
|
|
239
|
+
* a conversation only if your session is a member of it. Reconnects with the
|
|
240
|
+
* same exponential backoff as collection subscriptions.
|
|
241
|
+
*/
|
|
242
|
+
var ConversationSubscription = class {
|
|
243
|
+
constructor(ctx, conversationId) {
|
|
244
|
+
this.ctx = ctx;
|
|
245
|
+
this.conversationId = conversationId;
|
|
246
|
+
}
|
|
247
|
+
subscribe(cb, options = {}) {
|
|
248
|
+
if (!this.ctx.authHeaders()) throw new Error("Amba.messaging.conversation().subscribe() requires an authenticated session — call Amba.auth.signIn*/verify* first.");
|
|
249
|
+
return openStream({
|
|
250
|
+
url: `${this.ctx.baseUrl}/v1/client/realtime/conversations/${encodeURIComponent(this.conversationId)}`,
|
|
251
|
+
authHeaders: this.ctx.authHeaders,
|
|
252
|
+
onChange: cb,
|
|
253
|
+
options
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
|
|
102
258
|
//#endregion
|
|
103
259
|
//#region src/index.ts
|
|
260
|
+
const DEFAULT_API_URL = "https://api.amba.dev";
|
|
104
261
|
/**
|
|
105
262
|
* Internal state — one instance per page. Created lazily by `Amba.configure()`.
|
|
106
263
|
*/
|
|
@@ -111,6 +268,17 @@ let coreInstance = null;
|
|
|
111
268
|
* core finishes initializing.
|
|
112
269
|
*/
|
|
113
270
|
let _configuredProjectId = null;
|
|
271
|
+
/**
|
|
272
|
+
* Realtime (`Amba.collection(name).subscribe`) needs the API base URL,
|
|
273
|
+
* client key, and current session token in the TS layer to open the
|
|
274
|
+
* streamed connection — the engine doesn't expose them back. We capture
|
|
275
|
+
* the base URL + client key at configure-time and the session token from
|
|
276
|
+
* every auth result (the engine rotates it internally on refresh; the
|
|
277
|
+
* subscribe connection picks up the latest at connect time).
|
|
278
|
+
*/
|
|
279
|
+
let _configuredApiUrl = DEFAULT_API_URL;
|
|
280
|
+
let _configuredClientKey = null;
|
|
281
|
+
let _currentSessionToken = null;
|
|
114
282
|
function requireCore() {
|
|
115
283
|
if (!coreInstance) throw new AmbaApiError("NOT_INITIALIZED", "amba SDK not configured. Call Amba.configure({ projectId, clientKey }) at app start before any other API.");
|
|
116
284
|
return coreInstance;
|
|
@@ -148,9 +316,22 @@ function notifyAuthSubscribers(session) {
|
|
|
148
316
|
}
|
|
149
317
|
}
|
|
150
318
|
async function notifyFromAuthResult(result) {
|
|
319
|
+
if (result?.session_token) _currentSessionToken = result.session_token;
|
|
151
320
|
notifyAuthSubscribers(snapshotSession(result.user));
|
|
152
321
|
}
|
|
153
322
|
/**
|
|
323
|
+
* Build the auth headers for a realtime subscribe connection, or `null`
|
|
324
|
+
* when there's no live session. Read lazily at connect-time so reconnects
|
|
325
|
+
* pick up a rotated session token.
|
|
326
|
+
*/
|
|
327
|
+
function realtimeAuthHeaders() {
|
|
328
|
+
if (!_configuredClientKey || !_currentSessionToken) return null;
|
|
329
|
+
return {
|
|
330
|
+
"X-Api-Key": _configuredClientKey,
|
|
331
|
+
Authorization: `Bearer ${_currentSessionToken}`
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
154
335
|
* Top-level SDK namespace.
|
|
155
336
|
*
|
|
156
337
|
* Stateless from the JS side — all state lives in the SDK runtime. Calling
|
|
@@ -176,6 +357,8 @@ const Amba = {
|
|
|
176
357
|
throw toAmbaApiError(err);
|
|
177
358
|
}
|
|
178
359
|
_configuredProjectId = config.projectId;
|
|
360
|
+
_configuredApiUrl = config.apiUrl ?? DEFAULT_API_URL;
|
|
361
|
+
_configuredClientKey = config.clientKey;
|
|
179
362
|
},
|
|
180
363
|
get projectId() {
|
|
181
364
|
if (!_configuredProjectId) throw new AmbaApiError("NOT_INITIALIZED", "amba SDK not configured. Call Amba.configure({ projectId, clientKey }) at app start before any other API.");
|
|
@@ -274,6 +457,7 @@ const Amba = {
|
|
|
274
457
|
},
|
|
275
458
|
signOut: async (rotateAnonymousId = false) => {
|
|
276
459
|
await coreCall((c) => c.signOut(rotateAnonymousId));
|
|
460
|
+
_currentSessionToken = null;
|
|
277
461
|
notifyAuthSubscribers(null);
|
|
278
462
|
},
|
|
279
463
|
refresh: async () => {
|
|
@@ -317,6 +501,10 @@ const Amba = {
|
|
|
317
501
|
await coreCall((c) => c.sessionsRevoke(sessionId));
|
|
318
502
|
}
|
|
319
503
|
},
|
|
504
|
+
collection: (name) => new CollectionSubscription({
|
|
505
|
+
baseUrl: _configuredApiUrl,
|
|
506
|
+
authHeaders: realtimeAuthHeaders
|
|
507
|
+
}, name),
|
|
320
508
|
collections: {
|
|
321
509
|
find: async (name, options = {}) => {
|
|
322
510
|
return await coreCall((c) => c.collectionsFind(name, encodeJson(options)));
|
|
@@ -580,7 +768,12 @@ const Amba = {
|
|
|
580
768
|
getMessage: async (conversationId, messageId) => {
|
|
581
769
|
return await coreCall((c) => c.messagingGetMessage(conversationId, messageId));
|
|
582
770
|
},
|
|
583
|
-
|
|
771
|
+
addParticipant: async (conversationId, userId) => await coreCall((c) => c.messagingAddParticipant(conversationId, userId)),
|
|
772
|
+
markRead: async (conversationId) => await coreCall((c) => c.messagingMarkRead(conversationId)),
|
|
773
|
+
conversation: (conversationId) => new ConversationSubscription({
|
|
774
|
+
baseUrl: _configuredApiUrl,
|
|
775
|
+
authHeaders: realtimeAuthHeaders
|
|
776
|
+
}, conversationId)
|
|
584
777
|
},
|
|
585
778
|
moderation: {
|
|
586
779
|
reportUser: async (request) => await coreCall((c) => c.moderationReportUser(encodeJson(request))),
|
|
@@ -638,8 +831,8 @@ function getCoreWasm() {
|
|
|
638
831
|
* `verify-sdk-version-coherence` GH action gates every publish on the
|
|
639
832
|
* pair agreeing (and matching the `web-v<X.Y.Z>` tag when tag-triggered).
|
|
640
833
|
*/
|
|
641
|
-
const SDK_VERSION = "4.0.
|
|
834
|
+
const SDK_VERSION = "4.0.3";
|
|
642
835
|
|
|
643
836
|
//#endregion
|
|
644
|
-
export { Amba, AmbaApiError, SDK_VERSION, getCoreWasm };
|
|
837
|
+
export { Amba, AmbaApiError, CollectionSubscription, ConversationSubscription, SDK_VERSION, getCoreWasm };
|
|
645
838
|
//# 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.3",
|
|
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.3"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@types/node": "^22.13.14",
|