@xloon/xms-client 0.1.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.
@@ -0,0 +1,52 @@
1
+ import type { EventData, EventName, MethodName, MethodParams, MethodResult } from './protocol.js';
2
+ /**
3
+ * The subset of the WebSocket interface signalingClient depends on. Letting
4
+ * the constructor accept a factory that produces this shape (rather than
5
+ * hard-coding `new WebSocket(url)`) is what makes request/response
6
+ * correlation unit-testable without a real server or a jsdom WebSocket
7
+ * implementation -- tests inject a hand-written fake.
8
+ */
9
+ export interface WebSocketLike {
10
+ send(data: string): void;
11
+ close(code?: number, reason?: string): void;
12
+ readonly readyState: number;
13
+ onopen: ((event: Event) => void) | null;
14
+ onmessage: ((event: MessageEvent) => void) | null;
15
+ onerror: ((event: Event) => void) | null;
16
+ onclose: ((event: CloseEvent) => void) | null;
17
+ }
18
+ export type WebSocketFactory = (url: string) => WebSocketLike;
19
+ export type ConnectionState = 'idle' | 'connecting' | 'open' | 'closed';
20
+ type EventHandler<E extends EventName> = (data: EventData[E]) => void;
21
+ export declare class SignalingClient {
22
+ private readonly url;
23
+ private readonly wsFactory;
24
+ private ws;
25
+ private state;
26
+ private nextRequestId;
27
+ private readonly pending;
28
+ private readonly eventListeners;
29
+ private readonly openHandlers;
30
+ private readonly closeHandlers;
31
+ private readonly errorHandlers;
32
+ constructor(url: string, wsFactory?: WebSocketFactory);
33
+ getState(): ConnectionState;
34
+ connect(): Promise<void>;
35
+ disconnect(): void;
36
+ /**
37
+ * Sends a correlated request and resolves/rejects when the matching
38
+ * {type:"response", id} arrives. Rejects with the server's `error` object
39
+ * when `ok:false`, and with a synthetic ProtocolError if the connection is
40
+ * not open or closes before a response arrives -- never silently hangs.
41
+ */
42
+ request<M extends MethodName>(method: M, params: MethodParams[M]): Promise<MethodResult[M]>;
43
+ /** Subscribes to an unsolicited server event. Returns an unsubscribe function. */
44
+ on<E extends EventName>(event: E, handler: EventHandler<E>): () => void;
45
+ onOpen(handler: () => void): () => void;
46
+ onClose(handler: (event: CloseEvent) => void): () => void;
47
+ onError(handler: (event: Event) => void): () => void;
48
+ private generateRequestId;
49
+ private rejectAllPending;
50
+ private handleMessage;
51
+ }
52
+ export {};
@@ -0,0 +1,165 @@
1
+ const CONNECTION_CLOSED_ERROR = {
2
+ code: 'CONNECTION_CLOSED',
3
+ message: 'The signaling WebSocket connection closed before a response was received.',
4
+ };
5
+ const NOT_CONNECTED_ERROR = {
6
+ code: 'NOT_CONNECTED',
7
+ message: 'The signaling WebSocket is not connected.',
8
+ };
9
+ function defaultWebSocketFactory(url) {
10
+ return new WebSocket(url);
11
+ }
12
+ export class SignalingClient {
13
+ url;
14
+ wsFactory;
15
+ ws = null;
16
+ state = 'idle';
17
+ nextRequestId = 0;
18
+ pending = new Map();
19
+ eventListeners = new Map();
20
+ openHandlers = new Set();
21
+ closeHandlers = new Set();
22
+ errorHandlers = new Set();
23
+ constructor(url, wsFactory = defaultWebSocketFactory) {
24
+ this.url = url;
25
+ this.wsFactory = wsFactory;
26
+ }
27
+ getState() {
28
+ return this.state;
29
+ }
30
+ connect() {
31
+ if (this.state === 'open') {
32
+ return Promise.resolve();
33
+ }
34
+ this.state = 'connecting';
35
+ return new Promise((resolve, reject) => {
36
+ const ws = this.wsFactory(this.url);
37
+ this.ws = ws;
38
+ ws.onopen = () => {
39
+ this.state = 'open';
40
+ for (const handler of this.openHandlers)
41
+ handler();
42
+ resolve();
43
+ };
44
+ ws.onmessage = (event) => {
45
+ this.handleMessage(event.data);
46
+ };
47
+ ws.onerror = (event) => {
48
+ for (const handler of this.errorHandlers)
49
+ handler(event);
50
+ if (this.state === 'connecting') {
51
+ reject(new Error('Signaling WebSocket failed to connect.'));
52
+ }
53
+ };
54
+ ws.onclose = (event) => {
55
+ this.state = 'closed';
56
+ this.rejectAllPending(CONNECTION_CLOSED_ERROR);
57
+ for (const handler of this.closeHandlers)
58
+ handler(event);
59
+ };
60
+ });
61
+ }
62
+ disconnect() {
63
+ this.ws?.close();
64
+ }
65
+ /**
66
+ * Sends a correlated request and resolves/rejects when the matching
67
+ * {type:"response", id} arrives. Rejects with the server's `error` object
68
+ * when `ok:false`, and with a synthetic ProtocolError if the connection is
69
+ * not open or closes before a response arrives -- never silently hangs.
70
+ */
71
+ request(method, params) {
72
+ if (!this.ws || this.state !== 'open') {
73
+ return Promise.reject(NOT_CONNECTED_ERROR);
74
+ }
75
+ const id = this.generateRequestId();
76
+ const message = { type: 'request', id, method, params };
77
+ return new Promise((resolve, reject) => {
78
+ this.pending.set(id, {
79
+ resolve: resolve,
80
+ reject,
81
+ });
82
+ try {
83
+ this.ws.send(JSON.stringify(message));
84
+ }
85
+ catch (err) {
86
+ this.pending.delete(id);
87
+ reject({ code: 'SEND_FAILED', message: `Failed to send request: ${String(err)}` });
88
+ }
89
+ });
90
+ }
91
+ /** Subscribes to an unsolicited server event. Returns an unsubscribe function. */
92
+ on(event, handler) {
93
+ let handlers = this.eventListeners.get(event);
94
+ if (!handlers) {
95
+ handlers = new Set();
96
+ this.eventListeners.set(event, handlers);
97
+ }
98
+ handlers.add(handler);
99
+ return () => {
100
+ handlers.delete(handler);
101
+ };
102
+ }
103
+ onOpen(handler) {
104
+ this.openHandlers.add(handler);
105
+ return () => this.openHandlers.delete(handler);
106
+ }
107
+ onClose(handler) {
108
+ this.closeHandlers.add(handler);
109
+ return () => this.closeHandlers.delete(handler);
110
+ }
111
+ onError(handler) {
112
+ this.errorHandlers.add(handler);
113
+ return () => this.errorHandlers.delete(handler);
114
+ }
115
+ generateRequestId() {
116
+ this.nextRequestId += 1;
117
+ return `c-${this.nextRequestId.toString(36)}`;
118
+ }
119
+ rejectAllPending(error) {
120
+ for (const { reject } of this.pending.values()) {
121
+ reject(error);
122
+ }
123
+ this.pending.clear();
124
+ }
125
+ handleMessage(raw) {
126
+ let message;
127
+ try {
128
+ message = JSON.parse(raw);
129
+ }
130
+ catch (err) {
131
+ console.error('signalingClient: received malformed JSON message', raw, err);
132
+ return;
133
+ }
134
+ if (message.type === 'response') {
135
+ const pending = this.pending.get(message.id);
136
+ if (!pending) {
137
+ console.warn(`signalingClient: response for unknown/expired request id "${message.id}"`, message);
138
+ return;
139
+ }
140
+ this.pending.delete(message.id);
141
+ if (message.ok) {
142
+ pending.resolve(message.result);
143
+ }
144
+ else {
145
+ pending.reject(message.error);
146
+ }
147
+ return;
148
+ }
149
+ if (message.type === 'event') {
150
+ const handlers = this.eventListeners.get(message.event);
151
+ if (!handlers || handlers.size === 0) {
152
+ // No listener registered for this event -- expected for events no
153
+ // currently-mounted component cares about; nothing to correlate
154
+ // since events never carry a request id.
155
+ return;
156
+ }
157
+ for (const handler of handlers) {
158
+ handler(message.data);
159
+ }
160
+ return;
161
+ }
162
+ console.warn('signalingClient: received message with unknown "type"', message);
163
+ }
164
+ }
165
+ //# sourceMappingURL=signaling.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signaling.js","sourceRoot":"","sources":["../src/signaling.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@xloon/xms-client",
3
+ "version": "0.1.0",
4
+ "description": "Browser client SDK for the Xloon Media Server (XMS)",
5
+ "license": "Apache-2.0",
6
+ "keywords": [
7
+ "webrtc",
8
+ "sfu",
9
+ "video-conferencing",
10
+ "xms",
11
+ "xloon"
12
+ ],
13
+ "type": "module",
14
+ "main": "./dist/index.js",
15
+ "types": "./dist/index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./dist/index.d.ts",
19
+ "import": "./dist/index.js"
20
+ }
21
+ },
22
+ "files": [
23
+ "dist",
24
+ "LICENSE",
25
+ "NOTICE"
26
+ ],
27
+ "sideEffects": false,
28
+ "scripts": {
29
+ "build": "tsc -p tsconfig.build.json",
30
+ "typecheck": "tsc -p tsconfig.json --noEmit",
31
+ "test": "vitest run",
32
+ "prepublishOnly": "npm run typecheck && npm test && npm run build"
33
+ },
34
+ "publishConfig": {
35
+ "access": "public"
36
+ },
37
+ "devDependencies": {
38
+ "jsdom": "^30.1.1",
39
+ "typescript": "~6.0.2",
40
+ "vitest": "^5.0.2"
41
+ }
42
+ }