@base44-preview/platform 0.0.0-stage → 0.1.0-pr.312.2ef55e3
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/LICENSE +21 -0
- package/README.md +163 -2
- package/dist/client.d.ts +31 -0
- package/dist/client.js +37 -0
- package/dist/client.types.d.ts +36 -0
- package/dist/client.types.js +1 -0
- package/dist/errors.d.ts +47 -0
- package/dist/errors.js +47 -0
- package/dist/errors.types.d.ts +96 -0
- package/dist/errors.types.js +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +3 -0
- package/dist/modules/builder-protocol.d.ts +21 -0
- package/dist/modules/builder-protocol.js +39 -0
- package/dist/modules/builder-socket.d.ts +42 -0
- package/dist/modules/builder-socket.js +251 -0
- package/dist/modules/builder-subscription.d.ts +23 -0
- package/dist/modules/builder-subscription.js +55 -0
- package/dist/modules/builder.d.ts +4 -0
- package/dist/modules/builder.events.generated.d.ts +2194 -0
- package/dist/modules/builder.events.generated.js +3 -0
- package/dist/modules/builder.events.types.d.ts +58 -0
- package/dist/modules/builder.events.types.js +1 -0
- package/dist/modules/builder.js +7 -0
- package/dist/modules/builder.types.d.ts +162 -0
- package/dist/modules/builder.types.js +1 -0
- package/package.json +58 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 base44
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,164 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @base44/platform
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
```sh
|
|
4
|
+
npm install @base44/platform
|
|
5
|
+
```
|
|
6
|
+
|
|
7
|
+
This package implements the read-only white-label builder socket contract: a
|
|
8
|
+
partner's browser watches the builder chat of up to eight apps over the platform's
|
|
9
|
+
shared socket. It is independent of the app SDK (`@base44/sdk`).
|
|
10
|
+
The workspace must have white-label sockets enabled; this SDK does not open sessions.
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
import { createPlatformClient } from "@base44/platform";
|
|
14
|
+
|
|
15
|
+
const client = createPlatformClient({
|
|
16
|
+
socketUrl, // the session's socket_url, from POST /api/service/socket-sessions
|
|
17
|
+
async getSessionToken() {
|
|
18
|
+
const response = await fetch("/api/builder-socket-session", { method: "POST" });
|
|
19
|
+
if (!response.ok) throw new Error("Session request failed");
|
|
20
|
+
return (await response.json()).session_token;
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
const builder = client.builder.init({
|
|
24
|
+
onError(error) { showConnectionError(error.code); },
|
|
25
|
+
});
|
|
26
|
+
const subscription = builder.subscribe(appId, {
|
|
27
|
+
onSnapshot(snapshot) { replaceAppState(snapshot); },
|
|
28
|
+
async onEvent(event) { await applyEvent(event); }, // awaited before the next event
|
|
29
|
+
onError(error) { showSubscriptionError(error.code); },
|
|
30
|
+
});
|
|
31
|
+
await builder.connect();
|
|
32
|
+
// On view teardown:
|
|
33
|
+
subscription.unsubscribe();
|
|
34
|
+
builder.close();
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Sessions
|
|
38
|
+
|
|
39
|
+
`/api/builder-socket-session` is your backend's route, not an SDK endpoint. It opens a
|
|
40
|
+
socket session with its workspace key (`apps:watch` scope) through
|
|
41
|
+
`POST /api/service/socket-sessions` with `{ app_ids }`, and returns only the
|
|
42
|
+
`session_token` and `socket_url` to the browser. Pass `socket_url` as `socketUrl`. Never pass the workspace key or any other server
|
|
43
|
+
credential to this client. Subscribe only to apps on the session's allowlist; your
|
|
44
|
+
backend adds or removes apps with `PUT`/`DELETE /api/service/socket-sessions/{session_id}/rooms/{app_id}`.
|
|
45
|
+
|
|
46
|
+
A session lasts one hour and is never extended. The token goes exclusively in
|
|
47
|
+
CONNECT `auth.session_token`, never a URL, and the SDK keeps it in memory only. It
|
|
48
|
+
is reused across automatic reconnects. `getSessionToken` is called when a builder
|
|
49
|
+
session first connects, and again only when the server rejects the cached token or
|
|
50
|
+
expires the session; the SDK then reconnects once with the new token. Retrieval has
|
|
51
|
+
a twenty-second timeout and reports `session_unavailable` on failure.
|
|
52
|
+
|
|
53
|
+
One live socket per session: a newer connection with the same token replaces the
|
|
54
|
+
older one, which stops with `session_replaced`. Open one session per page.
|
|
55
|
+
|
|
56
|
+
## Lifecycle
|
|
57
|
+
|
|
58
|
+
`createPlatformClient({ socketUrl, getSessionToken })` creates a lightweight module
|
|
59
|
+
container: no Socket.IO instance, timers, token request or network activity. Its
|
|
60
|
+
`builder` module follows the server SDK's module-factory pattern.
|
|
61
|
+
|
|
62
|
+
`client.builder.init({ onError })` synchronously creates an independent `BuilderSession`
|
|
63
|
+
without connecting. Repeated calls create separate sessions, each owning its own
|
|
64
|
+
socket, listeners, subscriptions and cleanup. Close the returned session when its
|
|
65
|
+
view is disposed; the root client and other sessions remain usable.
|
|
66
|
+
|
|
67
|
+
`builder.connect()` resolves on CONNECT, before any snapshot. The socket uses the
|
|
68
|
+
default namespace on `/ws/socket.io/`, WebSocket only, with a dedicated manager.
|
|
69
|
+
Transport reconnection uses five attempts, starting at one second and capped at ten
|
|
70
|
+
seconds with jitter; each attempt has a twenty-second timeout. A temporary server
|
|
71
|
+
refusal is retried the same way. After exhaustion, a refusal or `session_replaced`,
|
|
72
|
+
fix the cause and call `builder.connect()`.
|
|
73
|
+
|
|
74
|
+
`builder.subscribe(appId, options)` returns a handle with `appId`, `active` and
|
|
75
|
+
idempotent `unsubscribe()`. App IDs are 24 lowercase hexadecimal characters. One
|
|
76
|
+
subscription per app and at most eight subscriptions are allowed per builder session.
|
|
77
|
+
Subscriptions can be created before connecting. `builder.close()` is terminal for that
|
|
78
|
+
session; call `client.builder.init(...)` again to start another.
|
|
79
|
+
|
|
80
|
+
## Snapshots and events
|
|
81
|
+
|
|
82
|
+
There is no replay cursor. Every join, including each rejoin after a reconnect, is
|
|
83
|
+
answered with a `Snapshot`: the app's current `status` and its last 50 public
|
|
84
|
+
messages. After a main-conversation rewrite (undo, restore, sync), the SDK delivers
|
|
85
|
+
`conversation.changed` and rejoins the room, so a fresh snapshot follows. Treat a
|
|
86
|
+
snapshot as the app's current state. The app's live events follow it, including
|
|
87
|
+
those that landed while it was read. It holds only the last 50 messages, so merge
|
|
88
|
+
by `id` if you keep longer history.
|
|
89
|
+
|
|
90
|
+
Delivery is serialized **per app**, snapshots included; a slow app does not block
|
|
91
|
+
others. Callbacks must settle; they cannot be forcibly cancelled. The client bounds
|
|
92
|
+
pending delivery to 1,000 items per app. Error observers are synchronous notifications;
|
|
93
|
+
their exceptions are isolated so they cannot interrupt another app's delivery.
|
|
94
|
+
|
|
95
|
+
## Public shapes
|
|
96
|
+
|
|
97
|
+
- `PlatformClientOptions` (`client.types.ts`): `socketUrl`, `getSessionToken`.
|
|
98
|
+
- `BuilderModule` (`modules/builder.types.ts`): lazy `init(options)` factory.
|
|
99
|
+
- `BuilderInitOptions`: connection-level `onError` observer.
|
|
100
|
+
- `BuilderSession`: `connect()`, `subscribe(appId, options)`, `close()`.
|
|
101
|
+
- `SubscriptionOptions`: required `onSnapshot`, `onEvent` and `onError`.
|
|
102
|
+
- `PlatformSubscription`: read-only `appId`, `active`, and `unsubscribe()`.
|
|
103
|
+
- `PlatformEvent`, `PlatformEventMap`, `PlatformSnapshot`, `ToolCall`, `GuardApproval`
|
|
104
|
+
(`modules/builder.events.types.ts`), derived from the generated event types.
|
|
105
|
+
- Error codes in `errors.types.ts`.
|
|
106
|
+
|
|
107
|
+
`PlatformEvent` is a **discriminated union**: switch on `event.type` to narrow
|
|
108
|
+
`event.data`. Each delivery contains `type`, `appId` and `data`. On the wire every
|
|
109
|
+
event is `{room, data}`; the SDK routes by `room` and validates the envelope, while
|
|
110
|
+
payload schemas and private-field filtering are the server's. Omitted keys mean
|
|
111
|
+
unchanged and explicit null clears. Events carrying `branch_id` belong to that branch;
|
|
112
|
+
null or absent is main. Unknown event names are not forwarded. `PlatformSnapshot` is
|
|
113
|
+
the service's `Snapshot` plus the app's `room`. Structural filtering does not promise
|
|
114
|
+
redaction of generated prose or user content.
|
|
115
|
+
|
|
116
|
+
## Event types
|
|
117
|
+
|
|
118
|
+
The socket protocol is defined by the service's AsyncAPI document, committed as
|
|
119
|
+
`asyncapi.json`. `src/modules/builder.events.generated.ts` is generated from it with
|
|
120
|
+
`json-schema-to-typescript`: one type per schema, named after the backend model
|
|
121
|
+
(`Message`, `StatusObject`, `QueueState`, one `*Call` per tool, one `*Approval` per
|
|
122
|
+
guard), plus `ServerEventMap` and `ClientMessageMap`. Never edit it by hand.
|
|
123
|
+
|
|
124
|
+
To pick up a protocol change:
|
|
125
|
+
|
|
126
|
+
```sh
|
|
127
|
+
# Production serves only beta and GA events, so point at a dev backend while events are alpha.
|
|
128
|
+
npm run sync:asyncapi -w @base44/platform -- http://localhost:8000/api/asyncapi.json
|
|
129
|
+
npm run gen:events -w @base44/platform
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
`sync:asyncapi` also reads `ASYNCAPI_URL`, defaults to production, and refuses a
|
|
133
|
+
document with no messages. CI runs `npm run check:events` and fails when the
|
|
134
|
+
generated file differs from what `asyncapi.json` produces.
|
|
135
|
+
|
|
136
|
+
## Reference docs
|
|
137
|
+
|
|
138
|
+
Every exported shape and field has JSDoc. `npm run docs` validates it into `docs`.
|
|
139
|
+
`npm run create-docs` runs the app SDK's TypeDoc and Mintlify pipeline for this package,
|
|
140
|
+
and `npm run copy-docs-local -- --target <mintlify-docs>` copies the result into
|
|
141
|
+
`developers/references/platform-sdk/docs`. The pipeline's config for this package lives
|
|
142
|
+
in `scripts/mintlify-post-processing/`. The Socket messages page is built in mintlify-docs from
|
|
143
|
+
the service's AsyncAPI document, not here.
|
|
144
|
+
|
|
145
|
+
`PlatformSocketError` extends `Error` with `code` and optional `appId`. Original
|
|
146
|
+
exceptions, payloads and credentials are never attached.
|
|
147
|
+
|
|
148
|
+
| Error code | Action |
|
|
149
|
+
| --- | --- |
|
|
150
|
+
| `connection_denied` | The session was rejected even with a fresh token: the workspace, key or session is not usable. |
|
|
151
|
+
| `session_unavailable` | `getSessionToken` failed or timed out; no provider details are forwarded. |
|
|
152
|
+
| `session_replaced` | Another connection with the same session took over. Stop, or open a new session. |
|
|
153
|
+
| `connection_failed` | Transport failure or exhausted retries; call `connect()` after fixing connectivity. |
|
|
154
|
+
| `access_denied` | The join was refused: the app is not on the session's allowlist, or joins were rate limited. |
|
|
155
|
+
| `access_revoked` | The app left the session's allowlist; do not resubscribe. |
|
|
156
|
+
| `snapshot_unavailable` | Joined, but the snapshot failed. Live events still flow; the subscription stays active. |
|
|
157
|
+
| `subscription_limit` | Release a subscription before adding another. |
|
|
158
|
+
| `delivery_overflow` | 1,000 deliveries queued for the app; subscribe again for a fresh snapshot. |
|
|
159
|
+
| `protocol_error` | Invalid routing or envelope. Stop and investigate. |
|
|
160
|
+
| `handler_failed` | An application callback failed; fix it and subscribe again. |
|
|
161
|
+
| `client_closed` | The builder session was explicitly closed; initialize another session. |
|
|
162
|
+
|
|
163
|
+
No SDK dependency, package version or lockfile changes are required. Socket.IO
|
|
164
|
+
remains the existing SDK dependency. Chat commands and HTTP APIs are outside this entry point.
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { PlatformClient, PlatformClientOptions } from "./client.types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Creates a platform client.
|
|
4
|
+
*
|
|
5
|
+
* This is the entry point of the Platform SDK. The client gives your white-label app editor
|
|
6
|
+
* access to the platform's modules, such as [`builder`](/developers/references/platform-sdk/docs/interfaces/builder).
|
|
7
|
+
*
|
|
8
|
+
* @param options - Configuration object for the client.
|
|
9
|
+
* @returns A configured platform client with access to the platform's modules.
|
|
10
|
+
* @throws {TypeError} When `socketUrl` isn't an HTTP or HTTPS origin.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* // Create a client for your app editor
|
|
15
|
+
* import { createPlatformClient } from '@base44/platform';
|
|
16
|
+
*
|
|
17
|
+
* const client = createPlatformClient({
|
|
18
|
+
* socketUrl,
|
|
19
|
+
* async getSessionToken() {
|
|
20
|
+
* const response = await fetch('/api/builder-socket-session', { method: 'POST' });
|
|
21
|
+
* const { session_token } = await response.json();
|
|
22
|
+
* return session_token;
|
|
23
|
+
* },
|
|
24
|
+
* });
|
|
25
|
+
*
|
|
26
|
+
* // Use the client to follow the AI chat in an app
|
|
27
|
+
* const builder = client.builder.init({ onError: (error) => console.error(error.code) });
|
|
28
|
+
* await builder.connect();
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export declare function createPlatformClient(options: PlatformClientOptions): PlatformClient;
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { createBuilder } from "./modules/builder.js";
|
|
2
|
+
/**
|
|
3
|
+
* Creates a platform client.
|
|
4
|
+
*
|
|
5
|
+
* This is the entry point of the Platform SDK. The client gives your white-label app editor
|
|
6
|
+
* access to the platform's modules, such as [`builder`](/developers/references/platform-sdk/docs/interfaces/builder).
|
|
7
|
+
*
|
|
8
|
+
* @param options - Configuration object for the client.
|
|
9
|
+
* @returns A configured platform client with access to the platform's modules.
|
|
10
|
+
* @throws {TypeError} When `socketUrl` isn't an HTTP or HTTPS origin.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* // Create a client for your app editor
|
|
15
|
+
* import { createPlatformClient } from '@base44/platform';
|
|
16
|
+
*
|
|
17
|
+
* const client = createPlatformClient({
|
|
18
|
+
* socketUrl,
|
|
19
|
+
* async getSessionToken() {
|
|
20
|
+
* const response = await fetch('/api/builder-socket-session', { method: 'POST' });
|
|
21
|
+
* const { session_token } = await response.json();
|
|
22
|
+
* return session_token;
|
|
23
|
+
* },
|
|
24
|
+
* });
|
|
25
|
+
*
|
|
26
|
+
* // Use the client to follow the AI chat in an app
|
|
27
|
+
* const builder = client.builder.init({ onError: (error) => console.error(error.code) });
|
|
28
|
+
* await builder.connect();
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export function createPlatformClient(options) {
|
|
32
|
+
const url = new URL(options.socketUrl);
|
|
33
|
+
if (!["https:", "http:"].includes(url.protocol) || url.username || url.password || url.search || url.hash || url.pathname !== "/") {
|
|
34
|
+
throw new TypeError("socketUrl must be an HTTP(S) origin without credentials, path, query or fragment");
|
|
35
|
+
}
|
|
36
|
+
return Object.freeze({ builder: Object.freeze(createBuilder({ ...options, socketUrl: url.origin })) });
|
|
37
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { BuilderModule } from "./modules/builder.types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Options for {@linkcode createPlatformClient | createPlatformClient()}.
|
|
4
|
+
*
|
|
5
|
+
* The client runs in the browser and never sees your workspace API key. Your server opens a socket
|
|
6
|
+
* session with the key and hands the browser only the session's token and socket URL.
|
|
7
|
+
*/
|
|
8
|
+
export interface PlatformClientOptions {
|
|
9
|
+
/**
|
|
10
|
+
* Origin of the platform socket.
|
|
11
|
+
*
|
|
12
|
+
* Use the `socket_url` your server receives from `POST /api/service/socket-sessions`. It's the same
|
|
13
|
+
* for every session in an environment, so your server can pass it to the page once. It must be an
|
|
14
|
+
* origin with no path, query, fragment, or credentials.
|
|
15
|
+
*/
|
|
16
|
+
socketUrl: string;
|
|
17
|
+
/**
|
|
18
|
+
* Returns a socket session token from your server.
|
|
19
|
+
*
|
|
20
|
+
* Your server opens a session with `POST /api/service/socket-sessions` and returns its
|
|
21
|
+
* `session_token`. The client calls this when it connects, and again when the token expires.
|
|
22
|
+
* Return a new token on every call.
|
|
23
|
+
*
|
|
24
|
+
* @returns The session token, or a promise resolving to it.
|
|
25
|
+
*/
|
|
26
|
+
getSessionToken: () => string | Promise<string>;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The platform client.
|
|
30
|
+
*
|
|
31
|
+
* Provides access to the platform's modules.
|
|
32
|
+
*/
|
|
33
|
+
export interface PlatformClient {
|
|
34
|
+
/** [Builder module](/developers/references/platform-sdk/docs/interfaces/builder) for following the AI chat in your users' apps live. */
|
|
35
|
+
readonly builder: BuilderModule;
|
|
36
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { PlatformSocketErrorCode } from "./errors.types.js";
|
|
2
|
+
/**
|
|
3
|
+
* The error the SDK reports when something goes wrong in a builder session.
|
|
4
|
+
*
|
|
5
|
+
* You don't create it. The SDK passes it to your `onError` callbacks, `connect()` rejects with it,
|
|
6
|
+
* and `subscribe()` throws it. Check `code` to see what happened, then look the code up below:
|
|
7
|
+
* - [Session error codes](#sessionerrorcode) go to the `onError` you pass to `init()`. The whole
|
|
8
|
+
* session is affected, and `appId` isn't set.
|
|
9
|
+
* - [App error codes](#apperrorcode) go to the subscription's `onError`. Only that app is affected,
|
|
10
|
+
* and `appId` says which one.
|
|
11
|
+
*
|
|
12
|
+
* The error never carries the session token, a callback's exception, or the server's exception text.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* // Handle session and app errors
|
|
17
|
+
* const builder = client.builder.init({
|
|
18
|
+
* // Session error codes: the whole session is affected
|
|
19
|
+
* onError(error) {
|
|
20
|
+
* if (error.code === 'session_revoked') showSignedOut();
|
|
21
|
+
* },
|
|
22
|
+
* });
|
|
23
|
+
*
|
|
24
|
+
* builder.subscribe(appId, {
|
|
25
|
+
* onSnapshot: renderChat,
|
|
26
|
+
* onEvent: applyEvent,
|
|
27
|
+
* // App error codes: only this app is affected, and error.appId says which
|
|
28
|
+
* onError(error) {
|
|
29
|
+
* if (error.code === 'access_revoked') hideLiveUpdates(error.appId);
|
|
30
|
+
* },
|
|
31
|
+
* });
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export declare class PlatformSocketError extends Error {
|
|
35
|
+
/** What happened. One of the [session error codes](#sessionerrorcode) or [app error codes](#apperrorcode). */
|
|
36
|
+
readonly code: PlatformSocketErrorCode;
|
|
37
|
+
/** ID of the app the error is about. Set for app error codes, and not set for session error codes. */
|
|
38
|
+
readonly appId?: string;
|
|
39
|
+
/**
|
|
40
|
+
* Creates an error. The SDK creates these; you don't need to.
|
|
41
|
+
*
|
|
42
|
+
* @param code - Error code.
|
|
43
|
+
* @param appId - ID of the app the error is about.
|
|
44
|
+
* @internal
|
|
45
|
+
*/
|
|
46
|
+
constructor(code: PlatformSocketErrorCode, appId?: string);
|
|
47
|
+
}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The error the SDK reports when something goes wrong in a builder session.
|
|
3
|
+
*
|
|
4
|
+
* You don't create it. The SDK passes it to your `onError` callbacks, `connect()` rejects with it,
|
|
5
|
+
* and `subscribe()` throws it. Check `code` to see what happened, then look the code up below:
|
|
6
|
+
* - [Session error codes](#sessionerrorcode) go to the `onError` you pass to `init()`. The whole
|
|
7
|
+
* session is affected, and `appId` isn't set.
|
|
8
|
+
* - [App error codes](#apperrorcode) go to the subscription's `onError`. Only that app is affected,
|
|
9
|
+
* and `appId` says which one.
|
|
10
|
+
*
|
|
11
|
+
* The error never carries the session token, a callback's exception, or the server's exception text.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* // Handle session and app errors
|
|
16
|
+
* const builder = client.builder.init({
|
|
17
|
+
* // Session error codes: the whole session is affected
|
|
18
|
+
* onError(error) {
|
|
19
|
+
* if (error.code === 'session_revoked') showSignedOut();
|
|
20
|
+
* },
|
|
21
|
+
* });
|
|
22
|
+
*
|
|
23
|
+
* builder.subscribe(appId, {
|
|
24
|
+
* onSnapshot: renderChat,
|
|
25
|
+
* onEvent: applyEvent,
|
|
26
|
+
* // App error codes: only this app is affected, and error.appId says which
|
|
27
|
+
* onError(error) {
|
|
28
|
+
* if (error.code === 'access_revoked') hideLiveUpdates(error.appId);
|
|
29
|
+
* },
|
|
30
|
+
* });
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export class PlatformSocketError extends Error {
|
|
34
|
+
/**
|
|
35
|
+
* Creates an error. The SDK creates these; you don't need to.
|
|
36
|
+
*
|
|
37
|
+
* @param code - Error code.
|
|
38
|
+
* @param appId - ID of the app the error is about.
|
|
39
|
+
* @internal
|
|
40
|
+
*/
|
|
41
|
+
constructor(code, appId) {
|
|
42
|
+
super(`Platform socket: ${code}`);
|
|
43
|
+
this.name = "PlatformSocketError";
|
|
44
|
+
this.code = code;
|
|
45
|
+
this.appId = appId;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codes of errors that affect the whole session. They go to the `onError` you pass to `init()`, and
|
|
3
|
+
* `connect()` rejects with them. Retryable means the same call can succeed again without changing anything.
|
|
4
|
+
*/
|
|
5
|
+
export type SessionErrorCode =
|
|
6
|
+
/**
|
|
7
|
+
* `getSessionToken` threw, took longer than 20 seconds or returned no token.
|
|
8
|
+
*
|
|
9
|
+
* Check the endpoint that opens sessions, then call `connect()` again. Retryable.
|
|
10
|
+
*/
|
|
11
|
+
"session_unavailable"
|
|
12
|
+
/**
|
|
13
|
+
* Base44 refused the session even with a fresh token: the token is invalid, or live updates aren't enabled for the workspace.
|
|
14
|
+
*
|
|
15
|
+
* Check that your server opens a new session on every call. Not retryable.
|
|
16
|
+
*/
|
|
17
|
+
| "connection_denied"
|
|
18
|
+
/**
|
|
19
|
+
* The socket couldn't connect after five retries, or the server closed it.
|
|
20
|
+
*
|
|
21
|
+
* Call `connect()` again, for example when the tab next gets focus. Retryable.
|
|
22
|
+
*/
|
|
23
|
+
| "connection_failed"
|
|
24
|
+
/**
|
|
25
|
+
* Your server or an admin ended the session, or its key lost access.
|
|
26
|
+
*
|
|
27
|
+
* Open a new session from your server if the page should still follow apps. Not retryable.
|
|
28
|
+
*/
|
|
29
|
+
| "session_revoked"
|
|
30
|
+
/**
|
|
31
|
+
* Another socket connected with the same session token.
|
|
32
|
+
*
|
|
33
|
+
* Use one session per tab. Don't reconnect automatically; call `connect()` only to take the session back on purpose. Not retryable.
|
|
34
|
+
*/
|
|
35
|
+
| "session_replaced"
|
|
36
|
+
/**
|
|
37
|
+
* A session frame didn't match the protocol.
|
|
38
|
+
*
|
|
39
|
+
* Update the SDK, then call `connect()` again. Not retryable.
|
|
40
|
+
*/
|
|
41
|
+
| "protocol_error"
|
|
42
|
+
/**
|
|
43
|
+
* You called `connect()` or `subscribe()` after `close()`.
|
|
44
|
+
*
|
|
45
|
+
* Create a new session with `init()`. Not retryable.
|
|
46
|
+
*/
|
|
47
|
+
| "client_closed";
|
|
48
|
+
/**
|
|
49
|
+
* Codes of errors for one app. They go to the subscription's `onError`, and `subscribe()` throws
|
|
50
|
+
* `subscription_limit`. Every one ends the subscription except `snapshot_unavailable`.
|
|
51
|
+
*/
|
|
52
|
+
export type AppErrorCode =
|
|
53
|
+
/**
|
|
54
|
+
* The app isn't on the session's allowlist, or the socket joins too often.
|
|
55
|
+
*
|
|
56
|
+
* Add the app to the session from your server, then subscribe again. Not retryable.
|
|
57
|
+
*/
|
|
58
|
+
"access_denied"
|
|
59
|
+
/**
|
|
60
|
+
* The app left the session's allowlist, or its workspace.
|
|
61
|
+
*
|
|
62
|
+
* Stop showing the app's live updates. Not retryable.
|
|
63
|
+
*/
|
|
64
|
+
| "access_revoked"
|
|
65
|
+
/**
|
|
66
|
+
* The subscription is active but its snapshot failed. Events keep arriving.
|
|
67
|
+
*
|
|
68
|
+
* Load the app's messages over the Apps API instead.
|
|
69
|
+
*/
|
|
70
|
+
| "snapshot_unavailable"
|
|
71
|
+
/**
|
|
72
|
+
* The session already has eight subscriptions.
|
|
73
|
+
*
|
|
74
|
+
* Unsubscribe from an app, or open another session. Not retryable.
|
|
75
|
+
*/
|
|
76
|
+
| "subscription_limit"
|
|
77
|
+
/**
|
|
78
|
+
* More than 1,000 deliveries waited for your callbacks.
|
|
79
|
+
*
|
|
80
|
+
* Make the callbacks faster, then subscribe again for a fresh snapshot. Retryable.
|
|
81
|
+
*/
|
|
82
|
+
| "delivery_overflow"
|
|
83
|
+
/**
|
|
84
|
+
* Your `onSnapshot` or `onEvent` callback threw or rejected.
|
|
85
|
+
*
|
|
86
|
+
* Fix the callback, then subscribe again. Not retryable.
|
|
87
|
+
*/
|
|
88
|
+
| "handler_failed"
|
|
89
|
+
/**
|
|
90
|
+
* An event for this app didn't match the protocol.
|
|
91
|
+
*
|
|
92
|
+
* Update the SDK, then subscribe again. Not retryable.
|
|
93
|
+
*/
|
|
94
|
+
| "protocol_error";
|
|
95
|
+
/** Every code a {@link PlatformSocketError} reports. */
|
|
96
|
+
export type PlatformSocketErrorCode = SessionErrorCode | AppErrorCode;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Browser platform modules, separate from the runtime and server SDKs. */
|
|
2
|
+
export { createPlatformClient } from "./client.js";
|
|
3
|
+
export { PlatformSocketError } from "./errors.js";
|
|
4
|
+
export type { AppErrorCode, PlatformSocketErrorCode, SessionErrorCode } from "./errors.types.js";
|
|
5
|
+
export type { PlatformClient, PlatformClientOptions } from "./client.types.js";
|
|
6
|
+
export type { BuilderModule, BuilderInitOptions, BuilderSession, PlatformSubscription, SubscriptionOptions } from "./modules/builder.types.js";
|
|
7
|
+
export type { GuardApproval, PlatformEvent, PlatformEventMap, PlatformSnapshot, ToolCall } from "./modules/builder.events.types.js";
|
|
8
|
+
export type * from "./modules/builder.events.generated.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { PlatformEvent, PlatformEventMap, PlatformSnapshot } from "./builder.events.types.js";
|
|
2
|
+
/** App events delivered to `onEvent`: every server event except the session events the socket handles. */
|
|
3
|
+
export declare const eventNames: ("message.updated" | "message.removed" | "app.status_changed" | "preview.reload_requested" | "preview.navigation_requested" | "queue.updated" | "task.progressed" | "image.resolved" | "conversation.changed" | "files.changed" | "branch.deleted" | "repository.changed" | "pull_request.changed")[];
|
|
4
|
+
/** Notices that name an app room, by the error code each one reports. A new notice fails to compile until it has one. */
|
|
5
|
+
export declare const roomNotices: {
|
|
6
|
+
readonly "room.access_denied": "access_denied";
|
|
7
|
+
readonly "room.access_revoked": "access_revoked";
|
|
8
|
+
readonly "room.snapshot_unavailable": "snapshot_unavailable";
|
|
9
|
+
};
|
|
10
|
+
export declare const sessionEndings: {
|
|
11
|
+
readonly expired: "session_expired";
|
|
12
|
+
readonly revoked: "session_revoked";
|
|
13
|
+
readonly replaced: "session_replaced";
|
|
14
|
+
};
|
|
15
|
+
export declare const appPattern: RegExp;
|
|
16
|
+
export declare const roomFor: (appId: string) => string;
|
|
17
|
+
export declare function object(value: unknown): Record<string, unknown>;
|
|
18
|
+
export declare function appFromRoom(value: unknown): string | undefined;
|
|
19
|
+
/** Every event is `{room, data}`; payload schemas are the service's, so only the envelope is validated. */
|
|
20
|
+
export declare function decode(type: keyof PlatformEventMap, appId: string, raw: unknown): PlatformEvent;
|
|
21
|
+
export declare function decodeSnapshot(raw: unknown): PlatformSnapshot;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { serverEventNames } from "./builder.events.generated.js";
|
|
2
|
+
const sessionEvents = ["app.snapshot", "session.ended"];
|
|
3
|
+
/** App events delivered to `onEvent`: every server event except the session events the socket handles. */
|
|
4
|
+
export const eventNames = serverEventNames.filter((name) => !sessionEvents.includes(name) && !name.startsWith("room."));
|
|
5
|
+
/** Notices that name an app room, by the error code each one reports. A new notice fails to compile until it has one. */
|
|
6
|
+
export const roomNotices = {
|
|
7
|
+
"room.access_denied": "access_denied",
|
|
8
|
+
"room.access_revoked": "access_revoked",
|
|
9
|
+
"room.snapshot_unavailable": "snapshot_unavailable",
|
|
10
|
+
};
|
|
11
|
+
// `expired` never reaches the partner: the session renews its token.
|
|
12
|
+
export const sessionEndings = {
|
|
13
|
+
expired: "session_expired",
|
|
14
|
+
revoked: "session_revoked",
|
|
15
|
+
replaced: "session_replaced",
|
|
16
|
+
};
|
|
17
|
+
export const appPattern = /^[a-f0-9]{24}$/;
|
|
18
|
+
export const roomFor = (appId) => `/apps/${appId}`;
|
|
19
|
+
export function object(value) {
|
|
20
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
21
|
+
throw new Error("Invalid frame");
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
export function appFromRoom(value) {
|
|
25
|
+
return typeof value === "string" && /^\/apps\/[a-f0-9]{24}$/.test(value) ? value.slice(6) : undefined;
|
|
26
|
+
}
|
|
27
|
+
/** Every event is `{room, data}`; payload schemas are the service's, so only the envelope is validated. */
|
|
28
|
+
export function decode(type, appId, raw) {
|
|
29
|
+
return { type, appId, data: object(object(raw).data) };
|
|
30
|
+
}
|
|
31
|
+
export function decodeSnapshot(raw) {
|
|
32
|
+
const frame = object(raw);
|
|
33
|
+
const data = object(frame.data);
|
|
34
|
+
if (!appFromRoom(frame.room) || !Array.isArray(data.messages) || (data.status !== null && typeof data.status !== "object")) {
|
|
35
|
+
throw new Error("Invalid snapshot");
|
|
36
|
+
}
|
|
37
|
+
const queue = data.queue !== null && typeof data.queue === "object" ? { queue: data.queue } : {};
|
|
38
|
+
return { room: frame.room, status: data.status, messages: data.messages, ...queue };
|
|
39
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
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 token?;
|
|
10
|
+
private tokenFresh;
|
|
11
|
+
private ending?;
|
|
12
|
+
private denialRetries;
|
|
13
|
+
private retryTimer?;
|
|
14
|
+
private authAttempt;
|
|
15
|
+
private cancelAuth?;
|
|
16
|
+
private connecting?;
|
|
17
|
+
private resolveConnect?;
|
|
18
|
+
private rejectConnect?;
|
|
19
|
+
constructor(config: PlatformClientOptions, options: BuilderInitOptions);
|
|
20
|
+
/** Connect with the session token. Resolves on CONNECT, before snapshots arrive.
|
|
21
|
+
* Transport loss retries up to five times and rejoins active subscriptions.
|
|
22
|
+
* Call again after a failure or `session_replaced`; concurrent calls share one attempt.
|
|
23
|
+
*/
|
|
24
|
+
connect(): Promise<void>;
|
|
25
|
+
/** Subscribe before or after connecting. One subscription per app, maximum eight, each on the
|
|
26
|
+
* session's allowlist. Snapshots and events are awaited in order per app; a failed callback,
|
|
27
|
+
* an invalid frame or a server refusal ends the subscription.
|
|
28
|
+
*/
|
|
29
|
+
subscribe(appId: string, options: SubscriptionOptions): PlatformSubscription;
|
|
30
|
+
/** Stop delivery, cancel reconnection and release all listeners. Idempotent and terminal. */
|
|
31
|
+
close(): void;
|
|
32
|
+
private join;
|
|
33
|
+
private authenticate;
|
|
34
|
+
/** A connect the server refused. Transport errors are Socket.IO's to retry. */
|
|
35
|
+
private refused;
|
|
36
|
+
private retryDenied;
|
|
37
|
+
private clearConnecting;
|
|
38
|
+
private connectionError;
|
|
39
|
+
private sessionEnded;
|
|
40
|
+
private roomNotice;
|
|
41
|
+
private protocolError;
|
|
42
|
+
}
|