@base44-preview/sdk 0.8.48-pr.286.7d59d14 → 0.8.48-pr.286.f67ed26

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.
@@ -1,9 +1,35 @@
1
- import type { PlatformClientOptions } from "./client.types.js";
2
- import type { BuilderModule } from "./modules/builder.types.js";
3
- /** Browser platform client. Construction creates no sockets, timers or network requests. */
1
+ import type { PlatformClientOptions, PlatformSubscription, SubscriptionOptions } from "./types.js";
2
+ /** Browser client for read-only platform app events. Constructing it opens no connection. */
4
3
  export declare class Base44PlatformClient {
5
- /** Lazy builder subscriptions with independent session lifecycles. */
6
- readonly builder: BuilderModule;
7
- /** Configure shared service/auth settings; each module initializes its own resources. */
4
+ private readonly socket;
5
+ private readonly subscriptions;
6
+ private readonly options;
7
+ private closed;
8
+ private needsFreshConnection;
9
+ private generation;
10
+ private authAttempt;
11
+ private cancelAuth?;
12
+ private connecting?;
13
+ private resolveConnect?;
14
+ private rejectConnect?;
15
+ /** Configure a dedicated connection. API keys belong exclusively on your backend. */
8
16
  constructor(options: PlatformClientOptions);
17
+ /** Connect using a freshly obtained token. Resolves on CONNECT, not on app replay completion.
18
+ * Unexpected transport loss retries up to five times and rejoins active subscriptions.
19
+ * Call again after addressing a connection/auth failure; concurrent calls share one attempt.
20
+ */
21
+ connect(): Promise<void>;
22
+ /** Subscribe before or after connecting. One subscription per app, maximum eight.
23
+ * Events and boundary callbacks are awaited in order per app. Failed application,
24
+ * invalid frames or server errors end the subscription without advancing its cursor.
25
+ */
26
+ subscribe(appId: string, options: SubscriptionOptions): PlatformSubscription;
27
+ /** Stop delivery, cancel reconnection and release all listeners. Idempotent and terminal. */
28
+ close(): void;
29
+ private join;
30
+ private authenticate;
31
+ private clearConnecting;
32
+ private connectionError;
33
+ private serverError;
34
+ private protocolError;
9
35
  }
