@droponair/sdk-js 0.18.0 → 0.21.0
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/CHANGELOG.md +38 -0
- package/README.md +38 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +10 -1
- package/dist/transport/auto-select.d.ts +47 -0
- package/dist/transport/auto-select.js +64 -0
- package/dist/transport/sse-transport.d.ts +62 -0
- package/dist/transport/sse-transport.js +122 -0
- package/dist/transport/webtransport-transport.d.ts +61 -0
- package/dist/transport/webtransport-transport.js +124 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,44 @@ This project follows [Semantic Versioning](https://semver.org/).
|
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
+
## [0.21.0], 2026-05-24
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **Transport auto-select helper (Phase 4.6).** New `selectTransport({ httpUrl })` reads `/api/info.transports`, intersects with the runtime's `WebTransport`/`WebSocket`/`EventSource` availability, and returns the best lane (preference: WebTransport -> WebSocket -> SSE; overridable via `preference`). Caller then constructs the corresponding primitive. Pluggable `fetchFn` for Node + tests.
|
|
14
|
+
|
|
15
|
+
### Notes
|
|
16
|
+
|
|
17
|
+
- Foundation for an `init({ transport: 'auto' })` shortcut once full `MessagingClient` integration lands (4.2.1b). Today it's a small standalone helper so existing customers can adopt the pattern without waiting.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## [0.20.0], 2026-05-24
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
|
|
25
|
+
- **WebTransport (HTTP/3) transport primitive (Phase 4.3).** New `WebTransportTransport` class uses the browser-native `WebTransport` API to ride the platform's `/v1/transport/wt` HTTP/3 endpoint, terminated by the `droponair-webtransport` sidecar. Same shape as `SseTransport`: `onFrame`, `sendEnvelope`, `connect`, `close`. Caller brings own protobuf decoder. Browser-only for v1 (Chromium 97+, Firefox 125+, Safari TP-only); Node 22+ has experimental support behind flags.
|
|
26
|
+
|
|
27
|
+
### Notes
|
|
28
|
+
|
|
29
|
+
- WebTransport gives multi-stream perf wins over WebSocket on flaky cellular and similar networks. WebSocket on `/ws` remains the default; this primitive is opt-in for environments that have native WebTransport.
|
|
30
|
+
- The droponair.com endpoint only advertises `transports: ["webtransport"]` once the `droponair-webtransport` sidecar is up and `app.transport.webtransport-enabled` is flipped on sdk-be. Check `GET /api/info.transports` for the live list.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## [0.19.0], 2026-05-24
|
|
35
|
+
|
|
36
|
+
### Added
|
|
37
|
+
|
|
38
|
+
- **SSE transport primitive (Phase 4.2.1).** New `SseTransport` class exposes the platform's HTTP-fallback lane (`/v1/transport/stream` + `/v1/transport/send`) as a small standalone client, for environments where WebSocket upgrades are blocked by corporate firewalls but HTTPS GET/POST work. EventSource-based receive (auto-reconnects), `fetch` POST for send. Bring your own protobuf decoder (use the codec already exported from the SDK).
|
|
39
|
+
|
|
40
|
+
### Notes
|
|
41
|
+
|
|
42
|
+
- v1 is a primitive; full `init({ transport: 'sse' })` integration into `MessagingClient` lands in 4.2.1b. Customers who need the SSE lane today can use `SseTransport` directly alongside the existing WS client.
|
|
43
|
+
- WebSocket on `/ws` remains the default for all SDK calls; this addition is purely opt-in. Older code paths and existing apps are unaffected.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
9
47
|
## [0.18.0], 2026-05-23
|
|
10
48
|
|
|
11
49
|
### Added
|
package/README.md
CHANGED
|
@@ -397,6 +397,44 @@ await client.stopSfuRecording(room.roomId, rec.recordingId);
|
|
|
397
397
|
|
|
398
398
|
Completion lands as the `sfu-recording.completed` webhook (or by polling `listSfuRecordings`) and carries `locationUri` pointing at the finalized file in your bucket. E2EE-encrypted SFU rooms cannot be server-recorded by design (the media server forwards traffic it cannot decrypt) - use the client-side `startRecording` for those.
|
|
399
399
|
|
|
400
|
+
Live call participants also receive in-band notification frames via the existing group-call event callback so a "🔴 RECORDING" UI badge stays in sync. The event types are `GROUP_CALL_SFU_RECORDING_STARTED` (payload = recordingId), `GROUP_CALL_SFU_RECORDING_STOPPED` (recordingId), and `GROUP_CALL_SFU_RECORDING_AVAILABLE` (`recordingId|locationUri`); the platform also notifies late joiners about any already-active SFU recording on the room. Plan limits (`maxSfuRecordingMinutesPerMonth`) are enforced server-side; deny at `startSfuRecording` returns HTTP 403 with a deny reason such as `MONTHLY_SFU_RECORDING_MINUTES_LIMIT_REACHED`.
|
|
401
|
+
|
|
402
|
+
### HTTP fallback (SSE) transport
|
|
403
|
+
|
|
404
|
+
Available since SDK `0.19.0`. For environments where WebSocket upgrades are blocked by a corporate firewall but plain HTTPS GET/POST work, the SDK exports a standalone `SseTransport` primitive that rides the platform's `/v1/transport/stream` (long-lived SSE) and `/v1/transport/send` (POST) endpoints. WebSocket on `/ws` remains the default for `client.connect()`; this primitive is opt-in for the corporate-firewall use case.
|
|
405
|
+
|
|
406
|
+
```typescript
|
|
407
|
+
import { SseTransport } from '@droponair/sdk-js';
|
|
408
|
+
|
|
409
|
+
const sse = new SseTransport({
|
|
410
|
+
httpUrl: 'https://sdk.droponair.com',
|
|
411
|
+
getJwt: async () => jwt,
|
|
412
|
+
});
|
|
413
|
+
sse.onFrame((bytes) => { /* protobuf decode */ });
|
|
414
|
+
await sse.connect();
|
|
415
|
+
await sse.sendEnvelope(envelopeBytes);
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
EventSource-based receive (auto-reconnects + 25s keep-alives that defeat proxy idle timeouts), `fetch` POST for send. Bring your own protobuf decoder (use the codec already exported by the SDK). Verify the lane is enabled on the server via `GET /api/info`: the `transports` array includes `"sse"` and `features` includes `"transport_sse"`. v1 covers 1:1 Envelope delivery; call signaling stays on WebSocket. Full `init({ transport: 'sse' })` integration into `MessagingClient` lands in a follow-up.
|
|
419
|
+
|
|
420
|
+
### WebTransport (HTTP/3) transport
|
|
421
|
+
|
|
422
|
+
Available since SDK `0.20.0`. For modern browsers + environments with native HTTP/3 support, the SDK exports a `WebTransportTransport` primitive that rides the platform's `/v1/transport/wt` endpoint. Multi-stream and lower head-of-line blocking versus WebSocket; particularly useful on flaky cellular networks.
|
|
423
|
+
|
|
424
|
+
```typescript
|
|
425
|
+
import { WebTransportTransport } from '@droponair/sdk-js';
|
|
426
|
+
|
|
427
|
+
const wt = new WebTransportTransport({
|
|
428
|
+
httpUrl: 'https://sdk.droponair.com',
|
|
429
|
+
getJwt: async () => jwt,
|
|
430
|
+
});
|
|
431
|
+
wt.onFrame((bytes) => { /* protobuf decode */ });
|
|
432
|
+
await wt.connect();
|
|
433
|
+
await wt.sendEnvelope(envelopeBytes);
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
Uses the browser-native `WebTransport` API; opens a single bidirectional stream and rides raw bytes both ways. Browser support: Chromium 97+ (stable), Firefox 125+ (stable), Safari Technology Preview only, Node 22+ behind experimental flags. The `droponair.com` endpoint advertises `transports: ["webtransport"]` only when the `droponair-webtransport` sidecar is live; check `GET /api/info.transports` and `features.transport_webtransport` to detect availability. Bring your own protobuf decoder. WebSocket remains the default; this primitive is purely opt-in.
|
|
437
|
+
|
|
400
438
|
### Call Recording
|
|
401
439
|
|
|
402
440
|
Available since SDK `0.16.0`. The SDK **signals** recording state on a group or room call; your app does the actual media capture (`MediaRecorder`) and uploads the file to your own storage — the platform never holds the media. The recording signal is broadcast to every participant (including anyone who joins later); that transparency is enforced server-side.
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,9 @@ export { SDK_VERSION, PROTOCOL_VERSION, PAYLOAD_FORMAT_VERSION } from './version
|
|
|
3
3
|
export declare function initialize(options: InitializeOptions): Promise<DropOnAirClient>;
|
|
4
4
|
export type { InitializeOptions, DropOnAirClient, DropOnAirEvent, MessageCallback, EventCallback, DecryptedMessage, KeyStorageAdapter, CallEvent, CallEventType, CallEventCallback, TurnCredentials, BroadcastMessage, BroadcastCallback, GroupInfo, GroupMemberInfo, DecryptedGroupMessage, GroupMessageCallback, GroupCallEvent, GroupCallEventType, GroupCallEventCallback, Room, RoomPolicy, CreateRoomOptions, UpdateRoomOptions, SfuToken, SfuRecording, MessageEditEvent, MessageEditCallback, MessageDeleteEvent, MessageDeleteCallback, DeviceInfo, ReadReceiptEvent, ReadReceiptCallback, NotificationClearEvent, NotificationClearCallback, DraftSyncEvent, DraftSyncCallback, } from './core/types';
|
|
5
5
|
export type { AttachmentEncryptionType, AttachmentConversationType, AttachmentRef, DeviceWrappedKey, CreateUploadSessionOptions, UploadSession, PrepareAttachmentOptions, DownloadedAttachment, } from './attachment/attachment-types';
|
|
6
|
+
export { SseTransport, type SseTransportOptions, type SseFrameHandler, type SseStateHandler, } from './transport/sse-transport';
|
|
7
|
+
export { WebTransportTransport, type WebTransportTransportOptions, type WTFrameHandler, type WTStateHandler, } from './transport/webtransport-transport';
|
|
8
|
+
export { selectTransport, type TransportLane, type SelectTransportOptions, } from './transport/auto-select';
|
|
6
9
|
declare const _default: {
|
|
7
10
|
initialize: typeof initialize;
|
|
8
11
|
};
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.PAYLOAD_FORMAT_VERSION = exports.PROTOCOL_VERSION = exports.SDK_VERSION = void 0;
|
|
3
|
+
exports.selectTransport = exports.WebTransportTransport = exports.SseTransport = exports.PAYLOAD_FORMAT_VERSION = exports.PROTOCOL_VERSION = exports.SDK_VERSION = void 0;
|
|
4
4
|
exports.initialize = initialize;
|
|
5
5
|
const messaging_client_1 = require("./core/messaging-client");
|
|
6
6
|
const session_manager_1 = require("./core/session-manager");
|
|
@@ -26,4 +26,13 @@ async function initialize(options) {
|
|
|
26
26
|
}
|
|
27
27
|
return client;
|
|
28
28
|
}
|
|
29
|
+
// Phase 4.2.1: HTTP fallback lane primitive for restrictive networks.
|
|
30
|
+
var sse_transport_1 = require("./transport/sse-transport");
|
|
31
|
+
Object.defineProperty(exports, "SseTransport", { enumerable: true, get: function () { return sse_transport_1.SseTransport; } });
|
|
32
|
+
// Phase 4.3: WebTransport (HTTP/3) lane primitive.
|
|
33
|
+
var webtransport_transport_1 = require("./transport/webtransport-transport");
|
|
34
|
+
Object.defineProperty(exports, "WebTransportTransport", { enumerable: true, get: function () { return webtransport_transport_1.WebTransportTransport; } });
|
|
35
|
+
// Phase 4.6: transport auto-select - intersects runtime + platform support.
|
|
36
|
+
var auto_select_1 = require("./transport/auto-select");
|
|
37
|
+
Object.defineProperty(exports, "selectTransport", { enumerable: true, get: function () { return auto_select_1.selectTransport; } });
|
|
29
38
|
exports.default = { initialize };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transport auto-select (Phase 4.6).
|
|
3
|
+
*
|
|
4
|
+
* Tiny helper that probes which transport lanes the runtime AND the platform
|
|
5
|
+
* BOTH support, and returns the best one for this client/host pair.
|
|
6
|
+
*
|
|
7
|
+
* Preference order (best -> fallback):
|
|
8
|
+
* 1. webtransport - multi-stream, lower head-of-line blocking
|
|
9
|
+
* 2. websocket - universal default
|
|
10
|
+
* 3. sse - HTTP-only fallback for restrictive networks
|
|
11
|
+
*
|
|
12
|
+
* The function fetches `<httpUrl>/api/info` once, intersects its
|
|
13
|
+
* `transports` array with the runtime's capabilities, and returns the
|
|
14
|
+
* highest-preference match. Caller then instantiates the corresponding
|
|
15
|
+
* primitive (`WebTransportTransport` / WebSocket / `SseTransport`) and
|
|
16
|
+
* uses it as their lane.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* import { selectTransport, WebTransportTransport, SseTransport } from '@droponair/sdk-js';
|
|
21
|
+
* const lane = await selectTransport({ httpUrl: 'https://sdk.droponair.com' });
|
|
22
|
+
* switch (lane) {
|
|
23
|
+
* case 'webtransport': // construct WebTransportTransport break;
|
|
24
|
+
* case 'sse': // construct SseTransport break;
|
|
25
|
+
* default: // fall through to WebSocket
|
|
26
|
+
* }
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export type TransportLane = 'webtransport' | 'websocket' | 'sse';
|
|
30
|
+
export interface SelectTransportOptions {
|
|
31
|
+
/** Base URL of the platform's REST API (no trailing slash). */
|
|
32
|
+
httpUrl: string;
|
|
33
|
+
/**
|
|
34
|
+
* Override the default WT -> WS -> SSE preference order. Useful for tests
|
|
35
|
+
* or when a specific lane is preferred (e.g. force `sse` first on a known
|
|
36
|
+
* firewalled network).
|
|
37
|
+
*/
|
|
38
|
+
preference?: TransportLane[];
|
|
39
|
+
/** Custom fetch impl, useful for Node polyfills + test doubles. */
|
|
40
|
+
fetchFn?: typeof fetch;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Returns the highest-preference transport lane available on BOTH the
|
|
44
|
+
* runtime and the platform. Throws if no lane is supported (extremely
|
|
45
|
+
* unusual; WebSocket is the universal floor).
|
|
46
|
+
*/
|
|
47
|
+
export declare function selectTransport(options: SelectTransportOptions): Promise<TransportLane>;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Transport auto-select (Phase 4.6).
|
|
4
|
+
*
|
|
5
|
+
* Tiny helper that probes which transport lanes the runtime AND the platform
|
|
6
|
+
* BOTH support, and returns the best one for this client/host pair.
|
|
7
|
+
*
|
|
8
|
+
* Preference order (best -> fallback):
|
|
9
|
+
* 1. webtransport - multi-stream, lower head-of-line blocking
|
|
10
|
+
* 2. websocket - universal default
|
|
11
|
+
* 3. sse - HTTP-only fallback for restrictive networks
|
|
12
|
+
*
|
|
13
|
+
* The function fetches `<httpUrl>/api/info` once, intersects its
|
|
14
|
+
* `transports` array with the runtime's capabilities, and returns the
|
|
15
|
+
* highest-preference match. Caller then instantiates the corresponding
|
|
16
|
+
* primitive (`WebTransportTransport` / WebSocket / `SseTransport`) and
|
|
17
|
+
* uses it as their lane.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* import { selectTransport, WebTransportTransport, SseTransport } from '@droponair/sdk-js';
|
|
22
|
+
* const lane = await selectTransport({ httpUrl: 'https://sdk.droponair.com' });
|
|
23
|
+
* switch (lane) {
|
|
24
|
+
* case 'webtransport': // construct WebTransportTransport break;
|
|
25
|
+
* case 'sse': // construct SseTransport break;
|
|
26
|
+
* default: // fall through to WebSocket
|
|
27
|
+
* }
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
31
|
+
exports.selectTransport = selectTransport;
|
|
32
|
+
const DEFAULT_PREFERENCE = ['webtransport', 'websocket', 'sse'];
|
|
33
|
+
/**
|
|
34
|
+
* Returns the highest-preference transport lane available on BOTH the
|
|
35
|
+
* runtime and the platform. Throws if no lane is supported (extremely
|
|
36
|
+
* unusual; WebSocket is the universal floor).
|
|
37
|
+
*/
|
|
38
|
+
async function selectTransport(options) {
|
|
39
|
+
const httpUrl = options.httpUrl.replace(/\/+$/, '');
|
|
40
|
+
const fetchFn = options.fetchFn ?? globalThis.fetch.bind(globalThis);
|
|
41
|
+
const preference = options.preference ?? DEFAULT_PREFERENCE;
|
|
42
|
+
const res = await fetchFn(`${httpUrl}/api/info`);
|
|
43
|
+
if (!res.ok) {
|
|
44
|
+
throw new Error(`selectTransport: GET /api/info failed (HTTP ${res.status})`);
|
|
45
|
+
}
|
|
46
|
+
const info = await res.json();
|
|
47
|
+
const serverLanes = new Set(info.transports ?? ['websocket']);
|
|
48
|
+
const runtimeSupports = (lane) => {
|
|
49
|
+
switch (lane) {
|
|
50
|
+
case 'webtransport':
|
|
51
|
+
return typeof globalThis.WebTransport === 'function';
|
|
52
|
+
case 'websocket':
|
|
53
|
+
return typeof globalThis.WebSocket === 'function';
|
|
54
|
+
case 'sse':
|
|
55
|
+
return typeof globalThis.EventSource === 'function';
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
for (const lane of preference) {
|
|
59
|
+
if (serverLanes.has(lane) && runtimeSupports(lane)) {
|
|
60
|
+
return lane;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
throw new Error('selectTransport: no transport lane supported by both runtime and platform');
|
|
64
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSE-lane primitive (Phase 4.2.1 v1).
|
|
3
|
+
*
|
|
4
|
+
* Standalone client for the platform's HTTP fallback transport
|
|
5
|
+
* (`GET /v1/transport/stream` for receive, `POST /v1/transport/send` for
|
|
6
|
+
* send). Suitable for environments where WebSocket upgrades are blocked
|
|
7
|
+
* by a corporate firewall but plain HTTPS GET/POST work.
|
|
8
|
+
*
|
|
9
|
+
* v1 surface intentionally narrow: this exposes the bytes lane only.
|
|
10
|
+
* Caller is responsible for parsing the protobuf Envelope (or using the
|
|
11
|
+
* codec exported from the SDK). Full `init({ transport: 'sse' })`
|
|
12
|
+
* integration into `MessagingClient` lands in 4.2.1b once the wire
|
|
13
|
+
* shape stabilises with real-world feedback.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* import { SseTransport } from '@droponair/sdk-js';
|
|
18
|
+
* const sse = new SseTransport({ httpUrl: 'https://sdk.droponair.com', getJwt: async () => myJwt });
|
|
19
|
+
* sse.onFrame((bytes) => { ... your protobuf decode ... });
|
|
20
|
+
* await sse.connect();
|
|
21
|
+
* await sse.sendEnvelope(myEnvelopeBytes);
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export interface SseTransportOptions {
|
|
25
|
+
/** Base URL of the platform's REST API (no trailing slash). */
|
|
26
|
+
httpUrl: string;
|
|
27
|
+
/** Returns a fresh JWT. Called once per connect. */
|
|
28
|
+
getJwt: () => Promise<string>;
|
|
29
|
+
/**
|
|
30
|
+
* Custom fetch implementation, useful for Node-side polyfills or test
|
|
31
|
+
* doubles. Defaults to globalThis.fetch.
|
|
32
|
+
*/
|
|
33
|
+
fetchFn?: typeof fetch;
|
|
34
|
+
/**
|
|
35
|
+
* Custom EventSource constructor, useful for environments (e.g. Node)
|
|
36
|
+
* that need the `eventsource` npm polyfill. Defaults to the global.
|
|
37
|
+
*/
|
|
38
|
+
eventSourceCtor?: typeof EventSource;
|
|
39
|
+
}
|
|
40
|
+
export type SseFrameHandler = (frame: Uint8Array) => void;
|
|
41
|
+
export type SseStateHandler = (state: 'connecting' | 'open' | 'closed' | 'error') => void;
|
|
42
|
+
export declare class SseTransport {
|
|
43
|
+
private readonly httpUrl;
|
|
44
|
+
private readonly getJwt;
|
|
45
|
+
private readonly fetchFn;
|
|
46
|
+
private readonly EventSourceCtor;
|
|
47
|
+
private eventSource;
|
|
48
|
+
private jwt;
|
|
49
|
+
private frameHandler;
|
|
50
|
+
private stateHandler;
|
|
51
|
+
constructor(options: SseTransportOptions);
|
|
52
|
+
/** Subscribe to incoming protobuf frames. Replaces any previous handler. */
|
|
53
|
+
onFrame(handler: SseFrameHandler): void;
|
|
54
|
+
/** Subscribe to lifecycle state changes. Replaces any previous handler. */
|
|
55
|
+
onState(handler: SseStateHandler): void;
|
|
56
|
+
/** Open the long-lived SSE stream. Resolves on first 'ready' event from the server. */
|
|
57
|
+
connect(): Promise<void>;
|
|
58
|
+
/** POST a single protobuf Envelope to the server. */
|
|
59
|
+
sendEnvelope(envelopeBytes: Uint8Array): Promise<void>;
|
|
60
|
+
/** Close the stream. Idempotent. */
|
|
61
|
+
close(): void;
|
|
62
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* SSE-lane primitive (Phase 4.2.1 v1).
|
|
4
|
+
*
|
|
5
|
+
* Standalone client for the platform's HTTP fallback transport
|
|
6
|
+
* (`GET /v1/transport/stream` for receive, `POST /v1/transport/send` for
|
|
7
|
+
* send). Suitable for environments where WebSocket upgrades are blocked
|
|
8
|
+
* by a corporate firewall but plain HTTPS GET/POST work.
|
|
9
|
+
*
|
|
10
|
+
* v1 surface intentionally narrow: this exposes the bytes lane only.
|
|
11
|
+
* Caller is responsible for parsing the protobuf Envelope (or using the
|
|
12
|
+
* codec exported from the SDK). Full `init({ transport: 'sse' })`
|
|
13
|
+
* integration into `MessagingClient` lands in 4.2.1b once the wire
|
|
14
|
+
* shape stabilises with real-world feedback.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* import { SseTransport } from '@droponair/sdk-js';
|
|
19
|
+
* const sse = new SseTransport({ httpUrl: 'https://sdk.droponair.com', getJwt: async () => myJwt });
|
|
20
|
+
* sse.onFrame((bytes) => { ... your protobuf decode ... });
|
|
21
|
+
* await sse.connect();
|
|
22
|
+
* await sse.sendEnvelope(myEnvelopeBytes);
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.SseTransport = void 0;
|
|
27
|
+
class SseTransport {
|
|
28
|
+
constructor(options) {
|
|
29
|
+
this.eventSource = null;
|
|
30
|
+
this.jwt = null;
|
|
31
|
+
this.frameHandler = null;
|
|
32
|
+
this.stateHandler = null;
|
|
33
|
+
this.httpUrl = options.httpUrl.replace(/\/+$/, '');
|
|
34
|
+
this.getJwt = options.getJwt;
|
|
35
|
+
this.fetchFn = options.fetchFn ?? globalThis.fetch.bind(globalThis);
|
|
36
|
+
this.EventSourceCtor = options.eventSourceCtor ?? globalThis.EventSource;
|
|
37
|
+
if (typeof this.EventSourceCtor !== 'function') {
|
|
38
|
+
throw new Error('SseTransport: EventSource is not available; pass options.eventSourceCtor (e.g. the `eventsource` npm package)');
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/** Subscribe to incoming protobuf frames. Replaces any previous handler. */
|
|
42
|
+
onFrame(handler) {
|
|
43
|
+
this.frameHandler = handler;
|
|
44
|
+
}
|
|
45
|
+
/** Subscribe to lifecycle state changes. Replaces any previous handler. */
|
|
46
|
+
onState(handler) {
|
|
47
|
+
this.stateHandler = handler;
|
|
48
|
+
}
|
|
49
|
+
/** Open the long-lived SSE stream. Resolves on first 'ready' event from the server. */
|
|
50
|
+
async connect() {
|
|
51
|
+
this.close();
|
|
52
|
+
this.jwt = await this.getJwt();
|
|
53
|
+
this.stateHandler?.('connecting');
|
|
54
|
+
const url = `${this.httpUrl}/v1/transport/stream?token=${encodeURIComponent(this.jwt)}`;
|
|
55
|
+
return new Promise((resolve, reject) => {
|
|
56
|
+
const es = new this.EventSourceCtor(url);
|
|
57
|
+
const onReady = () => {
|
|
58
|
+
this.stateHandler?.('open');
|
|
59
|
+
resolve();
|
|
60
|
+
};
|
|
61
|
+
es.addEventListener('ready', onReady);
|
|
62
|
+
es.addEventListener('envelope', (e) => {
|
|
63
|
+
const bytes = decodeBase64(e.data);
|
|
64
|
+
this.frameHandler?.(bytes);
|
|
65
|
+
});
|
|
66
|
+
es.onerror = (e) => {
|
|
67
|
+
this.stateHandler?.(es.readyState === EventSource.CLOSED ? 'closed' : 'error');
|
|
68
|
+
if (es.readyState === EventSource.CONNECTING) {
|
|
69
|
+
// EventSource auto-reconnects; nothing for us to do.
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (es.readyState === EventSource.CLOSED) {
|
|
73
|
+
// Hard close, e.g. 401. Reject the initial promise if still pending.
|
|
74
|
+
reject(new Error('SseTransport: stream closed'));
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
this.eventSource = es;
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
/** POST a single protobuf Envelope to the server. */
|
|
81
|
+
async sendEnvelope(envelopeBytes) {
|
|
82
|
+
if (!this.jwt) {
|
|
83
|
+
throw new Error('SseTransport: not connected (call connect() first)');
|
|
84
|
+
}
|
|
85
|
+
const res = await this.fetchFn(`${this.httpUrl}/v1/transport/send`, {
|
|
86
|
+
method: 'POST',
|
|
87
|
+
headers: {
|
|
88
|
+
'Authorization': `Bearer ${this.jwt}`,
|
|
89
|
+
'Content-Type': 'application/octet-stream',
|
|
90
|
+
},
|
|
91
|
+
// BodyInit accepts ArrayBuffer; this avoids a TS type mismatch on
|
|
92
|
+
// Uint8Array<ArrayBufferLike> while sending the same bytes.
|
|
93
|
+
body: envelopeBytes.buffer.slice(envelopeBytes.byteOffset, envelopeBytes.byteOffset + envelopeBytes.byteLength),
|
|
94
|
+
});
|
|
95
|
+
if (!res.ok) {
|
|
96
|
+
throw new Error(`SseTransport: send failed (HTTP ${res.status})`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/** Close the stream. Idempotent. */
|
|
100
|
+
close() {
|
|
101
|
+
if (this.eventSource) {
|
|
102
|
+
this.eventSource.close();
|
|
103
|
+
this.eventSource = null;
|
|
104
|
+
this.stateHandler?.('closed');
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
exports.SseTransport = SseTransport;
|
|
109
|
+
function decodeBase64(b64) {
|
|
110
|
+
if (typeof atob === 'function') {
|
|
111
|
+
const bin = atob(b64);
|
|
112
|
+
const out = new Uint8Array(bin.length);
|
|
113
|
+
for (let i = 0; i < bin.length; i++)
|
|
114
|
+
out[i] = bin.charCodeAt(i);
|
|
115
|
+
return out;
|
|
116
|
+
}
|
|
117
|
+
// Node fallback. Buffer is global there.
|
|
118
|
+
const Buf = globalThis.Buffer;
|
|
119
|
+
if (Buf)
|
|
120
|
+
return Buf.from(b64, 'base64');
|
|
121
|
+
throw new Error('SseTransport: no base64 decoder available (need atob or Buffer)');
|
|
122
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebTransport (HTTP/3) lane primitive (Phase 4.3).
|
|
3
|
+
*
|
|
4
|
+
* Standalone client for the platform's WebTransport endpoint, terminated by
|
|
5
|
+
* the `droponair-webtransport` Go sidecar and bridged into sdk-be `/ws`.
|
|
6
|
+
* Browser-native `WebTransport` API; no polyfill ships with the SDK.
|
|
7
|
+
*
|
|
8
|
+
* Browser support as of 2026-05:
|
|
9
|
+
* - Chromium 97+ (stable)
|
|
10
|
+
* - Firefox 125+ (stable)
|
|
11
|
+
* - Safari Technology Preview only (no stable release yet)
|
|
12
|
+
* - Node 22+ has experimental WebTransport behind a flag
|
|
13
|
+
*
|
|
14
|
+
* v1 surface is intentionally narrow: this exposes the bytes lane only,
|
|
15
|
+
* caller decodes protobuf. Full `init({ transport: 'webtransport' })`
|
|
16
|
+
* MessagingClient integration lands later, mirroring the SSE roadmap.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* import { WebTransportTransport } from '@droponair/sdk-js';
|
|
21
|
+
* const wt = new WebTransportTransport({
|
|
22
|
+
* httpUrl: 'https://sdk.droponair.com',
|
|
23
|
+
* getJwt: async () => myJwt,
|
|
24
|
+
* });
|
|
25
|
+
* wt.onFrame((bytes) => { ... protobuf decode ... });
|
|
26
|
+
* await wt.connect();
|
|
27
|
+
* await wt.sendEnvelope(envelopeBytes);
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export interface WebTransportTransportOptions {
|
|
31
|
+
/** Base URL of the platform's REST API. The WT endpoint is derived as
|
|
32
|
+
* `<httpUrl>/v1/transport/wt?token=<jwt>`. */
|
|
33
|
+
httpUrl: string;
|
|
34
|
+
/** Returns a fresh JWT. Called once per connect. */
|
|
35
|
+
getJwt: () => Promise<string>;
|
|
36
|
+
}
|
|
37
|
+
export type WTFrameHandler = (frame: Uint8Array) => void;
|
|
38
|
+
export type WTStateHandler = (state: 'connecting' | 'open' | 'closed' | 'error') => void;
|
|
39
|
+
export declare class WebTransportTransport {
|
|
40
|
+
private readonly httpUrl;
|
|
41
|
+
private readonly getJwt;
|
|
42
|
+
private session;
|
|
43
|
+
private stream;
|
|
44
|
+
private writer;
|
|
45
|
+
private reader;
|
|
46
|
+
private frameHandler;
|
|
47
|
+
private stateHandler;
|
|
48
|
+
private readLoopAbort;
|
|
49
|
+
constructor(options: WebTransportTransportOptions);
|
|
50
|
+
/** Subscribe to incoming protobuf frames. Replaces any previous handler. */
|
|
51
|
+
onFrame(handler: WTFrameHandler): void;
|
|
52
|
+
/** Subscribe to lifecycle state changes. Replaces any previous handler. */
|
|
53
|
+
onState(handler: WTStateHandler): void;
|
|
54
|
+
/** Open the WebTransport session + a single bidirectional stream. */
|
|
55
|
+
connect(): Promise<void>;
|
|
56
|
+
/** Write a single protobuf Envelope to the bidirectional stream. */
|
|
57
|
+
sendEnvelope(envelopeBytes: Uint8Array): Promise<void>;
|
|
58
|
+
/** Close the session. Idempotent. */
|
|
59
|
+
close(): void;
|
|
60
|
+
private startReadLoop;
|
|
61
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* WebTransport (HTTP/3) lane primitive (Phase 4.3).
|
|
4
|
+
*
|
|
5
|
+
* Standalone client for the platform's WebTransport endpoint, terminated by
|
|
6
|
+
* the `droponair-webtransport` Go sidecar and bridged into sdk-be `/ws`.
|
|
7
|
+
* Browser-native `WebTransport` API; no polyfill ships with the SDK.
|
|
8
|
+
*
|
|
9
|
+
* Browser support as of 2026-05:
|
|
10
|
+
* - Chromium 97+ (stable)
|
|
11
|
+
* - Firefox 125+ (stable)
|
|
12
|
+
* - Safari Technology Preview only (no stable release yet)
|
|
13
|
+
* - Node 22+ has experimental WebTransport behind a flag
|
|
14
|
+
*
|
|
15
|
+
* v1 surface is intentionally narrow: this exposes the bytes lane only,
|
|
16
|
+
* caller decodes protobuf. Full `init({ transport: 'webtransport' })`
|
|
17
|
+
* MessagingClient integration lands later, mirroring the SSE roadmap.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* import { WebTransportTransport } from '@droponair/sdk-js';
|
|
22
|
+
* const wt = new WebTransportTransport({
|
|
23
|
+
* httpUrl: 'https://sdk.droponair.com',
|
|
24
|
+
* getJwt: async () => myJwt,
|
|
25
|
+
* });
|
|
26
|
+
* wt.onFrame((bytes) => { ... protobuf decode ... });
|
|
27
|
+
* await wt.connect();
|
|
28
|
+
* await wt.sendEnvelope(envelopeBytes);
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
32
|
+
exports.WebTransportTransport = void 0;
|
|
33
|
+
class WebTransportTransport {
|
|
34
|
+
constructor(options) {
|
|
35
|
+
this.session = null;
|
|
36
|
+
this.stream = null;
|
|
37
|
+
this.writer = null;
|
|
38
|
+
this.reader = null;
|
|
39
|
+
this.frameHandler = null;
|
|
40
|
+
this.stateHandler = null;
|
|
41
|
+
this.readLoopAbort = false;
|
|
42
|
+
this.httpUrl = options.httpUrl.replace(/\/+$/, '');
|
|
43
|
+
this.getJwt = options.getJwt;
|
|
44
|
+
if (typeof globalThis.WebTransport !== 'function') {
|
|
45
|
+
throw new Error('WebTransportTransport: WebTransport is not available in this runtime. ' +
|
|
46
|
+
'Requires Chromium 97+, Firefox 125+, or Node 22+ with --experimental-* flags.');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/** Subscribe to incoming protobuf frames. Replaces any previous handler. */
|
|
50
|
+
onFrame(handler) { this.frameHandler = handler; }
|
|
51
|
+
/** Subscribe to lifecycle state changes. Replaces any previous handler. */
|
|
52
|
+
onState(handler) { this.stateHandler = handler; }
|
|
53
|
+
/** Open the WebTransport session + a single bidirectional stream. */
|
|
54
|
+
async connect() {
|
|
55
|
+
this.close();
|
|
56
|
+
const jwt = await this.getJwt();
|
|
57
|
+
this.stateHandler?.('connecting');
|
|
58
|
+
const url = `${this.httpUrl.replace(/^http/, 'https')}/v1/transport/wt?token=${encodeURIComponent(jwt)}`;
|
|
59
|
+
const WT = globalThis.WebTransport;
|
|
60
|
+
const session = new WT(url);
|
|
61
|
+
this.session = session;
|
|
62
|
+
await session.ready;
|
|
63
|
+
this.stateHandler?.('open');
|
|
64
|
+
const stream = await session.createBidirectionalStream();
|
|
65
|
+
this.stream = stream;
|
|
66
|
+
this.writer = stream.writable.getWriter();
|
|
67
|
+
this.reader = stream.readable.getReader();
|
|
68
|
+
session.closed
|
|
69
|
+
.then(() => this.stateHandler?.('closed'))
|
|
70
|
+
.catch(() => this.stateHandler?.('error'));
|
|
71
|
+
this.startReadLoop().catch((e) => {
|
|
72
|
+
this.stateHandler?.('error');
|
|
73
|
+
// eslint-disable-next-line no-console
|
|
74
|
+
console.warn('WebTransportTransport: read loop ended', e);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
/** Write a single protobuf Envelope to the bidirectional stream. */
|
|
78
|
+
async sendEnvelope(envelopeBytes) {
|
|
79
|
+
if (!this.writer) {
|
|
80
|
+
throw new Error('WebTransportTransport: not connected (call connect() first)');
|
|
81
|
+
}
|
|
82
|
+
await this.writer.write(envelopeBytes);
|
|
83
|
+
}
|
|
84
|
+
/** Close the session. Idempotent. */
|
|
85
|
+
close() {
|
|
86
|
+
this.readLoopAbort = true;
|
|
87
|
+
if (this.writer) {
|
|
88
|
+
try {
|
|
89
|
+
this.writer.releaseLock();
|
|
90
|
+
}
|
|
91
|
+
catch { }
|
|
92
|
+
this.writer = null;
|
|
93
|
+
}
|
|
94
|
+
if (this.reader) {
|
|
95
|
+
try {
|
|
96
|
+
this.reader.releaseLock();
|
|
97
|
+
}
|
|
98
|
+
catch { }
|
|
99
|
+
this.reader = null;
|
|
100
|
+
}
|
|
101
|
+
if (this.session) {
|
|
102
|
+
try {
|
|
103
|
+
this.session.close();
|
|
104
|
+
}
|
|
105
|
+
catch { }
|
|
106
|
+
this.session = null;
|
|
107
|
+
}
|
|
108
|
+
this.stream = null;
|
|
109
|
+
}
|
|
110
|
+
async startReadLoop() {
|
|
111
|
+
this.readLoopAbort = false;
|
|
112
|
+
if (!this.reader)
|
|
113
|
+
return;
|
|
114
|
+
while (!this.readLoopAbort) {
|
|
115
|
+
const { value, done } = await this.reader.read();
|
|
116
|
+
if (done)
|
|
117
|
+
return;
|
|
118
|
+
if (value && value.byteLength > 0) {
|
|
119
|
+
this.frameHandler?.(value);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
exports.WebTransportTransport = WebTransportTransport;
|
package/dist/version.d.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* MINOR, additive feature (e.g. multi-device payloads, new call event type)
|
|
8
8
|
* PATCH, bug-fix / perf improvement with no wire or API change
|
|
9
9
|
*/
|
|
10
|
-
export declare const SDK_VERSION = "0.
|
|
10
|
+
export declare const SDK_VERSION = "0.21.0";
|
|
11
11
|
/**
|
|
12
12
|
* Binary encrypted-payload format version.
|
|
13
13
|
* Included as the first byte of every encrypted payload so receivers can
|
package/dist/version.js
CHANGED
|
@@ -10,7 +10,7 @@ exports.PROTOCOL_VERSION = exports.PAYLOAD_FORMAT_VERSION = exports.SDK_VERSION
|
|
|
10
10
|
* MINOR, additive feature (e.g. multi-device payloads, new call event type)
|
|
11
11
|
* PATCH, bug-fix / perf improvement with no wire or API change
|
|
12
12
|
*/
|
|
13
|
-
exports.SDK_VERSION = '0.
|
|
13
|
+
exports.SDK_VERSION = '0.21.0';
|
|
14
14
|
/**
|
|
15
15
|
* Binary encrypted-payload format version.
|
|
16
16
|
* Included as the first byte of every encrypted payload so receivers can
|