@chitchat/sdk-web 0.1.0-dev.2 → 0.2.0-dev.1

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/README.md CHANGED
@@ -4,6 +4,10 @@
4
4
 
5
5
  This package is currently a **development prerelease**. Pin the version you have tested before shipping an application.
6
6
 
7
+ ## SDK foundation in v0.2
8
+
9
+ The Web SDK now rejects text or JSON transport frames. `protobuf.encode` and optional `protobuf.encodeAuth` must return binary Protobuf bytes. Connection opening has a bounded timeout and transport failures are isolated from stale sockets.
10
+
7
11
  ## What you need first
8
12
 
9
13
  1. A backend session endpoint created with `@chitchat/sdk-server`. It authenticates the person in your product and returns a short-lived `{ token, expiresAt }` response.
@@ -71,6 +75,8 @@ const chitchatEnvelopeCodec = {
71
75
  };
72
76
  ```
73
77
 
78
+ If the gateway has a distinct authentication envelope, add `encodeAuth({ token, protocol })` to the codec. It must also return binary bytes.
79
+
74
80
  Do not use a JSON mock codec in production. This SDK release exposes the adapter boundary; the generated ChitChat envelope codec and the production handshake compatibility test must come from the deployed platform protocol. If that codec is unavailable, do not enable realtime for users yet.
75
81
 
76
82
  ## Sending and receiving
package/index.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ import type { ReliableRealtimeClient, ReliableRealtimeClientOptions, RealtimeFrame } from '@chitchat/sdk-core';
2
+ export interface ProtobufCodec { encode(frame: RealtimeFrame): ArrayBuffer | ArrayBufferView; decode(binary: ArrayBuffer | Blob): RealtimeFrame; encodeAuth?(input: { token: string; protocol: 'chitchat.protobuf.v1' }): ArrayBuffer | ArrayBufferView; openTimeoutMs?: number; }
3
+ export function toWssEndpoint(endpoint: string, options?: { allowInsecureWs?: boolean }): string;
4
+ export function createWssTransportFactory(options: ProtobufCodec & { WebSocketImpl?: typeof WebSocket; allowInsecureWs?: boolean }): ReliableRealtimeClientOptions['webTransportFactory'];
5
+ export function createChitChatWebClient(options: Omit<ReliableRealtimeClientOptions, 'runtime' | 'webTransportFactory'> & { protobuf: ProtobufCodec; allowInsecureWs?: boolean }): ReliableRealtimeClient;
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "@chitchat/sdk-web",
3
- "version": "0.1.0-dev.2",
3
+ "version": "0.2.0-dev.1",
4
4
  "main": "src/index.js",
5
- "exports": "./src/index.js",
6
- "files": ["src", "README.md", "LICENSE"],
5
+ "exports": { ".": { "types": "./index.d.ts", "require": "./src/index.js", "import": "./src/index.js" } },
6
+ "types": "index.d.ts",
7
+ "files": ["src", "index.d.ts", "README.md", "LICENSE"],
7
8
  "engines": { "node": ">=18" },
8
- "dependencies": { "@chitchat/sdk-core": "0.1.0-dev.2" },
9
+ "dependencies": { "@chitchat/sdk-core": "0.2.0-dev.1" },
9
10
  "license": "UNLICENSED",
10
11
  "publishConfig": { "access": "public", "tag": "development" }
11
12
  }
package/src/index.js CHANGED
@@ -14,27 +14,62 @@ const toWssEndpoint = (endpoint, { allowInsecureWs = false } = {}) => {
14
14
  return url.toString();
15
15
  };
16
16
 
17
- const createWssTransportFactory = ({ WebSocketImpl = globalThis.WebSocket, encode, decode, allowInsecureWs = false }) => {
17
+ const isBinary = (value) => value instanceof ArrayBuffer || ArrayBuffer.isView(value) || (typeof Buffer !== 'undefined' && Buffer.isBuffer(value));
18
+ const binaryFrame = (value, name) => {
19
+ if (!isBinary(value)) throw new Error(`${name} must return a binary Protobuf frame`);
20
+ return value;
21
+ };
22
+
23
+ const createWssTransportFactory = ({ WebSocketImpl = globalThis.WebSocket, encode, decode, encodeAuth, allowInsecureWs = false, openTimeoutMs = 15_000 }) => {
18
24
  if (!WebSocketImpl || typeof encode !== 'function' || typeof decode !== 'function') throw new Error('WebSocket implementation and protobuf encode/decode functions are required');
25
+ if (typeof openTimeoutMs !== 'number' || !Number.isFinite(openTimeoutMs) || openTimeoutMs < 100 || openTimeoutMs > 120_000) throw new Error('openTimeoutMs must be between 100 and 120000');
19
26
  return async ({ endpoint, token, protocol }) => {
20
- let socket; let onMessage = () => {}; let onClose = () => {};
27
+ let socket; let onMessage = () => {}; let onClose = () => {}; let opened = false;
28
+ const encodeFrame = (frame) => binaryFrame(encode(frame), 'protobuf.encode');
29
+ const encodeAuthentication = () => binaryFrame(encodeAuth ? encodeAuth({ token, protocol }) : encode({ type: 'auth', token, protocol }), 'protobuf authentication encoder');
21
30
  return {
22
31
  onMessage: (listener) => { onMessage = listener; },
23
32
  onClose: (listener) => { onClose = listener; },
24
33
  open: () => new Promise((resolve, reject) => {
25
- socket = new WebSocketImpl(toWssEndpoint(endpoint, { allowInsecureWs }), protocol);
26
- socket.binaryType = 'arraybuffer';
27
- socket.onopen = () => { socket.send(encode({ type: 'auth', token })); resolve(); };
28
- socket.onerror = () => reject(new Error('WSS connection failed'));
29
- socket.onclose = (event) => onClose({ code: event.code, reason: event.reason });
30
- socket.onmessage = (event) => onMessage(decode(event.data));
34
+ let timer;
35
+ let settled = false;
36
+ const finish = (handler, value) => { if (settled) return; settled = true; clearTimeout(timer); handler(value); };
37
+ try {
38
+ socket = new WebSocketImpl(toWssEndpoint(endpoint, { allowInsecureWs }), protocol);
39
+ socket.binaryType = 'arraybuffer';
40
+ timer = setTimeout(() => { try { socket.close(); } catch (_) {} finish(reject, new Error('WSS connection timed out')); }, openTimeoutMs);
41
+ socket.onopen = () => {
42
+ try { socket.send(encodeAuthentication()); opened = true; finish(resolve); }
43
+ catch (error) { finish(reject, error); try { socket.close(); } catch (_) {} }
44
+ };
45
+ socket.onerror = () => finish(reject, new Error('WSS connection failed'));
46
+ socket.onclose = (event = {}) => {
47
+ if (!opened) finish(reject, new Error(event.reason || 'WSS connection closed before opening'));
48
+ onClose({ code: event.code, reason: event.reason });
49
+ };
50
+ socket.onmessage = (event) => {
51
+ try { onMessage(decode(event.data)); }
52
+ catch (_) { try { socket.close(); } catch (_) {} onClose({ code: 'PROTOCOL_DECODE_ERROR', reason: 'Invalid Protobuf frame' }); }
53
+ };
54
+ } catch (error) { finish(reject, error); }
31
55
  }),
32
- send: (frame) => socket.send(encode(frame)),
33
- close: () => new Promise((resolve) => { if (!socket || socket.readyState === 3) return resolve(); socket.onclose = () => resolve(); socket.close(); })
56
+ send: (frame) => {
57
+ if (!socket || socket.readyState !== 1) return Promise.reject(new Error('WSS transport is not open'));
58
+ try { socket.send(encodeFrame(frame)); return Promise.resolve(); } catch (error) { return Promise.reject(error); }
59
+ },
60
+ close: () => new Promise((resolve) => {
61
+ if (!socket || socket.readyState === 3) return resolve();
62
+ const current = socket.onclose;
63
+ socket.onclose = (event) => { if (typeof current === 'function') current(event); resolve(); };
64
+ try { socket.close(); } catch (_) { resolve(); }
65
+ })
34
66
  };
35
67
  };
36
68
  };
37
69
 
38
- const createChitChatWebClient = ({ endpoint, tokenProvider, protobuf, allowInsecureWs = false, ...options }) => new ReliableRealtimeClient({ endpoint, tokenProvider, runtime: 'web', webTransportFactory: createWssTransportFactory({ ...protobuf, allowInsecureWs }), ...options });
70
+ const createChitChatWebClient = ({ endpoint, tokenProvider, protobuf, allowInsecureWs = false, ...options }) => {
71
+ if (!protobuf || typeof protobuf !== 'object') throw new Error('protobuf codec is required');
72
+ return new ReliableRealtimeClient({ endpoint, tokenProvider, runtime: 'web', webTransportFactory: createWssTransportFactory({ ...protobuf, allowInsecureWs }), ...options });
73
+ };
39
74
 
40
75
  module.exports = { createChitChatWebClient, createWssTransportFactory, toWssEndpoint };