@@ -1,12 +1,218 @@
1
- import { createBuilder } from "./modules/builder.js";
2
- /** Browser platform client. Construction creates no sockets, timers or network requests. */
1
+ import { io } from "socket.io-client";
2
+ import { PlatformSocketError } from "./errors.js";
3
+ import { appFromRoom, appPattern, decode, decodeJoined, errorCodes, eventApp, eventNames, object, roomFor } from "./protocol.js";
4
+ import { notify, Subscription } from "./subscription.js";
5
+ /** Browser client for read-only platform app events. Constructing it opens no connection. */
3
6
  export class Base44PlatformClient {
4
- /** Configure shared service/auth settings; each module initializes its own resources. */
7
+ /** Configure a dedicated connection. API keys belong exclusively on your backend. */
5
8
  constructor(options) {
9
+ this.subscriptions = new Map();
10
+ this.closed = false;
11
+ this.needsFreshConnection = false;
12
+ this.generation = 0;
13
+ this.authAttempt = 0;
6
14
  const url = new URL(options.serverUrl);
7
15
  if (!["https:", "http:"].includes(url.protocol) || url.username || url.password || url.search || url.hash || url.pathname !== "/") {
8
16
  throw new TypeError("serverUrl must be an HTTP(S) origin without credentials, path, query or fragment");
9
17
  }
10
- this.builder = Object.freeze(createBuilder({ ...options, serverUrl: url.origin }));
18
+ this.options = { ...options };
19
+ this.socket = io(`${url.origin}/partner`, {
20
+ path: "/ws-whitelabel/socket.io/", transports: ["websocket"], autoConnect: false,
21
+ forceNew: true, reconnectionAttempts: 5, reconnectionDelay: 1000, reconnectionDelayMax: 10000,
22
+ timeout: 20000,
23
+ auth: (callback) => { void this.authenticate(callback); },
24
+ });
25
+ this.socket.on("connect", () => {
26
+ var _a;
27
+ const generation = ++this.generation;
28
+ this.needsFreshConnection = false;
29
+ for (const subscription of this.subscriptions.values())
30
+ this.join(subscription, generation);
31
+ (_a = this.resolveConnect) === null || _a === void 0 ? void 0 : _a.call(this);
32
+ this.clearConnecting();
33
+ });
34
+ this.socket.on("disconnect", (reason) => {
35
+ ++this.generation;
36
+ ++this.authAttempt;
37
+ if (reason === "io server disconnect")
38
+ this.connectionError(new PlatformSocketError("connection_failed"));
39
+ });
40
+ this.socket.on("connect_error", (error) => {
41
+ var _a;
42
+ this.connectionError(new PlatformSocketError(((_a = error.data) === null || _a === void 0 ? void 0 : _a.code) === "connection_denied" ? "connection_denied" : "connection_failed"));
43
+ });
44
+ this.socket.io.on("reconnect_failed", () => this.connectionError(new PlatformSocketError("connection_failed")));
45
+ this.socket.on("joined", (raw) => {
46
+ var _a;
47
+ try {
48
+ const joined = decodeJoined(raw);
49
+ (_a = this.subscriptions.get(appFromRoom(joined.room))) === null || _a === void 0 ? void 0 : _a.joined(joined);
50
+ }
51
+ catch (_b) {
52
+ this.protocolError(raw);
53
+ }
54
+ });
55
+ this.socket.on("error", (raw) => this.serverError(raw));
56
+ for (const type of eventNames)
57
+ this.socket.on(type, (raw) => {
58
+ var _a;
59
+ try {
60
+ const appId = eventApp(type, raw);
61
+ if (!appId)
62
+ throw new Error("Invalid app");
63
+ (_a = this.subscriptions.get(appId)) === null || _a === void 0 ? void 0 : _a.event(decode(type, appId, raw));
64
+ }
65
+ catch (_b) {
66
+ this.protocolError(raw);
67
+ }
68
+ });
69
+ }
70
+ /** Connect using a freshly obtained token. Resolves on CONNECT, not on app replay completion.
71
+ * Unexpected transport loss retries up to five times and rejoins active subscriptions.
72
+ * Call again after addressing a connection/auth failure; concurrent calls share one attempt.
73
+ */
74
+ connect() {
75
+ if (this.closed)
76
+ return Promise.reject(new PlatformSocketError("client_closed"));
77
+ if (this.socket.connected)
78
+ return Promise.resolve();
79
+ if (this.connecting)
80
+ return this.connecting;
81
+ const promise = new Promise((resolve, reject) => {
82
+ this.resolveConnect = resolve;
83
+ this.rejectConnect = reject;
84
+ });
85
+ this.connecting = promise;
86
+ this.socket.connect();
87
+ return promise;
88
+ }
89
+ /** Subscribe before or after connecting. One subscription per app, maximum eight.
90
+ * Events and boundary callbacks are awaited in order per app. Failed application,
91
+ * invalid frames or server errors end the subscription without advancing its cursor.
92
+ */
93
+ subscribe(appId, options) {
94
+ if (this.closed)
95
+ throw new PlatformSocketError("client_closed");
96
+ if (!appPattern.test(appId))
97
+ throw new TypeError("appId must be 24 lowercase hexadecimal characters");
98
+ if (options.afterSeq !== undefined && (typeof options.afterSeq !== "string" || !options.afterSeq))
99
+ throw new TypeError("afterSeq must be a nonempty opaque cursor");
100
+ if (this.subscriptions.has(appId))
101
+ throw new TypeError("An app may only have one subscription per client");
102
+ if (this.subscriptions.size >= 8)
103
+ throw new PlatformSocketError("subscription_limit", appId);
104
+ const subscription = new Subscription(appId, { ...options }, () => {
105
+ this.subscriptions.delete(appId);
106
+ this.needsFreshConnection = true;
107
+ if (this.socket.connected)
108
+ this.socket.emit("leave", roomFor(appId));
109
+ });
110
+ this.subscriptions.set(appId, subscription);
111
+ if (this.socket.connected && this.needsFreshConnection) {
112
+ // Leave has no acknowledgement; a new transport fences late events from retired streams.
113
+ this.socket.disconnect();
114
+ void this.connect().catch(() => { }); // Connection errors are delivered through onError.
115
+ }
116
+ else if (this.socket.connected) {
117
+ this.join(subscription, this.generation);
118
+ }
119
+ return subscription;
120
+ }
121
+ /** Stop delivery, cancel reconnection and release all listeners. Idempotent and terminal. */
122
+ close() {
123
+ var _a, _b;
124
+ if (this.closed)
125
+ return;
126
+ this.closed = true;
127
+ ++this.authAttempt;
128
+ (_a = this.cancelAuth) === null || _a === void 0 ? void 0 : _a.call(this);
129
+ ++this.generation;
130
+ for (const subscription of this.subscriptions.values())
131
+ subscription.unsubscribe();
132
+ (_b = this.rejectConnect) === null || _b === void 0 ? void 0 : _b.call(this, new PlatformSocketError("client_closed"));
133
+ this.clearConnecting();
134
+ this.socket.removeAllListeners();
135
+ this.socket.io.removeAllListeners();
136
+ this.socket.disconnect();
137
+ }
138
+ join(subscription, generation) {
139
+ subscription.join((cursor) => {
140
+ if (this.socket.connected && generation === this.generation) {
141
+ this.socket.emit("join", roomFor(subscription.appId), cursor === undefined ? {} : { after_seq: cursor });
142
+ }
143
+ });
144
+ }
145
+ async authenticate(callback) {
146
+ var _a;
147
+ const attempt = ++this.authAttempt;
148
+ (_a = this.cancelAuth) === null || _a === void 0 ? void 0 : _a.call(this);
149
+ let timer;
150
+ const timeout = new Promise((_, reject) => {
151
+ timer = setTimeout(() => reject(new Error("Token timeout")), 20000);
152
+ this.cancelAuth = () => { clearTimeout(timer); reject(new Error("Cancelled")); };
153
+ });
154
+ try {
155
+ const token = await Promise.race([Promise.resolve().then(() => this.options.getToken()), timeout]);
156
+ if (this.closed || attempt !== this.authAttempt)
157
+ return;
158
+ if (typeof token !== "string" || !token.trim())
159
+ throw new Error("Missing token");
160
+ callback({ token });
161
+ }
162
+ catch (_b) {
163
+ if (this.closed || attempt !== this.authAttempt)
164
+ return;
165
+ this.socket.disconnect();
166
+ this.connectionError(new PlatformSocketError("token_unavailable"));
167
+ }
168
+ finally {
169
+ clearTimeout(timer);
170
+ if (attempt === this.authAttempt)
171
+ this.cancelAuth = undefined;
172
+ }
173
+ }
174
+ clearConnecting() {
175
+ this.connecting = undefined;
176
+ this.resolveConnect = undefined;
177
+ this.rejectConnect = undefined;
178
+ }
179
+ connectionError(error) {
180
+ var _a;
181
+ (_a = this.rejectConnect) === null || _a === void 0 ? void 0 : _a.call(this, error);
182
+ this.clearConnecting();
183
+ if (!this.closed)
184
+ notify(this.options.onError, error);
185
+ }
186
+ serverError(raw) {
187
+ var _a;
188
+ try {
189
+ const frame = object(raw);
190
+ const code = errorCodes.find((code) => code === frame.code);
191
+ if (!code)
192
+ throw new Error("Unknown error");
193
+ const appId = appFromRoom(frame.room);
194
+ if (appId)
195
+ (_a = this.subscriptions.get(appId)) === null || _a === void 0 ? void 0 : _a.fail(code);
196
+ else if (frame.room === null)
197
+ this.connectionError(new PlatformSocketError(code));
198
+ else
199
+ throw new Error("Invalid room");
200
+ }
201
+ catch (_b) {
202
+ this.protocolError(raw);
203
+ }
204
+ }
205
+ protocolError(raw) {
206
+ var _a, _b;
207
+ const frame = raw && typeof raw === "object" ? raw : {};
208
+ const appId = (_a = appFromRoom(frame.room)) !== null && _a !== void 0 ? _a : (typeof frame.app_id === "string" && appPattern.test(frame.app_id) ? frame.app_id : undefined);
209
+ if (appId)
210
+ (_b = this.subscriptions.get(appId)) === null || _b === void 0 ? void 0 : _b.fail("protocol_error");
211
+ else {
212
+ // Unknown routing means no app cursor can safely advance past this frame.
213
+ for (const subscription of [...this.subscriptions.values()])
214
+ subscription.fail("protocol_error");
215
+ this.connectionError(new PlatformSocketError("protocol_error"));
216
+ }
11
217
  }
12
218
  }
