@4players/odin-plugin-web 1.0.0-alpha.9 → 1.0.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.
Files changed (54) hide show
  1. package/README.md +1 -1
  2. package/cmake/dependencies.cmake +31 -0
  3. package/cmake/options.cmake +53 -0
  4. package/esbuild.config.ts +49 -0
  5. package/lib/plugin/index.d.ts +42 -49
  6. package/lib/plugin/index.js +1 -292
  7. package/lib/worker/shared.js +1 -0
  8. package/lib/worker/unshared.js +1 -0
  9. package/package.json +34 -29
  10. package/lib/plugin/audioController.d.ts +0 -107
  11. package/lib/plugin/audioController.js +0 -488
  12. package/lib/plugin/config.d.ts +0 -10
  13. package/lib/plugin/config.js +0 -10
  14. package/lib/plugin/connectionPool.d.ts +0 -16
  15. package/lib/plugin/connectionPool.js +0 -110
  16. package/lib/plugin/mainConnection.d.ts +0 -39
  17. package/lib/plugin/mainConnection.js +0 -293
  18. package/lib/plugin/roomConnection.d.ts +0 -36
  19. package/lib/plugin/roomConnection.js +0 -303
  20. package/lib/plugin/rpcConnection.d.ts +0 -48
  21. package/lib/plugin/rpcConnection.js +0 -234
  22. package/lib/plugin/token.d.ts +0 -17
  23. package/lib/plugin/token.js +0 -78
  24. package/lib/wasm/index.d.ts +0 -97
  25. package/lib/worker/buffers.d.ts +0 -42
  26. package/lib/worker/buffers.js +0 -79
  27. package/lib/worker/common.d.ts +0 -45
  28. package/lib/worker/common.js +0 -7
  29. package/lib/worker/config.d.ts +0 -8
  30. package/lib/worker/config.js +0 -11
  31. package/lib/worker/index.d.ts +0 -3
  32. package/lib/worker/index.js +0 -1
  33. package/lib/worker/memory.d.ts +0 -9
  34. package/lib/worker/memory.js +0 -29
  35. package/lib/worker/messages.d.ts +0 -14
  36. package/lib/worker/messages.js +0 -15
  37. package/lib/worker/packet.d.ts +0 -11
  38. package/lib/worker/packet.js +0 -59
  39. package/lib/worker/shared/capture.d.ts +0 -28
  40. package/lib/worker/shared/capture.js +0 -63
  41. package/lib/worker/shared/index.d.ts +0 -3
  42. package/lib/worker/shared/index.js +0 -148
  43. package/lib/worker/shared/messages.d.ts +0 -67
  44. package/lib/worker/shared/messages.js +0 -64
  45. package/lib/worker/shared/playback.d.ts +0 -21
  46. package/lib/worker/shared/playback.js +0 -78
  47. package/lib/worker/unshared/capture.d.ts +0 -28
  48. package/lib/worker/unshared/capture.js +0 -66
  49. package/lib/worker/unshared/index.d.ts +0 -3
  50. package/lib/worker/unshared/index.js +0 -171
  51. package/lib/worker/unshared/messages.d.ts +0 -81
  52. package/lib/worker/unshared/messages.js +0 -80
  53. package/lib/worker/unshared/playback.d.ts +0 -23
  54. package/lib/worker/unshared/playback.js +0 -83
