@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.
- package/README.md +1 -1
- package/cmake/dependencies.cmake +31 -0
- package/cmake/options.cmake +53 -0
- package/esbuild.config.ts +49 -0
- package/lib/plugin/index.d.ts +42 -49
- package/lib/plugin/index.js +1 -292
- package/lib/worker/shared.js +1 -0
- package/lib/worker/unshared.js +1 -0
- package/package.json +34 -29
- package/lib/plugin/audioController.d.ts +0 -107
- package/lib/plugin/audioController.js +0 -488
- package/lib/plugin/config.d.ts +0 -10
- package/lib/plugin/config.js +0 -10
- package/lib/plugin/connectionPool.d.ts +0 -16
- package/lib/plugin/connectionPool.js +0 -110
- package/lib/plugin/mainConnection.d.ts +0 -39
- package/lib/plugin/mainConnection.js +0 -293
- package/lib/plugin/roomConnection.d.ts +0 -36
- package/lib/plugin/roomConnection.js +0 -303
- package/lib/plugin/rpcConnection.d.ts +0 -48
- package/lib/plugin/rpcConnection.js +0 -234
- package/lib/plugin/token.d.ts +0 -17
- package/lib/plugin/token.js +0 -78
- package/lib/wasm/index.d.ts +0 -97
- package/lib/worker/buffers.d.ts +0 -42
- package/lib/worker/buffers.js +0 -79
- package/lib/worker/common.d.ts +0 -45
- package/lib/worker/common.js +0 -7
- package/lib/worker/config.d.ts +0 -8
- package/lib/worker/config.js +0 -11
- package/lib/worker/index.d.ts +0 -3
- package/lib/worker/index.js +0 -1
- package/lib/worker/memory.d.ts +0 -9
- package/lib/worker/memory.js +0 -29
- package/lib/worker/messages.d.ts +0 -14
- package/lib/worker/messages.js +0 -15
- package/lib/worker/packet.d.ts +0 -11
- package/lib/worker/packet.js +0 -59
- package/lib/worker/shared/capture.d.ts +0 -28
- package/lib/worker/shared/capture.js +0 -63
- package/lib/worker/shared/index.d.ts +0 -3
- package/lib/worker/shared/index.js +0 -148
- package/lib/worker/shared/messages.d.ts +0 -67
- package/lib/worker/shared/messages.js +0 -64
- package/lib/worker/shared/playback.d.ts +0 -21
- package/lib/worker/shared/playback.js +0 -78
- package/lib/worker/unshared/capture.d.ts +0 -28
- package/lib/worker/unshared/capture.js +0 -66
- package/lib/worker/unshared/index.d.ts +0 -3
- package/lib/worker/unshared/index.js +0 -171
- package/lib/worker/unshared/messages.d.ts +0 -81
- package/lib/worker/unshared/messages.js +0 -80
- package/lib/worker/unshared/playback.d.ts +0 -23
- package/lib/worker/unshared/playback.js +0 -83
|
@@ -1,303 +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 { RpcConnection } from './rpcConnection';
|
|
11
|
-
import { Token } from './token';
|
|
12
|
-
import { Selector, failure, sleep, success, RoomCommandsRpc, } from '@4players/odin-common';
|
|
13
|
-
import * as z from 'zod';
|
|
14
|
-
import { ConnectTimeout, MinTokenLifetime, MinTokenRefreshInterval, } from './config';
|
|
15
|
-
export class RoomConnection {
|
|
16
|
-
get token() {
|
|
17
|
-
return this._Token;
|
|
18
|
-
}
|
|
19
|
-
get userData() {
|
|
20
|
-
return this._UserData;
|
|
21
|
-
}
|
|
22
|
-
get position() {
|
|
23
|
-
return this._Position;
|
|
24
|
-
}
|
|
25
|
-
get closed() {
|
|
26
|
-
return this._Close.signal.aborted;
|
|
27
|
-
}
|
|
28
|
-
get mainConnection() {
|
|
29
|
-
return this._MainConnection;
|
|
30
|
-
}
|
|
31
|
-
constructor(connectionPool, onEvent, gateway, token, roomId, userData, position) {
|
|
32
|
-
this.connectionPool = connectionPool;
|
|
33
|
-
this.onEvent = onEvent;
|
|
34
|
-
this.gateway = gateway;
|
|
35
|
-
this.roomId = roomId;
|
|
36
|
-
this._Close = new AbortController();
|
|
37
|
-
this._LastTokenRenew = performance.now();
|
|
38
|
-
this._Closed = new Promise((accept) => this._Close.signal.addEventListener('abort', () => accept('closed'), {
|
|
39
|
-
once: true,
|
|
40
|
-
}));
|
|
41
|
-
this._Token = token;
|
|
42
|
-
this._UserData = userData;
|
|
43
|
-
this._Position = position;
|
|
44
|
-
this._Main().then(({ reason }) => console.debug(`room closed; ${reason}`));
|
|
45
|
-
}
|
|
46
|
-
request(name, properties) {
|
|
47
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
48
|
-
if (this._RoomConnection === undefined) {
|
|
49
|
-
return failure('currently not connected');
|
|
50
|
-
}
|
|
51
|
-
const response = yield this._RoomConnection.request(name, properties);
|
|
52
|
-
if (response !== 'closed' && response.type === 'Success') {
|
|
53
|
-
this._HandleRequests(name, properties, response.value);
|
|
54
|
-
}
|
|
55
|
-
return response;
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
typedRequest(name, properties) {
|
|
59
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
60
|
-
const schema = RoomCommandsRpc[name];
|
|
61
|
-
const response = yield this.request(name, properties);
|
|
62
|
-
if (response === 'closed' || response.type === 'Failure')
|
|
63
|
-
return response;
|
|
64
|
-
const parsed = schema.response.safeParse(response.value);
|
|
65
|
-
if (parsed.success)
|
|
66
|
-
return success(parsed.data);
|
|
67
|
-
else {
|
|
68
|
-
return failure(`received invalid response from ${name}; ${parsed.error}`);
|
|
69
|
-
}
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
close() {
|
|
73
|
-
this._Close.abort();
|
|
74
|
-
}
|
|
75
|
-
get renewTokenIn() {
|
|
76
|
-
const earliest = this._LastTokenRenew - performance.now() + MinTokenRefreshInterval;
|
|
77
|
-
const latest = this._Token.timeLeft - MinTokenLifetime;
|
|
78
|
-
return Math.max(earliest, latest);
|
|
79
|
-
}
|
|
80
|
-
_Main() {
|
|
81
|
-
var _a;
|
|
82
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
83
|
-
let closeReason = 'unable to establish connection';
|
|
84
|
-
/// as long as we are asked to continue
|
|
85
|
-
while (this._Close.signal.aborted === false) {
|
|
86
|
-
const result = yield this._ConnectToServer();
|
|
87
|
-
if (result.type === 'Failure') {
|
|
88
|
-
closeReason = result.reason;
|
|
89
|
-
break;
|
|
90
|
-
}
|
|
91
|
-
(_a = this._RoomConnection) === null || _a === void 0 ? void 0 : _a.close();
|
|
92
|
-
this._RoomConnection = undefined;
|
|
93
|
-
this._MainConnection = undefined;
|
|
94
|
-
// can the token be used to request a better token from a gateway?
|
|
95
|
-
if (this.gateway !== undefined && this._Token.hasAudience('gateway')) {
|
|
96
|
-
this._ChangeStatus('Joining', `requesting new token from ${this.gateway}`);
|
|
97
|
-
const newToken = yield contactGateway(this.gateway, this._Token, this._Close.signal);
|
|
98
|
-
if (newToken.type === 'Success') {
|
|
99
|
-
if (newToken.value !== 'rejected') {
|
|
100
|
-
// we got a better token, lets retry.
|
|
101
|
-
this._Token = newToken.value;
|
|
102
|
-
continue;
|
|
103
|
-
}
|
|
104
|
-
else {
|
|
105
|
-
closeReason = 'invalid or expired token';
|
|
106
|
-
break;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
// we failed to get a connection, should we try again?
|
|
111
|
-
if (this._Token.timeLeft <= 0) {
|
|
112
|
-
closeReason = 'unable to use expired token';
|
|
113
|
-
break;
|
|
114
|
-
}
|
|
115
|
-
// slow retries down
|
|
116
|
-
yield sleep(1000);
|
|
117
|
-
}
|
|
118
|
-
this._ChangeStatus('Closed', this._Close.signal.aborted ? 'user requested' : closeReason);
|
|
119
|
-
this.close();
|
|
120
|
-
return failure(closeReason);
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
_ConnectToServer() {
|
|
124
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
125
|
-
// for now strip upstream wrappers in the token, because it not supported in web
|
|
126
|
-
let token = this._Token;
|
|
127
|
-
while (token.upstream !== undefined) {
|
|
128
|
-
token = token.upstream;
|
|
129
|
-
}
|
|
130
|
-
if (!token.hasAudience('sfu') || token.address === undefined) {
|
|
131
|
-
return success('continue');
|
|
132
|
-
}
|
|
133
|
-
this._ChangeStatus('Joining', `connecting to ${token.address}`);
|
|
134
|
-
// are we already connected to the server?
|
|
135
|
-
if (this._MainConnection === undefined ||
|
|
136
|
-
this._MainConnection.closed ||
|
|
137
|
-
this._MainConnection.url !== token.address) {
|
|
138
|
-
// no? release the not fitting connection
|
|
139
|
-
if (this._MainConnection) {
|
|
140
|
-
this.connectionPool.releaseConnection(this._MainConnection);
|
|
141
|
-
}
|
|
142
|
-
// connect to the right server
|
|
143
|
-
const mainConnection = yield this.connectionPool.acquireConnection(token.address, this._Close.signal);
|
|
144
|
-
if (mainConnection === 'closed')
|
|
145
|
-
return failure('connection pool closed'); // connection pool is closed
|
|
146
|
-
if (mainConnection.type === 'Failure') {
|
|
147
|
-
this._ChangeStatus('Joining', `connection failed; ${mainConnection.reason}`);
|
|
148
|
-
return success('continue');
|
|
149
|
-
}
|
|
150
|
-
this._MainConnection = mainConnection.value;
|
|
151
|
-
}
|
|
152
|
-
// join room
|
|
153
|
-
const result = yield Promise.race([
|
|
154
|
-
Promise.all([
|
|
155
|
-
this._MainConnection.joinRoom(token, this.roomId, this.userData, this.position),
|
|
156
|
-
]),
|
|
157
|
-
sleep(ConnectTimeout, 'timeout'),
|
|
158
|
-
this._Closed,
|
|
159
|
-
]);
|
|
160
|
-
if (result === 'closed')
|
|
161
|
-
return failure('room connection closed'); // room connection was closed
|
|
162
|
-
if (result !== 'timeout') {
|
|
163
|
-
const [websocket] = result;
|
|
164
|
-
if (websocket === 'closed')
|
|
165
|
-
return success('continue'); // connection was lost, before joinRoom finished
|
|
166
|
-
if (websocket.type === 'Failure') {
|
|
167
|
-
// join was rejected by the server
|
|
168
|
-
return failure(`unable to join room; ${websocket.reason}`);
|
|
169
|
-
}
|
|
170
|
-
// room connection is open, make it available
|
|
171
|
-
this._RoomConnection = new RpcConnection(websocket.value);
|
|
172
|
-
this._ChangeStatus('Joined', `joined room ${this.roomId} on ${websocket.value.url}`);
|
|
173
|
-
// TODO(sven): handle this
|
|
174
|
-
// await this._MainConnection.webRtcReady()
|
|
175
|
-
// process room events
|
|
176
|
-
yield this._HandleConnection(this._MainConnection, websocket.value);
|
|
177
|
-
}
|
|
178
|
-
// try to reconnect
|
|
179
|
-
return success('continue');
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
_ChangeStatus(status, message) {
|
|
183
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
184
|
-
const properties = {
|
|
185
|
-
status,
|
|
186
|
-
message,
|
|
187
|
-
};
|
|
188
|
-
this.onEvent('RoomStatusChanged', properties);
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
_HandleConnection(main, webSocket) {
|
|
192
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
193
|
-
if (this._RoomConnection === undefined)
|
|
194
|
-
return;
|
|
195
|
-
const selector = new Selector([
|
|
196
|
-
() => sleep(this.renewTokenIn, 'renewToken'),
|
|
197
|
-
() => sleep(500, 'tick'),
|
|
198
|
-
() => __awaiter(this, void 0, void 0, function* () { var _a; return yield ((_a = this._RoomConnection) === null || _a === void 0 ? void 0 : _a.recv()); }),
|
|
199
|
-
() => this._Closed,
|
|
200
|
-
() => main.closePromise,
|
|
201
|
-
]);
|
|
202
|
-
while (true) {
|
|
203
|
-
const value = yield selector.next();
|
|
204
|
-
switch (value) {
|
|
205
|
-
case undefined:
|
|
206
|
-
case 'closed':
|
|
207
|
-
webSocket.close();
|
|
208
|
-
return;
|
|
209
|
-
case 'renewToken':
|
|
210
|
-
this._RenewToken(main, webSocket);
|
|
211
|
-
break;
|
|
212
|
-
case 'tick':
|
|
213
|
-
break;
|
|
214
|
-
default:
|
|
215
|
-
this._HandleEvent(value);
|
|
216
|
-
break;
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
_HandleEvent(event) {
|
|
222
|
-
this.onEvent(event.name, event.properties);
|
|
223
|
-
}
|
|
224
|
-
_HandleRequests(name, properties, response) {
|
|
225
|
-
switch (name) {
|
|
226
|
-
case 'SetPeerPosition':
|
|
227
|
-
// eslint-disable-next-line no-case-declarations
|
|
228
|
-
const setPeerPosition = RoomCommandsRpc.SetPeerPosition.request.safeParse(properties);
|
|
229
|
-
if (setPeerPosition.success) {
|
|
230
|
-
const value = setPeerPosition.data.position;
|
|
231
|
-
this._Position = value.length === 2 ? [value[0], value[1], 0] : value;
|
|
232
|
-
}
|
|
233
|
-
else {
|
|
234
|
-
console.warn('unable to process SetPeerPosition command');
|
|
235
|
-
}
|
|
236
|
-
break;
|
|
237
|
-
case 'UpdatePeer':
|
|
238
|
-
// eslint-disable-next-line no-case-declarations
|
|
239
|
-
const updatePeer = RoomCommandsRpc.UpdatePeer.request.safeParse(properties);
|
|
240
|
-
if (updatePeer.success) {
|
|
241
|
-
this._UserData = updatePeer.data.user_data;
|
|
242
|
-
}
|
|
243
|
-
else {
|
|
244
|
-
console.warn('unable to process UpdatePeer command');
|
|
245
|
-
}
|
|
246
|
-
break;
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
_RenewToken(main, webSocket) {
|
|
250
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
251
|
-
this._LastTokenRenew = performance.now();
|
|
252
|
-
if (this.token.timeLeft > MinTokenLifetime)
|
|
253
|
-
return;
|
|
254
|
-
const response = yield main.getReconnectToken(webSocket);
|
|
255
|
-
if (response.type === 'Success') {
|
|
256
|
-
if (response.value.timeLeft > this._Token.timeLeft) {
|
|
257
|
-
this._Token = response.value;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
else {
|
|
261
|
-
console.warn(`${main.url} rejected reconnect token request; ${response.reason}`);
|
|
262
|
-
}
|
|
263
|
-
});
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
function contactGateway(gateway, token, signal) {
|
|
267
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
268
|
-
try {
|
|
269
|
-
const response = yield fetch(gateway, {
|
|
270
|
-
body: '{"jsonrpc":"2.0","id":1,"method":"Connect","params":{}}',
|
|
271
|
-
method: 'POST',
|
|
272
|
-
headers: {
|
|
273
|
-
'content-type': 'application/json',
|
|
274
|
-
authorization: `Bearer ${token}`,
|
|
275
|
-
},
|
|
276
|
-
signal,
|
|
277
|
-
});
|
|
278
|
-
const schema = z.union([
|
|
279
|
-
z.object({
|
|
280
|
-
result: z.object({
|
|
281
|
-
token: z.string(),
|
|
282
|
-
}),
|
|
283
|
-
}),
|
|
284
|
-
z.object({
|
|
285
|
-
error: z.object({
|
|
286
|
-
message: z.string(),
|
|
287
|
-
}),
|
|
288
|
-
}),
|
|
289
|
-
]);
|
|
290
|
-
const body = schema.parse(yield response.json());
|
|
291
|
-
if ('result' in body) {
|
|
292
|
-
return Token.parse(body.result.token);
|
|
293
|
-
}
|
|
294
|
-
else {
|
|
295
|
-
console.error(`gateway rejected token; ${body.error.message}`);
|
|
296
|
-
return success('rejected');
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
catch (error) {
|
|
300
|
-
return failure(String(error));
|
|
301
|
-
}
|
|
302
|
-
});
|
|
303
|
-
}
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import * as z from 'zod';
|
|
2
|
-
import { Result, RpcMessage } from '@4players/odin-common';
|
|
3
|
-
export declare function connect(url: URL, signal: AbortSignal): Promise<Result<WebSocket>>;
|
|
4
|
-
export declare function connectOnce(url: URL, signal: AbortSignal): Promise<Result<WebSocket>>;
|
|
5
|
-
export declare class RpcConnection {
|
|
6
|
-
readonly webSocket: WebSocket;
|
|
7
|
-
private readonly _SendQueue;
|
|
8
|
-
private readonly _RecvQueue;
|
|
9
|
-
private readonly _Requests;
|
|
10
|
-
private readonly _Close;
|
|
11
|
-
private _NextId;
|
|
12
|
-
get closed(): boolean;
|
|
13
|
-
get closeSignal(): AbortSignal;
|
|
14
|
-
constructor(webSocket: WebSocket);
|
|
15
|
-
request(name: string, properties: unknown): Promise<Result<unknown> | 'closed'>;
|
|
16
|
-
request(name: string, properties: unknown, timeout: number): Promise<Result<unknown> | 'closed' | 'timeout'>;
|
|
17
|
-
notify(name: string, properties: unknown): void;
|
|
18
|
-
recv(): Promise<RpcMessage | 'closed'>;
|
|
19
|
-
close(): void;
|
|
20
|
-
private _TimeoutRequest;
|
|
21
|
-
private _Send;
|
|
22
|
-
private _OnWebSocketOpen;
|
|
23
|
-
private _OnWebSocketMessage;
|
|
24
|
-
private _OnResponse;
|
|
25
|
-
private _OnEvent;
|
|
26
|
-
private _Cleanup;
|
|
27
|
-
}
|
|
28
|
-
export type Commands = Record<string, {
|
|
29
|
-
request: z.ZodTypeAny;
|
|
30
|
-
response: z.ZodTypeAny;
|
|
31
|
-
}>;
|
|
32
|
-
export type Events = Record<string, z.ZodTypeAny>;
|
|
33
|
-
export interface TypedRpcMessage<Name extends string, Properties> extends RpcMessage {
|
|
34
|
-
name: Name;
|
|
35
|
-
properties: Properties;
|
|
36
|
-
}
|
|
37
|
-
export type RealizedEvents<E extends Events> = {
|
|
38
|
-
[Name in Extract<keyof E, string>]: TypedRpcMessage<Name, z.infer<E[Name]>>;
|
|
39
|
-
}[Extract<keyof E, string>];
|
|
40
|
-
export declare class TypedRpcConnection<C extends Commands, E extends Events> extends RpcConnection {
|
|
41
|
-
readonly _Commands: C;
|
|
42
|
-
readonly _Events: E;
|
|
43
|
-
constructor(webSocket: WebSocket, _Commands: C, _Events: E);
|
|
44
|
-
request<Name extends Extract<keyof C, string>>(name: Name, properties: z.infer<C[Name]['request']>): Promise<Result<z.infer<C[Name]['response']>> | 'closed'>;
|
|
45
|
-
request<Name extends Extract<keyof C, string>>(name: Name, properties: z.infer<C[Name]['request']>, timeout: number): Promise<Result<z.infer<C[Name]['response']>> | 'closed' | 'timeout'>;
|
|
46
|
-
notify<Name extends Extract<keyof C, string>>(name: Name, properties: z.infer<C[Name]['request']>): void;
|
|
47
|
-
recv(): Promise<RealizedEvents<E> | 'closed'>;
|
|
48
|
-
}
|
|
@@ -1,234 +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 * as msgpack from '@msgpack/msgpack';
|
|
12
|
-
import { failure, success, isKnownRpcMessage, parseRpc, parseRpcMessage, abortableSleep, } from '@4players/odin-common';
|
|
13
|
-
import { ConnectInterval } from './config';
|
|
14
|
-
export function connect(url, signal) {
|
|
15
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
16
|
-
console.debug(`connecting to ${url}`);
|
|
17
|
-
const start = performance.now();
|
|
18
|
-
let connection = failure('no connection was attempted');
|
|
19
|
-
for (let failures = 0; failures < ConnectInterval.length && signal.aborted === false; failures++) {
|
|
20
|
-
const nextReconnectAt = start + whenToConnect(failures);
|
|
21
|
-
if ((yield abortableSleep(nextReconnectAt - performance.now(), signal)) ===
|
|
22
|
-
'aborted') {
|
|
23
|
-
return failure('aborted');
|
|
24
|
-
}
|
|
25
|
-
connection = yield connectOnce(url, signal);
|
|
26
|
-
if (connection.type === 'Success') {
|
|
27
|
-
return connection;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
return connection;
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
export function connectOnce(url, signal) {
|
|
34
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
35
|
-
let webSocket;
|
|
36
|
-
try {
|
|
37
|
-
webSocket = new WebSocket(url);
|
|
38
|
-
}
|
|
39
|
-
catch (reason) {
|
|
40
|
-
return failure(`unable to create websocket; ${reason}`);
|
|
41
|
-
}
|
|
42
|
-
const abort = new AbortController();
|
|
43
|
-
const onOpen = new Promise((accept) => webSocket.addEventListener('open', () => accept(success(webSocket)), {
|
|
44
|
-
signal: abort.signal,
|
|
45
|
-
}));
|
|
46
|
-
const onClose = new Promise((accept) => webSocket.addEventListener('close', (event) => accept(failure(event.reason)), { signal: abort.signal }));
|
|
47
|
-
const onAbort = new Promise((accept) => signal.addEventListener('abort', () => accept(failure('aborted')), {
|
|
48
|
-
signal: abort.signal,
|
|
49
|
-
}));
|
|
50
|
-
const result = yield Promise.race([onOpen, onClose, onAbort]);
|
|
51
|
-
abort.abort();
|
|
52
|
-
return result;
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
function whenToConnect(failures) {
|
|
56
|
-
const past_connects = ConnectInterval.slice(0, failures).reduce((a, b) => a + b, 0);
|
|
57
|
-
const interval = ConnectInterval[failures] * Math.random();
|
|
58
|
-
return past_connects + interval;
|
|
59
|
-
}
|
|
60
|
-
export class RpcConnection {
|
|
61
|
-
get closed() {
|
|
62
|
-
return this._Close.signal.aborted;
|
|
63
|
-
}
|
|
64
|
-
get closeSignal() {
|
|
65
|
-
return this._Close.signal;
|
|
66
|
-
}
|
|
67
|
-
constructor(webSocket) {
|
|
68
|
-
this.webSocket = webSocket;
|
|
69
|
-
this._SendQueue = [];
|
|
70
|
-
this._RecvQueue = [];
|
|
71
|
-
this._Requests = new Map();
|
|
72
|
-
this._Close = new AbortController();
|
|
73
|
-
this._NextId = 1;
|
|
74
|
-
this.webSocket.addEventListener('open', this._OnWebSocketOpen.bind(this));
|
|
75
|
-
this.webSocket.addEventListener('close', this.close.bind(this));
|
|
76
|
-
this.webSocket.addEventListener('message', this._OnWebSocketMessage.bind(this));
|
|
77
|
-
this.webSocket.binaryType = 'arraybuffer';
|
|
78
|
-
this._Close.signal.addEventListener('abort', this._Cleanup.bind(this), {
|
|
79
|
-
once: true,
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
request(name, properties, timeout) {
|
|
83
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
84
|
-
if (this.closed) {
|
|
85
|
-
return Promise.resolve('closed');
|
|
86
|
-
}
|
|
87
|
-
const id = this._NextId;
|
|
88
|
-
const timeoutId = timeout === undefined
|
|
89
|
-
? undefined
|
|
90
|
-
: setTimeout(() => this._TimeoutRequest(id), timeout);
|
|
91
|
-
this._Send([0, id, name, properties]);
|
|
92
|
-
return new Promise((accept) => {
|
|
93
|
-
this._Requests.set(id, { accept, timeoutId });
|
|
94
|
-
this._NextId += 1;
|
|
95
|
-
});
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
notify(name, properties) {
|
|
99
|
-
if (this.closed === false) {
|
|
100
|
-
this._Send([2, name, properties]);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
recv() {
|
|
104
|
-
if (this.closed === false) {
|
|
105
|
-
return new Promise((accept) => this._RecvQueue.push(accept));
|
|
106
|
-
}
|
|
107
|
-
else {
|
|
108
|
-
return Promise.resolve('closed');
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
close() {
|
|
112
|
-
this._Close.abort();
|
|
113
|
-
}
|
|
114
|
-
_TimeoutRequest(requestId) {
|
|
115
|
-
const request = this._Requests.get(requestId);
|
|
116
|
-
if (request === undefined)
|
|
117
|
-
return;
|
|
118
|
-
this._Requests.delete(requestId);
|
|
119
|
-
clearTimeout(request.timeoutId);
|
|
120
|
-
request.accept('timeout');
|
|
121
|
-
}
|
|
122
|
-
_Send(message) {
|
|
123
|
-
this.webSocket.send(msgpack.encode(message));
|
|
124
|
-
}
|
|
125
|
-
_OnWebSocketOpen(ev) {
|
|
126
|
-
for (const data in this._SendQueue) {
|
|
127
|
-
this.webSocket.send(data);
|
|
128
|
-
}
|
|
129
|
-
this._SendQueue.length = 0;
|
|
130
|
-
}
|
|
131
|
-
_OnWebSocketMessage(ev) {
|
|
132
|
-
const rpc = parseRpc(ev.data);
|
|
133
|
-
if (rpc.type !== 'Success') {
|
|
134
|
-
this.webSocket.close();
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
switch (rpc.value[0]) {
|
|
138
|
-
case 0:
|
|
139
|
-
this._Send([1, rpc.value[1], 'not supported', undefined]);
|
|
140
|
-
break;
|
|
141
|
-
case 1:
|
|
142
|
-
this._OnResponse(rpc.value[1], rpc.value[2], rpc.value[3]);
|
|
143
|
-
break;
|
|
144
|
-
case 2:
|
|
145
|
-
this._OnEvent(rpc.value[1], rpc.value[2]);
|
|
146
|
-
break;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
_OnResponse(id, error, result) {
|
|
150
|
-
const request = this._Requests.get(id);
|
|
151
|
-
if (request === undefined)
|
|
152
|
-
return;
|
|
153
|
-
clearTimeout(request.timeoutId);
|
|
154
|
-
request.accept(error === null ? success(result) : failure(error));
|
|
155
|
-
}
|
|
156
|
-
_OnEvent(name, properties) {
|
|
157
|
-
console.log('event', name, properties);
|
|
158
|
-
const receiver = this._RecvQueue.shift();
|
|
159
|
-
if (receiver !== undefined) {
|
|
160
|
-
receiver({ name, properties });
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
_Cleanup() {
|
|
164
|
-
console.log(`cleanup ${this.webSocket.url}`);
|
|
165
|
-
for (const request of this._Requests.values()) {
|
|
166
|
-
clearTimeout(request.timeoutId);
|
|
167
|
-
request.accept('closed');
|
|
168
|
-
}
|
|
169
|
-
this._Requests.clear();
|
|
170
|
-
for (const accept of this._RecvQueue) {
|
|
171
|
-
accept('closed');
|
|
172
|
-
}
|
|
173
|
-
this._RecvQueue.length = 0;
|
|
174
|
-
this._SendQueue.length = 0;
|
|
175
|
-
this.webSocket.close();
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
export class TypedRpcConnection extends RpcConnection {
|
|
179
|
-
constructor(webSocket, _Commands, _Events) {
|
|
180
|
-
super(webSocket);
|
|
181
|
-
this._Commands = _Commands;
|
|
182
|
-
this._Events = _Events;
|
|
183
|
-
}
|
|
184
|
-
request(name, properties, timeout) {
|
|
185
|
-
const _super = Object.create(null, {
|
|
186
|
-
request: { get: () => super.request }
|
|
187
|
-
});
|
|
188
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
189
|
-
properties = this._Commands[name].request.parse(properties);
|
|
190
|
-
const response = yield (timeout !== undefined
|
|
191
|
-
? _super.request.call(this, name, properties, timeout)
|
|
192
|
-
: _super.request.call(this, name, properties));
|
|
193
|
-
if (response === 'closed' ||
|
|
194
|
-
response === 'timeout' ||
|
|
195
|
-
response.type === 'Failure') {
|
|
196
|
-
return response;
|
|
197
|
-
}
|
|
198
|
-
const parsed = this._Commands[name].response.safeParse(response.value);
|
|
199
|
-
if (parsed.success) {
|
|
200
|
-
response.value = parsed.data;
|
|
201
|
-
return response;
|
|
202
|
-
}
|
|
203
|
-
else {
|
|
204
|
-
return failure(`response schema failed validation: ${parsed.error}`);
|
|
205
|
-
}
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
notify(name, properties) {
|
|
209
|
-
properties = this._Commands[name].request.parse(properties);
|
|
210
|
-
super.notify(name, properties);
|
|
211
|
-
}
|
|
212
|
-
recv() {
|
|
213
|
-
const _super = Object.create(null, {
|
|
214
|
-
recv: { get: () => super.recv }
|
|
215
|
-
});
|
|
216
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
217
|
-
const event = yield _super.recv.call(this);
|
|
218
|
-
if (event === 'closed')
|
|
219
|
-
return event;
|
|
220
|
-
const parsed = parseRpcMessage(this._Events, event);
|
|
221
|
-
if (parsed !== undefined) {
|
|
222
|
-
return parsed;
|
|
223
|
-
}
|
|
224
|
-
if (!isKnownRpcMessage(this._Events, event.name)) {
|
|
225
|
-
console.warn(`dropping unknown event ${event.name}`);
|
|
226
|
-
}
|
|
227
|
-
else {
|
|
228
|
-
console.error(`schema validation for ${event.name} failed; closing connection`);
|
|
229
|
-
this.close();
|
|
230
|
-
}
|
|
231
|
-
return 'closed';
|
|
232
|
-
});
|
|
233
|
-
}
|
|
234
|
-
}
|
package/lib/plugin/token.d.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import * as z from 'zod';
|
|
2
|
-
import { TokenAudienceSchema, TokenClaimsSchema, Result } from '@4players/odin-common';
|
|
3
|
-
export type TokenClaims = z.infer<typeof TokenClaimsSchema>;
|
|
4
|
-
export declare class Token {
|
|
5
|
-
readonly text: string;
|
|
6
|
-
readonly claims: TokenClaims;
|
|
7
|
-
static parse(token: string): Result<Token>;
|
|
8
|
-
private readonly _Address;
|
|
9
|
-
private readonly _Upstream;
|
|
10
|
-
protected constructor(text: string, claims: TokenClaims);
|
|
11
|
-
get timeLeft(): number;
|
|
12
|
-
get address(): URL | undefined;
|
|
13
|
-
get upstream(): Token | undefined;
|
|
14
|
-
get defaultRoomId(): string | undefined;
|
|
15
|
-
hasAudience(audience: z.infer<typeof TokenAudienceSchema>): boolean;
|
|
16
|
-
toString(): string;
|
|
17
|
-
}
|
package/lib/plugin/token.js
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import { TokenClaimsSchema, failure, fromBase64Url, success, } from '@4players/odin-common';
|
|
2
|
-
export class Token {
|
|
3
|
-
static parse(token) {
|
|
4
|
-
const segments = token.split('.');
|
|
5
|
-
if (segments.length !== 3)
|
|
6
|
-
return failure('expected three segments');
|
|
7
|
-
const body = fromBase64Url(segments[1]);
|
|
8
|
-
if (body.type === 'Failure') {
|
|
9
|
-
return failure(`invalid token; ${body.reason}`);
|
|
10
|
-
}
|
|
11
|
-
let json;
|
|
12
|
-
try {
|
|
13
|
-
json = JSON.parse(body.value);
|
|
14
|
-
}
|
|
15
|
-
catch (reason) {
|
|
16
|
-
return failure(`invalid token; ${reason}`);
|
|
17
|
-
}
|
|
18
|
-
const parsed = TokenClaimsSchema.safeParse(json);
|
|
19
|
-
if (parsed.success === false) {
|
|
20
|
-
return failure(`invalid token; ${parsed.error}`);
|
|
21
|
-
}
|
|
22
|
-
return success(new Token(token, parsed.data));
|
|
23
|
-
}
|
|
24
|
-
constructor(text, claims) {
|
|
25
|
-
this.text = text;
|
|
26
|
-
this.claims = claims;
|
|
27
|
-
const address = claims.adr;
|
|
28
|
-
if (address !== undefined) {
|
|
29
|
-
try {
|
|
30
|
-
this._Address = new URL(`wss://${address}`);
|
|
31
|
-
}
|
|
32
|
-
catch (error) {
|
|
33
|
-
console.error(`address in token(${address}) can not be parsed; ${error}`);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
if (claims.ups !== undefined) {
|
|
37
|
-
const upstream = Token.parse(claims.ups);
|
|
38
|
-
if (upstream.type === 'Success') {
|
|
39
|
-
this._Upstream = upstream.value;
|
|
40
|
-
}
|
|
41
|
-
else {
|
|
42
|
-
console.error(`upstream in token can not be parsed; ${upstream.reason}`);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
get timeLeft() {
|
|
47
|
-
if (this.claims.exp === undefined)
|
|
48
|
-
return Infinity;
|
|
49
|
-
return this.claims.exp * 1000 - Date.now();
|
|
50
|
-
}
|
|
51
|
-
get address() {
|
|
52
|
-
return this._Address;
|
|
53
|
-
}
|
|
54
|
-
get upstream() {
|
|
55
|
-
return this._Upstream;
|
|
56
|
-
}
|
|
57
|
-
get defaultRoomId() {
|
|
58
|
-
if (Array.isArray(this.claims.rid)) {
|
|
59
|
-
return this.claims.rid.length === 1 ? this.claims.rid[0] : undefined;
|
|
60
|
-
}
|
|
61
|
-
else {
|
|
62
|
-
return this.claims.rid;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
hasAudience(audience) {
|
|
66
|
-
if (this.claims.aud === undefined)
|
|
67
|
-
return true;
|
|
68
|
-
if (Array.isArray(this.claims.aud)) {
|
|
69
|
-
return this.claims.aud.includes(audience);
|
|
70
|
-
}
|
|
71
|
-
else {
|
|
72
|
-
return this.claims.aud === audience;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
toString() {
|
|
76
|
-
return this.text;
|
|
77
|
-
}
|
|
78
|
-
}
|