@@ -1,4 +1,5 @@
1
- import type { PlatformSocketErrorCode } from "./errors.types.js";
1
+ /** Server subscription failures and client transport/processing failures. */
2
+ export type PlatformSocketErrorCode = "invalid_room" | "invalid_cursor" | "access_denied" | "subscription_limit" | "resync_required" | "stream_unavailable" | "connection_denied" | "connection_failed" | "token_unavailable" | "protocol_error" | "handler_failed" | "client_closed";
2
3
  /** Sanitized failure. Original token-provider, handler and server exceptions are not retained. */
3
4
  export declare class PlatformSocketError extends Error {
4
5
  /** Stable machine-readable category. */
@@ -1,7 +1,6 @@
1
- /** Browser platform modules, separate from the runtime and server SDKs. */
1
+ /** Read-only browser platform subscriptions, separate from the runtime and server SDKs. */
2
2
  export { Base44PlatformClient } from "./client.js";
3
3
  export { PlatformSocketError } from "./errors.js";
4
- export type { PlatformSocketErrorCode } from "./errors.types.js";
5
- export type { PlatformClientOptions } from "./client.types.js";
6
- export type { BuilderModule, BuilderInitOptions, BuilderSession, PlatformSubscription, SubscriptionOptions } from "./modules/builder.types.js";
7
- export type { AppUpdate, ChatMessage, ToolCall, QueueItem, QueueUpdate, TaskUpdate, ImageReady, Directive, PlatformEventMap, PlatformEvent, Joined } from "./modules/builder.events.types.js";
4
+ export type { PlatformSocketErrorCode } from "./errors.js";
5
+ export type { PlatformClientOptions, PlatformSubscription, SubscriptionOptions } from "./types.js";
6
+ export type { AppUpdate, ChatMessage, ToolCall, QueueItem, QueueUpdate, TaskUpdate, ImageReady, Directive, PlatformEventMap, PlatformEvent, Joined } from "./events.js";
@@ -1,3 +1,3 @@
1
- /** Browser platform modules, separate from the runtime and server SDKs. */
1
+ /** Read-only browser platform subscriptions, separate from the runtime and server SDKs. */
2
2
  export { Base44PlatformClient } from "./client.js";
3
3
  export { PlatformSocketError } from "./errors.js";
@@ -1,4 +1,4 @@
1
- import type { Joined, PlatformEvent, PlatformEventMap } from "./builder.events.types.js";
1
+ import type { Joined, PlatformEvent, PlatformEventMap } from "./events.js";
2
2
  export declare const eventNames: readonly ["update_model", "directive", "queue_update", "task_update", "image_ready"];
3
3
  export declare const errorCodes: readonly ["invalid_room", "invalid_cursor", "access_denied", "subscription_limit", "resync_required", "stream_unavailable"];
4
4
  export declare const appPattern: RegExp;
@@ -1,7 +1,6 @@
1
- import type { Joined, PlatformEvent } from "./builder.events.types.js";
2
- import { PlatformSocketError } from "../errors.js";
3
- import type { PlatformSocketErrorCode } from "../errors.types.js";
4
- import type { PlatformSubscription, SubscriptionOptions } from "./builder.types.js";
1
+ import type { Joined, PlatformEvent } from "./events.js";
2
+ import { PlatformSocketError, type PlatformSocketErrorCode } from "./errors.js";
3
+ import type { PlatformSubscription, SubscriptionOptions } from "./types.js";
5
4
  /** @internal */
6
5
  export declare function notify(callback: (error: PlatformSocketError) => void, error: PlatformSocketError): void;
7
6
  /** @internal */
@@ -1,4 +1,4 @@
1
- import { PlatformSocketError } from "../errors.js";
1
+ import { PlatformSocketError } from "./errors.js";
2
2
  /** @internal */
3
3
  export function notify(callback, error) {
4
4
  try {
@@ -0,0 +1,33 @@
1
+ import type { Joined, PlatformEvent } from "./events.js";
2
+ import type { PlatformSocketError } from "./errors.js";
3
+ /** Configuration for the browser platform connection. Never supply an API key. */
4
+ export interface PlatformClientOptions {
5
+ /** Origin of the platform service, e.g. https://base44.app. No path/query/credentials. */
6
+ serverUrl: string;
7
+ /** Fetch a browser credential from your backend. Called for every connection attempt. */
8
+ getToken: () => string | Promise<string>;
9
+ /** Connection-level error notification. Errors never include tokens or server exception text. */
10
+ onError: (error: PlatformSocketError) => void;
11
+ }
12
+ /** One app subscription; at most eight may be active per client. */
13
+ export interface SubscriptionOptions {
14
+ /** Last successfully applied cursor for this app; omit for a fresh live boundary. */
15
+ afterSeq?: string;
16
+ /** Apply each event. Delivery is serial per app; rejection pauses this subscription. */
17
+ onEvent: (event: PlatformEvent) => void | Promise<void>;
18
+ /** Handle subscription errors. Reconcile on resync_required; never silently reset a cursor. */
19
+ onError: (error: PlatformSocketError) => void;
20
+ /** Optional replay-complete notification, awaited before advancing to the boundary cursor. */
21
+ onJoined?: (joined: Joined) => void | Promise<void>;
22
+ }
23
+ /** Subscription lifetime and last successfully applied cursor. */
24
+ export interface PlatformSubscription {
25
+ /** App identifier. */
26
+ readonly appId: string;
27
+ /** Last applied event/boundary cursor; persist alongside the state it describes. */
28
+ readonly cursor: string | undefined;
29
+ /** True while subscribed; false after an error or explicit unsubscribe. */
30
+ readonly active: boolean;
31
+ /** Stop this app's delivery and release its subscription slot. Idempotent. */
32
+ unsubscribe(): void;
33
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@base44-preview/sdk",
3
- "version": "0.8.48-pr.286.7d59d14",
3
+ "version": "0.8.48-pr.286.f67ed26",
4
4
  "description": "JavaScript SDK for Base44 API",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -1,7 +0,0 @@
1
- /** Shared configuration for browser platform modules. Never supply an API key. */
2
- export interface PlatformClientOptions {
3
- /** Origin of the platform service, e.g. https://base44.app. No path/query/credentials. */
4
- serverUrl: string;
5
- /** Refresh a browser credential through your backend; called on every builder connection attempt. */
6
- refreshToken: () => string | Promise<string>;
7
- }
@@ -1,2 +0,0 @@
1
- /** Server subscription failures and client transport/processing failures. */
2
- export type PlatformSocketErrorCode = "invalid_room" | "invalid_cursor" | "access_denied" | "subscription_limit" | "resync_required" | "stream_unavailable" | "connection_denied" | "connection_failed" | "token_unavailable" | "protocol_error" | "handler_failed" | "client_closed";
@@ -1,35 +0,0 @@
1
- import type { PlatformClientOptions } from "../client.types.js";
2
- import type { BuilderInitOptions, BuilderSession, PlatformSubscription, SubscriptionOptions } from "./builder.types.js";
3
- /** @internal */
4
- export declare class BuilderSocket implements BuilderSession {
5
- private readonly socket;
6
- private readonly subscriptions;
7
- private readonly options;
8
- private closed;
9
- private needsFreshConnection;
10
- private generation;
11
- private authAttempt;
12
- private cancelAuth?;
13
- private connecting?;
14
- private resolveConnect?;
15
- private rejectConnect?;
16
- constructor(config: PlatformClientOptions, options: BuilderInitOptions);
17
- /** Connect using a freshly obtained token. Resolves on CONNECT, not on app replay completion.
18
- * Unexpected transport loss retries up to five times and rejoins active subscriptions.
19
- * Call again after addressing a connection/auth failure; concurrent calls share one attempt.
20
- */
21
- connect(): Promise<void>;
22
- /** Subscribe before or after connecting. One subscription per app, maximum eight.
23
- * Events and boundary callbacks are awaited in order per app. Failed application,
24
- * invalid frames or server errors end the subscription without advancing its cursor.
25
- */
26
- subscribe(appId: string, options: SubscriptionOptions): PlatformSubscription;
27
- /** Stop delivery, cancel reconnection and release all listeners. Idempotent and terminal. */
28
- close(): void;
29
- private join;
30
- private authenticate;
31
- private clearConnecting;
32
- private connectionError;
33
- private serverError;
34
- private protocolError;
35
- }
@@ -1,213 +0,0 @@
1
- import { io } from "socket.io-client";
2
- import { PlatformSocketError } from "../errors.js";
3
- import { appFromRoom, appPattern, decode, decodeJoined, errorCodes, eventApp, eventNames, object, roomFor } from "./builder-protocol.js";
4
- import { notify, Subscription } from "./builder-subscription.js";
5
- /** @internal */
6
- export class BuilderSocket {
7
- constructor(config, options) {
8
- this.subscriptions = new Map();
9
- this.closed = false;
10
- this.needsFreshConnection = false;
11
- this.generation = 0;
12
- this.authAttempt = 0;
13
- this.options = { ...config, onError: options.onError };
14
- this.socket = io(`${config.serverUrl}/partner`, {
15
- path: "/ws-whitelabel/socket.io/", transports: ["websocket"], autoConnect: false,
16
- forceNew: true, reconnectionAttempts: 5, reconnectionDelay: 1000, reconnectionDelayMax: 10000,
17
- timeout: 20000,
18
- auth: (callback) => { void this.authenticate(callback); },
19
- });
20
- this.socket.on("connect", () => {
21
- var _a;
22
- const generation = ++this.generation;
23
- this.needsFreshConnection = false;
24
- for (const subscription of this.subscriptions.values())
25
- this.join(subscription, generation);
26
- (_a = this.resolveConnect) === null || _a === void 0 ? void 0 : _a.call(this);
27
- this.clearConnecting();
28
- });
29
- this.socket.on("disconnect", (reason) => {
30
- ++this.generation;
31
- ++this.authAttempt;
32
- if (reason === "io server disconnect")
33
- this.connectionError(new PlatformSocketError("connection_failed"));
34
- });
35
- this.socket.on("connect_error", (error) => {
36
- var _a;
37
- this.connectionError(new PlatformSocketError(((_a = error.data) === null || _a === void 0 ? void 0 : _a.code) === "connection_denied" ? "connection_denied" : "connection_failed"));
38
- });
39
- this.socket.io.on("reconnect_failed", () => this.connectionError(new PlatformSocketError("connection_failed")));
40
- this.socket.on("joined", (raw) => {
41
- var _a;
42
- try {
43
- const joined = decodeJoined(raw);
44
- (_a = this.subscriptions.get(appFromRoom(joined.room))) === null || _a === void 0 ? void 0 : _a.joined(joined);
45
- }
46
- catch (_b) {
47
- this.protocolError(raw);
48
- }
49
- });
50
- this.socket.on("error", (raw) => this.serverError(raw));
51
- for (const type of eventNames)
52
- this.socket.on(type, (raw) => {
53
- var _a;
54
- try {
55
- const appId = eventApp(type, raw);
56
- if (!appId)
57
- throw new Error("Invalid app");
58
- (_a = this.subscriptions.get(appId)) === null || _a === void 0 ? void 0 : _a.event(decode(type, appId, raw));
59
- }
60
- catch (_b) {
61
- this.protocolError(raw);
62
- }
63
- });
64
- }
65
- /** Connect using a freshly obtained token. Resolves on CONNECT, not on app replay completion.
66
- * Unexpected transport loss retries up to five times and rejoins active subscriptions.
67
- * Call again after addressing a connection/auth failure; concurrent calls share one attempt.
68
- */
69
- connect() {
70
- if (this.closed)
71
- return Promise.reject(new PlatformSocketError("client_closed"));
72
- if (this.socket.connected)
73
- return Promise.resolve();
74
- if (this.connecting)
75
- return this.connecting;
76
- const promise = new Promise((resolve, reject) => {
77
- this.resolveConnect = resolve;
78
- this.rejectConnect = reject;
79
- });
80
- this.connecting = promise;
81
- this.socket.connect();
82
- return promise;
83
- }
84
- /** Subscribe before or after connecting. One subscription per app, maximum eight.
85
- * Events and boundary callbacks are awaited in order per app. Failed application,
86
- * invalid frames or server errors end the subscription without advancing its cursor.
87
- */
88
- subscribe(appId, options) {
89
- if (this.closed)
90
- throw new PlatformSocketError("client_closed");
91
- if (!appPattern.test(appId))
92
- throw new TypeError("appId must be 24 lowercase hexadecimal characters");
93
- if (options.afterSeq !== undefined && (typeof options.afterSeq !== "string" || !options.afterSeq))
94
- throw new TypeError("afterSeq must be a nonempty opaque cursor");
95
- if (this.subscriptions.has(appId))
96
- throw new TypeError("An app may only have one subscription per builder session");
97
- if (this.subscriptions.size >= 8)
98
- throw new PlatformSocketError("subscription_limit", appId);
99
- const subscription = new Subscription(appId, { ...options }, () => {
100
- this.subscriptions.delete(appId);
101
- this.needsFreshConnection = true;
102
- if (this.socket.connected)
103
- this.socket.emit("leave", roomFor(appId));
104
- });
105
- this.subscriptions.set(appId, subscription);
106
- if (this.socket.connected && this.needsFreshConnection) {
107
- // Leave has no acknowledgement; a new transport fences late events from retired streams.
108
- this.socket.disconnect();
109
- void this.connect().catch(() => { }); // Connection errors are delivered through onError.
110
- }
111
- else if (this.socket.connected) {
112
- this.join(subscription, this.generation);
113
- }
114
- return subscription;
115
- }
116
- /** Stop delivery, cancel reconnection and release all listeners. Idempotent and terminal. */
117
- close() {
118
- var _a, _b;
119
- if (this.closed)
120
- return;
121
- this.closed = true;
122
- ++this.authAttempt;
123
- (_a = this.cancelAuth) === null || _a === void 0 ? void 0 : _a.call(this);
124
- ++this.generation;
125
- for (const subscription of this.subscriptions.values())
126
- subscription.unsubscribe();
127
- (_b = this.rejectConnect) === null || _b === void 0 ? void 0 : _b.call(this, new PlatformSocketError("client_closed"));
128
- this.clearConnecting();
129
- this.socket.removeAllListeners();
130
- this.socket.io.removeAllListeners();
131
- this.socket.disconnect();
132
- }
133
- join(subscription, generation) {
134
- subscription.join((cursor) => {
135
- if (this.socket.connected && generation === this.generation) {
136
- this.socket.emit("join", roomFor(subscription.appId), cursor === undefined ? {} : { after_seq: cursor });
137
- }
138
- });
139
- }
140
- async authenticate(callback) {
141
- var _a;
142
- const attempt = ++this.authAttempt;
143
- (_a = this.cancelAuth) === null || _a === void 0 ? void 0 : _a.call(this);
144
- let timer;
145
- const timeout = new Promise((_, reject) => {
146
- timer = setTimeout(() => reject(new Error("Token timeout")), 20000);
147
- this.cancelAuth = () => { clearTimeout(timer); reject(new Error("Cancelled")); };
148
- });
149
- try {
150
- const token = await Promise.race([Promise.resolve().then(() => this.options.refreshToken()), timeout]);
151
- if (this.closed || attempt !== this.authAttempt)
152
- return;
153
- if (typeof token !== "string" || !token.trim())
154
- throw new Error("Missing token");
155
- callback({ token });
156
- }
157
- catch (_b) {
158
- if (this.closed || attempt !== this.authAttempt)
159
- return;
160
- this.socket.disconnect();
161
- this.connectionError(new PlatformSocketError("token_unavailable"));
162
- }
163
- finally {
164
- clearTimeout(timer);
165
- if (attempt === this.authAttempt)
166
- this.cancelAuth = undefined;
167
- }
168
- }
169
- clearConnecting() {
170
- this.connecting = undefined;
171
- this.resolveConnect = undefined;
172
- this.rejectConnect = undefined;
173
- }
174
- connectionError(error) {
175
- var _a;
176
- (_a = this.rejectConnect) === null || _a === void 0 ? void 0 : _a.call(this, error);
177
- this.clearConnecting();
178
- if (!this.closed)
179
- notify(this.options.onError, error);
180
- }
181
- serverError(raw) {
182
- var _a;
183
- try {
184
- const frame = object(raw);
185
- const code = errorCodes.find((code) => code === frame.code);
186
- if (!code)
187
- throw new Error("Unknown error");
188
- const appId = appFromRoom(frame.room);
189
- if (appId)
190
- (_a = this.subscriptions.get(appId)) === null || _a === void 0 ? void 0 : _a.fail(code);
191
- else if (frame.room === null)
192
- this.connectionError(new PlatformSocketError(code));
193
- else
194
- throw new Error("Invalid room");
195
- }
196
- catch (_b) {
197
- this.protocolError(raw);
198
- }
199
- }
200
- protocolError(raw) {
201
- var _a, _b;
202
- const frame = raw && typeof raw === "object" ? raw : {};
203
- const appId = (_a = appFromRoom(frame.room)) !== null && _a !== void 0 ? _a : (typeof frame.app_id === "string" && appPattern.test(frame.app_id) ? frame.app_id : undefined);
204
- if (appId)
205
- (_b = this.subscriptions.get(appId)) === null || _b === void 0 ? void 0 : _b.fail("protocol_error");
206
- else {
207
- // Unknown routing means no app cursor can safely advance past this frame.
208
- for (const subscription of [...this.subscriptions.values()])
209
- subscription.fail("protocol_error");
210
- this.connectionError(new PlatformSocketError("protocol_error"));
211
- }
212
- }
213
- }
@@ -1,4 +0,0 @@
1
- import type { PlatformClientOptions } from "../client.types.js";
2
- import type { BuilderModule } from "./builder.types.js";
3
- /** @internal */
4
- export declare function createBuilder(config: PlatformClientOptions): BuilderModule;
@@ -1 +0,0 @@
1
- export {};
@@ -1,7 +0,0 @@
1
- import { BuilderSocket } from "./builder-socket.js";
2
- /** @internal */
3
- export function createBuilder(config) {
4
- return {
5
- init(options) { return new BuilderSocket(config, options); },
6
- };
7
- }
@@ -1,48 +0,0 @@
1
- import type { Joined, PlatformEvent } from "./builder.events.types.js";
2
- import type { PlatformSocketError } from "../errors.js";
3
- /** Options for an independent builder socket session. */
4
- export interface BuilderInitOptions {
5
- /** Connection-level errors, sanitized to exclude tokens and server exception text. */
6
- onError: (error: PlatformSocketError) => void;
7
- }
8
- /** Lazy builder module; no socket exists until init is called. */
9
- export interface BuilderModule {
10
- /** Create an independent session without connecting. Call connect on the returned session. */
11
- init(options: BuilderInitOptions): BuilderSession;
12
- }
13
- /** One builder socket session. Owns its subscriptions and connection lifecycle. */
14
- export interface BuilderSession {
15
- /** Connect with a refreshed browser token; resolves on CONNECT, before app replay completes.
16
- * Transport loss retries five times and rejoins active subscriptions from applied cursors.
17
- * Call again after fixing connection/auth failures. Concurrent calls share one attempt.
18
- */
19
- connect(): Promise<void>;
20
- /** Subscribe before or after connecting. One subscription per app, up to eight per session.
21
- * Callbacks run serially per app; errors stop delivery without advancing the cursor.
22
- */
23
- subscribe(appId: string, options: SubscriptionOptions): PlatformSubscription;
24
- /** Stop this session, its subscriptions and reconnection. Idempotent and terminal. */
25
- close(): void;
26
- }
27
- /** One app subscription; at most eight may be active per builder session. */
28
- export interface SubscriptionOptions {
29
- /** Last successfully applied cursor for this app; omit for a fresh live boundary. */
30
- afterSeq?: string;
31
- /** Apply each event. Delivery is serial per app; rejection pauses this subscription. */
32
- onEvent: (event: PlatformEvent) => void | Promise<void>;
33
- /** Handle subscription errors. Reconcile on resync_required; never silently reset a cursor. */
34
- onError: (error: PlatformSocketError) => void;
35
- /** Optional replay-complete notification, awaited before advancing to the boundary cursor. */
36
- onJoined?: (joined: Joined) => void | Promise<void>;
37
- }
38
- /** Subscription lifetime and last successfully applied cursor. */
39
- export interface PlatformSubscription {
40
- /** App identifier. */
41
- readonly appId: string;
42
- /** Last applied event/boundary cursor; persist alongside the state it describes. */
43
- readonly cursor: string | undefined;
44
- /** True while subscribed; false after an error or explicit unsubscribe. */
45
- readonly active: boolean;
46
- /** Stop this app's delivery and release its subscription slot. Idempotent. */
47
- unsubscribe(): void;
48
- }
@@ -1 +0,0 @@
1
- export {};