@@ -1,10 +0,0 @@
1
- export const HealthCheckTimeout = 1000; /* ms */
2
- export const ClockJumpCheckInterval = 1000; /* ms */
3
- export const ClockJumpThreshold = 10000; /* ms */
4
- export const MainConnectionPingInterval = 10000; /* ms */
5
- export const WebRTCIceTimeout = 3000; /* ms */
6
- export const WebRTCTimeout = 10000; /* ms */
7
- export const ConnectTimeout = 5000; /* ms */
8
- export const MinTokenLifetime = 60000; /* 1min */
9
- export const MinTokenRefreshInterval = 5000; /* 5sec */
10
- export const ConnectInterval = [0, 100, 200, 200, 500, 1000, 2000]; /* ms */
@@ -1,16 +0,0 @@
1
- import { DatagramHandler, MainConnection } from './mainConnection';
2
- import { Result } from '@4players/odin-common';
3
- export declare class ConnectionPool {
4
- readonly onDatagram: DatagramHandler;
5
- private _LastConnectionId;
6
- private readonly _ConnectionsByUrl;
7
- private readonly _ConnectionsById;
8
- private readonly _Close;
9
- get closed(): boolean;
10
- constructor(onDatagram: DatagramHandler);
11
- sendDatagram(connectionId: number, packet: Uint8Array): void;
12
- acquireConnection(url: URL, signal: AbortSignal): Promise<Result<MainConnection> | 'closed'>;
13
- releaseConnection(connection: MainConnection): void;
14
- checkHealth(): Promise<void>;
15
- close(): void;
16
- }
@@ -1,110 +0,0 @@
1
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
- return new (P || (P = Promise))(function (resolve, reject) {
4
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
- step((generator = generator.apply(thisArg, _arguments || [])).next());
8
- });
9
- };
10
- import { ClockJumpCheckInterval, ClockJumpThreshold } from './config';
11
- import { MainConnection } from './mainConnection';
12
- import { failure, nextTick, success } from '@4players/odin-common';
13
- export class ConnectionPool {
14
- get closed() {
15
- return this._Close.signal.aborted;
16
- }
17
- constructor(onDatagram) {
18
- this.onDatagram = onDatagram;
19
- this._LastConnectionId = 0;
20
- this._ConnectionsByUrl = new Map();
21
- this._ConnectionsById = new Map();
22
- this._Close = new AbortController();
23
- let lastCheckAt = Date.now();
24
- const clockJumpDetector = setInterval(() => {
25
- const elapsed = Math.abs(Date.now() - lastCheckAt);
26
- if (elapsed > ClockJumpThreshold)
27
- this.checkHealth();
28
- lastCheckAt = Date.now();
29
- }, ClockJumpCheckInterval);
30
- addEventListener('offline', () => this.checkHealth(), {
31
- signal: this._Close.signal,
32
- });
33
- this._Close.signal.addEventListener('abort', () => {
34
- clearInterval(clockJumpDetector);
35
- for (const entry of this._ConnectionsById.values()) {
36
- if (entry.status === 'ready') {
37
- entry.connection.close();
38
- }
39
- }
40
- this._ConnectionsById.clear();
41
- this._ConnectionsByUrl.clear();
42
- }, { once: true });
43
- }
44
- sendDatagram(connectionId, packet) {
45
- const connection = this._ConnectionsById.get(connectionId);
46
- if (connection !== undefined && connection.status === 'ready') {
47
- connection.connection.sendDatagram(packet);
48
- }
49
- }
50
- acquireConnection(url, signal) {
51
- return __awaiter(this, void 0, void 0, function* () {
52
- yield nextTick(); // slow down acquireConnection
53
- if (this.closed)
54
- return 'closed';
55
- let entry = this._ConnectionsByUrl.get(url);
56
- if (entry === undefined ||
57
- (entry.status === 'ready' && entry.connection.closed)) {
58
- const id = this._LastConnectionId++;
59
- const promise = MainConnection.create(id, url, this.onDatagram, signal);
60
- entry = { status: 'init', id, promise };
61
- this._ConnectionsByUrl.set(url, entry);
62
- this._ConnectionsById.set(id, entry);
63
- const connection = yield promise;
64
- if (connection.type !== 'Success') {
65
- this._ConnectionsByUrl.delete(url);
66
- this._ConnectionsById.delete(id);
67
- return connection;
68
- }
69
- entry = { status: 'ready', connection: connection.value, references: 1 };
70
- this._ConnectionsByUrl.set(url, entry);
71
- this._ConnectionsById.set(id, entry);
72
- return success(entry.connection);
73
- }
74
- else if (entry.status === 'init') {
75
- const onAbort = new Promise((accept) => signal.addEventListener('abort', () => accept(), { once: true }));
76
- const result = yield Promise.race([entry.promise, onAbort]);
77
- if (result === undefined)
78
- return failure('aborted');
79
- return result;
80
- }
81
- else {
82
- entry.references += 1;
83
- return success(entry.connection);
84
- }
85
- });
86
- }
87
- releaseConnection(connection) {
88
- const entry = this._ConnectionsById.get(connection.id);
89
- if (entry === undefined)
90
- return;
91
- if (entry.status === 'init') {
92
- console.error('released connection before it was acquired!');
93
- return;
94
- }
95
- entry.references -= 1;
96
- if (entry.references === 0) {
97
- entry.connection.close();
98
- this._ConnectionsByUrl.delete(connection.url);
99
- this._ConnectionsById.delete(connection.id);
100
- }
101
- }
102
- checkHealth() {
103
- return __awaiter(this, void 0, void 0, function* () {
104
- yield Promise.all(Array.from(this._ConnectionsById.values()).map((entry) => entry.status === 'ready' ? entry.connection.healthCheck() : undefined));
105
- });
106
- }
107
- close() {
108
- this._Close.abort();
109
- }
110
- }
@@ -1,39 +0,0 @@
1
- import { TypedRpcConnection } from './rpcConnection';
2
- import { Token } from './token';
3
- import { MainCommands, MainNotifications, Result } from '@4players/odin-common';
4
- type RpcConnection = TypedRpcConnection<MainCommands, MainNotifications>;
5
- export type DatagramHandler = (connectionId: number, datagram: Uint8Array) => void;
6
- export declare class MainConnection {
7
- readonly id: number;
8
- readonly url: URL;
9
- readonly onDatagram: DatagramHandler;
10
- readonly connection: RpcConnection;
11
- readonly webRtc: RTCPeerConnection;
12
- static create(id: number, url: URL, onDatagram: DatagramHandler, signal: AbortSignal): Promise<Result<MainConnection>>;
13
- private readonly _AudioChannel;
14
- private readonly _PeerIdMap;
15
- private _PeriodicHealthCheck;
16
- private _WebRtcIceTimeout;
17
- private _WebRtcTimeout;
18
- private _ClosePromise;
19
- get closePromise(): Promise<"closed">;
20
- get closeSignal(): AbortSignal;
21
- get closed(): boolean;
22
- webRtcReady(): Promise<boolean>;
23
- private constructor();
24
- close(): void;
25
- joinRoom(token: Token, roomId: string, userData: Uint8Array, position: [number, number, number]): Promise<Result<WebSocket> | 'closed'>;
26
- getReconnectToken(webSocket: WebSocket): Promise<Result<Token>>;
27
- sendDatagram(datagram: Uint8Array): void;
28
- healthCheck(): Promise<boolean>;
29
- private _Request;
30
- private _readEvents;
31
- private _SendOffer;
32
- private _OnWebRtcStatusChanged;
33
- private _OnWebRtcUpdate;
34
- private _UpdateRemoteSdp;
35
- private _AddIceCandidate;
36
- private _OnIceCandidate;
37
- private _CleanUp;
38
- }
39
- export {};
@@ -1,293 +0,0 @@
1
- /* eslint-disable no-dupe-class-members */
2
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
- return new (P || (P = Promise))(function (resolve, reject) {
5
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
- step((generator = generator.apply(thisArg, _arguments || [])).next());
9
- });
10
- };
11
- import { HealthCheckTimeout, MainConnectionPingInterval, WebRTCIceTimeout as WebRtcIceTimeout, WebRTCTimeout as WebRtcTimeout, } from './config';
12
- import { TypedRpcConnection, connect } from './rpcConnection';
13
- import { Token } from './token';
14
- import { MainCommandsRpc, MainNotificationsRpc, assert, extendUrl, failure, success, } from '@4players/odin-common';
15
- export class MainConnection {
16
- static create(id, url, onDatagram, signal) {
17
- return __awaiter(this, void 0, void 0, function* () {
18
- if (signal.aborted)
19
- return failure('aborted');
20
- const webSocketUrl = extendUrl(url, 'main');
21
- if (webSocketUrl.type !== 'Success')
22
- return webSocketUrl;
23
- const webSocket = yield connect(webSocketUrl.value, signal);
24
- if (webSocket.type !== 'Success')
25
- return webSocket;
26
- const connection = new TypedRpcConnection(webSocket.value, MainCommandsRpc, MainNotificationsRpc);
27
- let webRtc;
28
- try {
29
- webRtc = new RTCPeerConnection();
30
- }
31
- catch (reason) {
32
- connection.close();
33
- return failure(`unable to create webrtc connection; ${reason}`);
34
- }
35
- return success(new MainConnection(id, url, onDatagram, connection, webRtc));
36
- });
37
- }
38
- get closePromise() {
39
- return this._ClosePromise;
40
- }
41
- get closeSignal() {
42
- return this.connection.closeSignal;
43
- }
44
- get closed() {
45
- return this.connection.closed;
46
- }
47
- webRtcReady() {
48
- const isReady = () => {
49
- switch (this.webRtc.connectionState) {
50
- case 'connected':
51
- return true;
52
- case 'failed':
53
- case 'closed':
54
- return false;
55
- default:
56
- return undefined;
57
- }
58
- };
59
- const result = isReady();
60
- if (result !== undefined)
61
- return Promise.resolve(result);
62
- return new Promise((accept) => {
63
- const onConnectionStateChanged = () => {
64
- const result = isReady();
65
- if (result !== undefined) {
66
- this.webRtc.removeEventListener('connectionstatechange', onConnectionStateChanged);
67
- accept(result);
68
- }
69
- };
70
- this.webRtc.addEventListener('connectionstatechange', onConnectionStateChanged);
71
- });
72
- }
73
- constructor(id, url, onDatagram, connection, webRtc) {
74
- this.id = id;
75
- this.url = url;
76
- this.onDatagram = onDatagram;
77
- this.connection = connection;
78
- this.webRtc = webRtc;
79
- this._PeerIdMap = new WeakMap();
80
- this.closeSignal.addEventListener('abort', () => this._CleanUp(), {
81
- once: true,
82
- });
83
- this._ClosePromise = new Promise((accept) => {
84
- this.closeSignal.addEventListener('abort', () => accept('closed'), {
85
- once: true,
86
- });
87
- });
88
- this._AudioChannel = this.webRtc.createDataChannel('audio', {
89
- id: 2,
90
- negotiated: true,
91
- ordered: false,
92
- maxRetransmits: 0,
93
- });
94
- this._AudioChannel.binaryType = 'arraybuffer';
95
- this._AudioChannel.onmessage = (event) => onDatagram(this.id, new Uint8Array(event.data));
96
- this.webRtc.onicecandidate = this._OnIceCandidate.bind(this);
97
- this.webRtc.onconnectionstatechange = () => this._OnWebRtcStatusChanged();
98
- this._SendOffer();
99
- if (MainConnectionPingInterval !== undefined) {
100
- this._PeriodicHealthCheck = setInterval(() => this.healthCheck(), MainConnectionPingInterval);
101
- }
102
- this._readEvents();
103
- }
104
- close() {
105
- this.connection.close();
106
- }
107
- joinRoom(token, roomId, userData, position) {
108
- return __awaiter(this, void 0, void 0, function* () {
109
- const response = yield this.connection.request('JoinRoom', {
110
- token: String(token),
111
- room_id: roomId,
112
- user_data: userData,
113
- position,
114
- });
115
- if (response === 'closed' || response.type === 'Failure')
116
- return response;
117
- const responseValue = response.value;
118
- if (!('token' in responseValue)) {
119
- return failure('JoinRoom responded without token');
120
- }
121
- const url = extendUrl(this.url, `room?token=${responseValue.token}`);
122
- if (url.type === 'Failure')
123
- return url;
124
- const webSocket = yield connect(url.value, this.closeSignal);
125
- if (webSocket.type === 'Success') {
126
- this._PeerIdMap.set(webSocket.value, responseValue.peer_id);
127
- }
128
- return webSocket;
129
- });
130
- }
131
- getReconnectToken(webSocket) {
132
- return __awaiter(this, void 0, void 0, function* () {
133
- const peerId = this._PeerIdMap.get(webSocket);
134
- if (peerId === undefined) {
135
- return failure('the webSocket is not part of this connection');
136
- }
137
- const response = yield this._Request('RequestReconnectToken', {
138
- peer_id: peerId,
139
- });
140
- if (response.type === 'Failure')
141
- return response;
142
- return Token.parse(response.value);
143
- });
144
- }
145
- sendDatagram(datagram) {
146
- if (this._AudioChannel.readyState === 'open') {
147
- this._AudioChannel.send(datagram);
148
- }
149
- }
150
- healthCheck() {
151
- return __awaiter(this, void 0, void 0, function* () {
152
- const response = yield this._Request('Ping', {}, HealthCheckTimeout);
153
- const success = response !== 'timeout' && response.type === 'Success';
154
- if (!success)
155
- this.close();
156
- return success;
157
- });
158
- }
159
- _Request(name, properties, timeout) {
160
- return __awaiter(this, void 0, void 0, function* () {
161
- const result = yield (timeout === undefined
162
- ? this.connection.request(name, properties)
163
- : this.connection.request(name, properties, timeout));
164
- if (result === 'closed')
165
- return failure('connection lost');
166
- return result;
167
- });
168
- }
169
- _readEvents() {
170
- return __awaiter(this, void 0, void 0, function* () {
171
- while (true) {
172
- const event = yield this.connection.recv();
173
- if (event === 'closed')
174
- break;
175
- console.debug(`${this.url} received ${event.name} on main`, event.properties);
176
- if (event.name === 'WebRtcUpdate') {
177
- this._OnWebRtcUpdate(event.properties);
178
- }
179
- }
180
- this.close();
181
- });
182
- }
183
- _SendOffer() {
184
- var _a;
185
- return __awaiter(this, void 0, void 0, function* () {
186
- let sdp;
187
- try {
188
- const offer = yield this.webRtc.createOffer();
189
- assert(offer.type === 'offer' && offer.sdp, 'generated broken webrtc offer');
190
- yield this.webRtc.setLocalDescription(offer);
191
- sdp = (_a = offer.sdp) !== null && _a !== void 0 ? _a : '';
192
- }
193
- catch (_b) {
194
- console.error('RTCPeerConnection failed creating an offer');
195
- return;
196
- }
197
- yield this._Request('WebRtcUpdate', {
198
- kind: 'Sdp',
199
- type: 'Offer',
200
- sdp,
201
- media_map: [],
202
- });
203
- });
204
- }
205
- _OnWebRtcStatusChanged() {
206
- console.log(`webrtc ${this.webRtc.connectionState}`);
207
- switch (this.webRtc.connectionState) {
208
- case 'closed':
209
- case 'failed':
210
- this.close();
211
- break;
212
- case 'connected':
213
- clearTimeout(this._WebRtcIceTimeout);
214
- this._WebRtcIceTimeout = undefined;
215
- clearTimeout(this._WebRtcTimeout);
216
- this._WebRtcTimeout = undefined;
217
- break;
218
- case 'disconnected':
219
- this.healthCheck();
220
- // falls through
221
- default:
222
- if (this._WebRtcIceTimeout === undefined) {
223
- this._WebRtcIceTimeout = setTimeout(() => {
224
- this.webRtc.restartIce();
225
- this._WebRtcIceTimeout = undefined;
226
- }, WebRtcIceTimeout);
227
- }
228
- if (this._WebRtcTimeout === undefined) {
229
- this._WebRtcTimeout = setTimeout(() => this.close(), WebRtcTimeout);
230
- }
231
- break;
232
- }
233
- }
234
- _OnWebRtcUpdate(update) {
235
- switch (update.kind) {
236
- case 'Sdp':
237
- this._UpdateRemoteSdp(update);
238
- break;
239
- case 'Trickle':
240
- this._AddIceCandidate(update);
241
- break;
242
- case 'TrickleFinished':
243
- this.webRtc.addIceCandidate(undefined);
244
- break;
245
- }
246
- }
247
- _UpdateRemoteSdp(update) {
248
- return __awaiter(this, void 0, void 0, function* () {
249
- const type = update.type === 'Answer' ? 'answer' : 'offer';
250
- yield this.webRtc.setRemoteDescription({ type, sdp: update.sdp });
251
- });
252
- }
253
- _AddIceCandidate(candidate) {
254
- var _a, _b, _c;
255
- const candidateInit = {
256
- candidate: candidate.candidate,
257
- sdpMid: (_a = candidate.spd_mid) !== null && _a !== void 0 ? _a : undefined,
258
- sdpMLineIndex: (_b = candidate.spd_mline_index) !== null && _b !== void 0 ? _b : undefined,
259
- usernameFragment: (_c = candidate.username_fragment) !== null && _c !== void 0 ? _c : undefined,
260
- };
261
- // HACK/TODO(max): find out why the server is not sending sdpMid or sdpMLineIndex
262
- if (candidateInit.sdpMid === undefined &&
263
- candidateInit.sdpMLineIndex === undefined) {
264
- candidateInit.sdpMid = '0';
265
- }
266
- this.webRtc.addIceCandidate(candidateInit);
267
- }
268
- _OnIceCandidate(event) {
269
- return __awaiter(this, void 0, void 0, function* () {
270
- if (event.candidate) {
271
- const { candidate, sdpMid, sdpMLineIndex, usernameFragment } = event.candidate;
272
- this._Request('WebRtcUpdate', {
273
- kind: 'Trickle',
274
- candidate,
275
- spd_mid: sdpMid !== null && sdpMid !== void 0 ? sdpMid : undefined,
276
- spd_mline_index: sdpMLineIndex !== null && sdpMLineIndex !== void 0 ? sdpMLineIndex : undefined,
277
- username_fragment: usernameFragment !== null && usernameFragment !== void 0 ? usernameFragment : undefined,
278
- });
279
- }
280
- else {
281
- this._Request('WebRtcUpdate', { kind: 'TrickleFinished' });
282
- }
283
- });
284
- }
285
- _CleanUp() {
286
- console.debug(`${this.url} closed`);
287
- this.connection.close();
288
- this.webRtc.close();
289
- clearInterval(this._PeriodicHealthCheck);
290
- clearTimeout(this._WebRtcIceTimeout);
291
- clearTimeout(this._WebRtcTimeout);
292
- }
293
- }
@@ -1,36 +0,0 @@
1
- import { ConnectionPool } from './connectionPool';
2
- import { MainConnection } from './mainConnection';
3
- import { Token } from './token';
4
- import { Backend, RoomCommands, Result } from '@4players/odin-common';
5
- import * as z from 'zod';
6
- export declare class RoomConnection {
7
- readonly connectionPool: ConnectionPool;
8
- readonly onEvent: Backend.OnEvent;
9
- readonly gateway: URL | undefined;
10
- readonly roomId: string;
11
- private _RoomConnection;
12
- private _MainConnection;
13
- private readonly _Close;
14
- private _Token;
15
- private _UserData;
16
- private _Position;
17
- private _LastTokenRenew;
18
- private readonly _Closed;
19
- get token(): Token;
20
- get userData(): Uint8Array;
21
- get position(): [number, number, number];
22
- get closed(): boolean;
23
- get mainConnection(): MainConnection | undefined;
24
- constructor(connectionPool: ConnectionPool, onEvent: Backend.OnEvent, gateway: URL | undefined, token: Token, roomId: string, userData: Uint8Array, position: [number, number, number]);
25
- request(name: string, properties: unknown): Promise<Result<unknown> | 'closed'>;
26
- typedRequest<Name extends Extract<keyof RoomCommands, string>>(name: Name, properties: z.infer<RoomCommands[Name]['request']>): Promise<Result<z.infer<RoomCommands[Name]['response']>> | 'closed'>;
27
- close(): void;
28
- get renewTokenIn(): number;
29
- private _Main;
30
- private _ConnectToServer;
31
- private _ChangeStatus;
32
- private _HandleConnection;
33
- private _HandleEvent;
34
- private _HandleRequests;
35
- private _RenewToken;
36
- }