@droponair/sdk-js 0.19.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 CHANGED
@@ -6,6 +6,31 @@ 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
+
9
34
  ## [0.19.0], 2026-05-24
10
35
 
11
36
  ### 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
@@ -4,6 +4,8 @@ export declare function initialize(options: InitializeOptions): Promise<DropOnAi
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
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';
7
9
  declare const _default: {
8
10
  initialize: typeof initialize;
9
11
  };
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.SseTransport = 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");
@@ -29,4 +29,10 @@ async function initialize(options) {
29
29
  // Phase 4.2.1: HTTP fallback lane primitive for restrictive networks.
30
30
  var sse_transport_1 = require("./transport/sse-transport");
31
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; } });
32
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,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.19.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.19.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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@droponair/sdk-js",
3
- "version": "0.19.0",
3
+ "version": "0.21.0",
4
4
  "description": "DropOnAir SDK for end-to-end encrypted messaging",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",