@4players/odin-plugin-web 1.0.0-alpha.11 → 1.0.0-alpha.13

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.
@@ -1,9 +1,7 @@
1
1
  import { Backend, Strand } from '@4players/odin-common';
2
- import { DeviceParameters } from '.';
2
+ import { DeviceParameters } from './index';
3
3
  import { RoomConnection } from './roomConnection';
4
4
  import { CaptureSink, PlaybackId, PlaybackWorker } from '../worker/common';
5
- export type Volume = Backend.Volume;
6
- export type Activity = Backend.Activity;
7
5
  export type CreateAudioPlaybackParameters = Backend.CreateAudioPlaybackParameters;
8
6
  export type CreateAudioCaptureParameters = Backend.CreateAudioCaptureParameters & {
9
7
  readonly device?: Device;
@@ -38,7 +36,7 @@ export declare class AudioController {
38
36
  globalVolume: number;
39
37
  playbacks: Map<string, {
40
38
  id: GlobalMediaId | undefined;
41
- volume: Volume;
39
+ volume: Backend.Volume;
42
40
  playback: AudioPlayback;
43
41
  }>;
44
42
  setPlaybackStrand: Strand;
@@ -58,9 +56,9 @@ export declare class AudioController {
58
56
  playbackSetGlobalVolume(value: Backend.Volume): void;
59
57
  playbackStart(uid: string, id: GlobalMediaId): void;
60
58
  playbackStop(uid: string, id: GlobalMediaId): void;
61
- playbackGetActivity(uid: string): Activity;
62
- playbackSetVolume(uid: string, volume: Volume): void;
63
- playbackGetVolume(uid: string): Volume;
59
+ playbackGetActivity(uid: string): Backend.Activity;
60
+ playbackSetVolume(uid: string, volume: Backend.Volume): void;
61
+ playbackGetVolume(uid: string): Backend.Volume;
64
62
  closePlayback(uid: string): void;
65
63
  close(): void;
66
64
  }
@@ -95,6 +93,7 @@ export declare class AudioCapture implements Backend.AudioCapture {
95
93
  get volume(): Backend.Volume;
96
94
  get activity(): import("../worker/common").Activity;
97
95
  get speechProbability(): number;
96
+ get deviceId(): string | undefined;
98
97
  constructor(controller: AudioController, parameters: CreateAudioCaptureParameters);
99
98
  start(room: RoomConnection, media: GlobalMediaId, uid: Backend.UID): void;
100
99
  stop(room: RoomConnection, media: GlobalMediaId): void;
@@ -8,8 +8,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  });
9
9
  };
10
10
  import { Strand, assert, failure, sleep, success, } from '@4players/odin-common';
11
- import { Silence, } from '../worker/common';
12
11
  import { createCaptureWorker, createPlaybackWorker } from '../worker';
12
+ import { Silence, } from '../worker/common';
13
13
  const defaultVad = {
14
14
  voiceActivity: {
15
15
  attackThreshold: 0.9,
@@ -337,6 +337,10 @@ export class AudioCapture {
337
337
  var _a, _b;
338
338
  return (_b = (_a = this._Capture) === null || _a === void 0 ? void 0 : _a.worker.speechProbability) !== null && _b !== void 0 ? _b : 0;
339
339
  }
340
+ get deviceId() {
341
+ var _a, _b;
342
+ return (_b = (_a = this._deviceParameters) === null || _a === void 0 ? void 0 : _a.device) === null || _b === void 0 ? void 0 : _b.device.deviceId;
343
+ }
340
344
  constructor(controller, parameters) {
341
345
  this.controller = controller;
342
346
  this._Medias = new Map();
@@ -401,13 +405,19 @@ export class AudioCapture {
401
405
  setDevice(parameters) {
402
406
  var _a;
403
407
  return __awaiter(this, void 0, void 0, function* () {
408
+ const rollbackParams = this._deviceParameters;
409
+ this._deviceParameters = parameters;
410
+ if (this._Volume === 'muted') {
411
+ return;
412
+ }
404
413
  const device = yield openCaptureDevice(this.controller, parameters, this._Apm);
405
414
  if (device.type !== 'Success') {
406
415
  console.error(`unable to open capture device; ${device.reason}`);
416
+ this._deviceParameters = rollbackParams;
407
417
  return;
408
418
  }
409
- this._deviceParameters = parameters;
410
419
  if (this._Capture !== undefined) {
420
+ stopMediaStream(this._Capture.source.mediaStream);
411
421
  this._Capture.source.disconnect(this._Capture.worker.worklet);
412
422
  }
413
423
  if (this._Capture === undefined ||
@@ -462,7 +472,7 @@ export class AudioCapture {
462
472
  }
463
473
  }
464
474
  function stopMediaStream(ms) {
465
- ms.getTracks().forEach(track => {
475
+ ms.getTracks().forEach((track) => {
466
476
  track.stop();
467
477
  ms.removeTrack(track);
468
478
  });
@@ -1,5 +1,5 @@
1
- import { DatagramHandler, MainConnection } from './mainConnection';
2
1
  import { Result } from '@4players/odin-common';
2
+ import { DatagramHandler, MainConnection } from './mainConnection';
3
3
  export declare class ConnectionPool {
4
4
  readonly onDatagram: DatagramHandler;
5
5
  private _LastConnectionId;
@@ -7,9 +7,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
+ import { failure, nextTick, success } from '@4players/odin-common';
10
11
  import { ClockJumpCheckInterval, ClockJumpThreshold } from './config';
11
12
  import { MainConnection } from './mainConnection';
12
- import { failure, nextTick, success } from '@4players/odin-common';
13
13
  export class ConnectionPool {
14
14
  get closed() {
15
15
  return this._Close.signal.aborted;
@@ -1,9 +1,7 @@
1
- import { ConnectionPool } from './connectionPool';
2
1
  import { Backend } from '@4players/odin-common';
3
- import { AudioCapture, AudioContextFactory, AudioPlayback, CreateAudioCaptureParameters, CreateAudioPlaybackParameters, Device, Volume } from './audioController';
2
+ import { AudioCapture, AudioContextFactory, AudioPlayback, CreateAudioCaptureParameters, CreateAudioPlaybackParameters, Device } from './audioController';
3
+ import { ConnectionPool } from './connectionPool';
4
4
  import { MainConnection } from './mainConnection';
5
- export type Media = Backend.Media;
6
- export type JoinRoomParameters = Backend.JoinRoomParameters;
7
5
  export interface DeviceParameters {
8
6
  readonly device?: Device;
9
7
  onStatusChanged?: Backend.OnStatusChanged;
@@ -17,11 +15,11 @@ export declare class WebPlugin implements Backend.Plugin {
17
15
  get playbackVolume(): Backend.Volume;
18
16
  static create(audioContextFactory: AudioContextFactory): Promise<WebPlugin>;
19
17
  private constructor();
20
- joinRoom(parameters: JoinRoomParameters): Room;
18
+ joinRoom(parameters: Backend.JoinRoomParameters): Room;
21
19
  createAudioPlayback(parameters: CreateAudioPlaybackParameters): Promise<AudioPlayback>;
22
20
  createAudioCapture(parameters: CreateAudioCaptureParameters): Promise<AudioCapture>;
23
21
  enumerateDevices(): Promise<Device[]>;
24
- setOutputVolume(volume: Volume): void;
22
+ setOutputVolume(volume: Backend.Volume): void;
25
23
  setOutputDevice(device: DeviceParameters): Promise<void>;
26
24
  close(): void;
27
25
  }
@@ -34,10 +32,10 @@ export declare class Room implements Backend.Room {
34
32
  private readonly _Peers;
35
33
  private readonly _AvailableMediaIds;
36
34
  get mainConnection(): MainConnection | undefined;
37
- constructor(id: number, connectionPool: ConnectionPool, parameters: JoinRoomParameters);
35
+ constructor(id: number, connectionPool: ConnectionPool, parameters: Backend.JoinRoomParameters);
38
36
  request(method: string, properties: unknown): Promise<unknown>;
39
- link(media: Media): void;
40
- unlink(media: Media): void;
37
+ link(media: Backend.Media): void;
38
+ unlink(media: Backend.Media): void;
41
39
  close(): void;
42
40
  private _HandleEvent;
43
41
  private _HandleKnownEvent;
@@ -7,12 +7,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
+ import { assert, unwrap, RoomNotificationsRpc, generateUUID, } from '@4players/odin-common';
11
+ import { AudioCapture, AudioContextCollection, AudioController, AudioPlayback, } from './audioController';
10
12
  import { ConnectionPool } from './connectionPool';
11
13
  import { RoomConnection } from './roomConnection';
12
- import { assert, unwrap, parseRpcMessage, RoomNotificationsRpc, } from '@4players/odin-common';
14
+ import { parseRpcMessage } from './rpcConnection';
13
15
  import { Token } from './token';
14
- import { AudioCapture, AudioContextCollection, AudioController, AudioPlayback, } from './audioController';
15
- import * as uuid from 'uuid';
16
16
  export class WebPlugin {
17
17
  get playbackVolume() {
18
18
  return this._PlaybackVolume;
@@ -115,7 +115,7 @@ export class Room {
115
115
  mediaId,
116
116
  }
117
117
  : undefined,
118
- uid: uuid.v4(),
118
+ uid: generateUUID(),
119
119
  };
120
120
  this._Captures.set(media, entry);
121
121
  if (entry.media !== undefined) {
@@ -256,7 +256,7 @@ export class Room {
256
256
  const peer = this._Peers.get(peerId);
257
257
  if (peer === undefined || media.properties.kind !== 'audio')
258
258
  return;
259
- const uid = String((_a = media.properties.uid) !== null && _a !== void 0 ? _a : uuid.v4());
259
+ const uid = String((_a = media.properties.uid) !== null && _a !== void 0 ? _a : generateUUID());
260
260
  console.log(`uuid ${media.properties.uid} ${uid}`);
261
261
  media.properties.uid = uid; // feed the uid back, so it can be seen be seen down the chain.
262
262
  const entry = (_b = this._Playbacks.get(uid)) !== null && _b !== void 0 ? _b : {
@@ -1,13 +1,12 @@
1
- import { TypedRpcConnection } from './rpcConnection';
2
- import { Token } from './token';
3
1
  import { MainCommands, MainNotifications, Result } from '@4players/odin-common';
4
- type RpcConnection = TypedRpcConnection<MainCommands, MainNotifications>;
2
+ import { Token } from './token';
3
+ import { TypedRpcConnection } from './rpcConnection';
5
4
  export type DatagramHandler = (connectionId: number, datagram: Uint8Array) => void;
6
5
  export declare class MainConnection {
7
6
  readonly id: number;
8
7
  readonly url: URL;
9
8
  readonly onDatagram: DatagramHandler;
10
- readonly connection: RpcConnection;
9
+ readonly connection: TypedRpcConnection<MainCommands, MainNotifications>;
11
10
  readonly webRtc: RTCPeerConnection;
12
11
  static create(id: number, url: URL, onDatagram: DatagramHandler, signal: AbortSignal): Promise<Result<MainConnection>>;
13
12
  private readonly _AudioChannel;
@@ -36,4 +35,3 @@ export declare class MainConnection {
36
35
  private _OnIceCandidate;
37
36
  private _CleanUp;
38
37
  }
39
- export {};
@@ -8,10 +8,11 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  step((generator = generator.apply(thisArg, _arguments || [])).next());
9
9
  });
10
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
11
  import { MainCommandsRpc, MainNotificationsRpc, assert, extendUrl, failure, success, } from '@4players/odin-common';
12
+ import { Token } from './token';
13
+ import { TypedRpcConnection } from './rpcConnection';
14
+ import { connect } from './websocket';
15
+ import { HealthCheckTimeout, MainConnectionPingInterval, WebRTCIceTimeout as WebRtcIceTimeout, WebRTCTimeout as WebRtcTimeout, } from './config';
15
16
  export class MainConnection {
16
17
  static create(id, url, onDatagram, signal) {
17
18
  return __awaiter(this, void 0, void 0, function* () {
@@ -1,8 +1,7 @@
1
+ import { Backend, RoomCommands, Result, Infer } from '@4players/odin-common';
1
2
  import { ConnectionPool } from './connectionPool';
2
3
  import { MainConnection } from './mainConnection';
3
4
  import { Token } from './token';
4
- import { Backend, RoomCommands, Result } from '@4players/odin-common';
5
- import * as z from 'zod';
6
5
  export declare class RoomConnection {
7
6
  readonly connectionPool: ConnectionPool;
8
7
  readonly onEvent: Backend.OnEvent;
@@ -14,6 +13,8 @@ export declare class RoomConnection {
14
13
  private _Token;
15
14
  private _UserData;
16
15
  private _Position;
16
+ private _ConnectionFailures;
17
+ private _FailureLimit;
17
18
  private _LastTokenRenew;
18
19
  private readonly _Closed;
19
20
  get token(): Token;
@@ -23,7 +24,7 @@ export declare class RoomConnection {
23
24
  get mainConnection(): MainConnection | undefined;
24
25
  constructor(connectionPool: ConnectionPool, onEvent: Backend.OnEvent, gateway: URL | undefined, token: Token, roomId: string, userData: Uint8Array, position: [number, number, number]);
25
26
  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
+ typedRequest<Name extends Extract<keyof RoomCommands, string>>(name: Name, properties: Infer<RoomCommands[Name]['request']>): Promise<Result<Infer<RoomCommands[Name]['response']>> | 'closed'>;
27
28
  close(): void;
28
29
  get renewTokenIn(): number;
29
30
  private _Main;
@@ -7,10 +7,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
+ import { Selector, failure, sleep, success, RoomCommandsRpc, } from '@4players/odin-common';
10
11
  import { RpcConnection } from './rpcConnection';
11
12
  import { Token } from './token';
12
- import { Selector, failure, sleep, success, RoomCommandsRpc, } from '@4players/odin-common';
13
- import * as z from 'zod';
14
13
  import { ConnectTimeout, MinTokenLifetime, MinTokenRefreshInterval, } from './config';
15
14
  export class RoomConnection {
16
15
  get token() {
@@ -34,6 +33,8 @@ export class RoomConnection {
34
33
  this.gateway = gateway;
35
34
  this.roomId = roomId;
36
35
  this._Close = new AbortController();
36
+ this._ConnectionFailures = 0;
37
+ this._FailureLimit = 5;
37
38
  this._LastTokenRenew = performance.now();
38
39
  this._Closed = new Promise((accept) => this._Close.signal.addEventListener('abort', () => accept('closed'), {
39
40
  once: true,
@@ -144,6 +145,10 @@ export class RoomConnection {
144
145
  if (mainConnection === 'closed')
145
146
  return failure('connection pool closed'); // connection pool is closed
146
147
  if (mainConnection.type === 'Failure') {
148
+ if (this._ConnectionFailures < this._FailureLimit) {
149
+ this._ConnectionFailures++;
150
+ return failure(`Connection failed; ${mainConnection.reason}`);
151
+ }
147
152
  this._ChangeStatus('Joining', `connection failed; ${mainConnection.reason}`);
148
153
  return success('continue');
149
154
  }
@@ -180,13 +185,11 @@ export class RoomConnection {
180
185
  });
181
186
  }
182
187
  _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
- });
188
+ const properties = {
189
+ status,
190
+ message,
191
+ };
192
+ this.onEvent('RoomStatusChanged', properties);
190
193
  }
191
194
  _HandleConnection(main, webSocket) {
192
195
  return __awaiter(this, void 0, void 0, function* () {
@@ -264,6 +267,7 @@ export class RoomConnection {
264
267
  }
265
268
  }
266
269
  function contactGateway(gateway, token, signal) {
270
+ var _a, _b;
267
271
  return __awaiter(this, void 0, void 0, function* () {
268
272
  try {
269
273
  const response = yield fetch(gateway, {
@@ -275,24 +279,13 @@ function contactGateway(gateway, token, signal) {
275
279
  },
276
280
  signal,
277
281
  });
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());
282
+ // TODO: re-add validation once schemas are available in common
283
+ const body = yield response.json();
291
284
  if ('result' in body) {
292
285
  return Token.parse(body.result.token);
293
286
  }
294
287
  else {
295
- console.error(`gateway rejected token; ${body.error.message}`);
288
+ console.error(`gateway rejected token; ${(_b = (_a = body.error) === null || _a === void 0 ? void 0 : _a.message) !== null && _b !== void 0 ? _b : 'malformed response'}`);
296
289
  return success('rejected');
297
290
  }
298
291
  }
@@ -1,7 +1,12 @@
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>>;
1
+ import { Result, Accept, MessagePackRpc, Notifications, Infer, Commands } from '@4players/odin-common';
2
+ export interface RpcMessage {
3
+ name: string;
4
+ properties: unknown;
5
+ }
6
+ export interface RpcRequest {
7
+ accept: Accept<Result<unknown> | 'closed' | 'timeout'>;
8
+ timeoutId: ReturnType<typeof setTimeout> | undefined;
9
+ }
5
10
  export declare class RpcConnection {
6
11
  readonly webSocket: WebSocket;
7
12
  private readonly _SendQueue;
@@ -25,24 +30,22 @@ export declare class RpcConnection {
25
30
  private _OnEvent;
26
31
  private _Cleanup;
27
32
  }
28
- export type Commands = Record<string, {
29
- request: z.ZodTypeAny;
30
- response: z.ZodTypeAny;
31
- }>;
32
- export type Events = Record<string, z.ZodTypeAny>;
33
33
  export interface TypedRpcMessage<Name extends string, Properties> extends RpcMessage {
34
34
  name: Name;
35
35
  properties: Properties;
36
36
  }
37
- export type RealizedEvents<E extends Events> = {
38
- [Name in Extract<keyof E, string>]: TypedRpcMessage<Name, z.infer<E[Name]>>;
37
+ export type RealizedEvents<E extends Notifications> = {
38
+ [Name in Extract<keyof E, string>]: TypedRpcMessage<Name, Infer<E[Name]>>;
39
39
  }[Extract<keyof E, string>];
40
- export declare class TypedRpcConnection<C extends Commands, E extends Events> extends RpcConnection {
40
+ export declare class TypedRpcConnection<C extends Commands, E extends Notifications> extends RpcConnection {
41
41
  readonly _Commands: C;
42
42
  readonly _Events: E;
43
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;
44
+ request<Name extends Extract<keyof C, string>>(name: Name, properties: Infer<C[Name]['request']>): Promise<Result<Infer<C[Name]['response']>> | 'closed'>;
45
+ request<Name extends Extract<keyof C, string>>(name: Name, properties: Infer<C[Name]['request']>, timeout: number): Promise<Result<Infer<C[Name]['response']>> | 'closed' | 'timeout'>;
46
+ notify<Name extends Extract<keyof C, string>>(name: Name, properties: Infer<C[Name]['request']>): void;
47
47
  recv(): Promise<RealizedEvents<E> | 'closed'>;
48
48
  }
49
+ export declare function parseRpc(buffer: ArrayBuffer): Result<MessagePackRpc>;
50
+ export declare function parseRpcMessage<E extends Notifications>(events: E, rpc: RpcMessage): RealizedEvents<E> | undefined;
51
+ export declare function isKnownRpcMessage<E extends Notifications>(events: E, name: string): name is Extract<keyof E, string>;
@@ -8,55 +8,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  step((generator = generator.apply(thisArg, _arguments || [])).next());
9
9
  });
10
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
- }
11
+ import { failure, success, msgpackEncode, msgpackDecode, MessagePackRpcSchema, } from '@4players/odin-common';
60
12
  export class RpcConnection {
61
13
  get closed() {
62
14
  return this._Close.signal.aborted;
@@ -120,7 +72,7 @@ export class RpcConnection {
120
72
  request.accept('timeout');
121
73
  }
122
74
  _Send(message) {
123
- this.webSocket.send(msgpack.encode(message));
75
+ this.webSocket.send(msgpackEncode(message));
124
76
  }
125
77
  _OnWebSocketOpen(ev) {
126
78
  for (const data in this._SendQueue) {
@@ -232,3 +184,26 @@ export class TypedRpcConnection extends RpcConnection {
232
184
  });
233
185
  }
234
186
  }
187
+ export function parseRpc(buffer) {
188
+ try {
189
+ return success(MessagePackRpcSchema.parse(msgpackDecode(buffer)));
190
+ }
191
+ catch (error) {
192
+ return failure(String(error));
193
+ }
194
+ }
195
+ export function parseRpcMessage(events, rpc) {
196
+ if (!isKnownRpcMessage(events, rpc.name))
197
+ return undefined;
198
+ const parsed = events[rpc.name].safeParse(rpc.properties);
199
+ if (!parsed.success) {
200
+ return undefined;
201
+ }
202
+ return {
203
+ name: rpc.name,
204
+ properties: parsed.data,
205
+ };
206
+ }
207
+ export function isKnownRpcMessage(events, name) {
208
+ return name in events;
209
+ }
@@ -1,6 +1,4 @@
1
- import * as z from 'zod';
2
- import { TokenAudienceSchema, TokenClaimsSchema, Result } from '@4players/odin-common';
3
- export type TokenClaims = z.infer<typeof TokenClaimsSchema>;
1
+ import { TokenAudienceSchema, TokenClaims, Result, Infer } from '@4players/odin-common';
4
2
  export declare class Token {
5
3
  readonly text: string;
6
4
  readonly claims: TokenClaims;
@@ -12,6 +10,6 @@ export declare class Token {
12
10
  get address(): URL | undefined;
13
11
  get upstream(): Token | undefined;
14
12
  get defaultRoomId(): string | undefined;
15
- hasAudience(audience: z.infer<typeof TokenAudienceSchema>): boolean;
13
+ hasAudience(audience: Infer<typeof TokenAudienceSchema>): boolean;
16
14
  toString(): string;
17
15
  }
@@ -24,13 +24,12 @@ export class Token {
24
24
  constructor(text, claims) {
25
25
  this.text = text;
26
26
  this.claims = claims;
27
- const address = claims.adr;
28
- if (address !== undefined) {
27
+ if (claims.adr !== undefined) {
29
28
  try {
30
- this._Address = new URL(`wss://${address}`);
29
+ this._Address = new URL(`wss://${claims.adr}`);
31
30
  }
32
31
  catch (error) {
33
- console.error(`address in token(${address}) can not be parsed; ${error}`);
32
+ console.error(`address in token(${claims.adr}) can not be parsed; ${error}`);
34
33
  }
35
34
  }
36
35
  if (claims.ups !== undefined) {
@@ -0,0 +1,3 @@
1
+ import { Result } from '@4players/odin-common';
2
+ export declare function connect(url: URL, signal: AbortSignal): Promise<Result<WebSocket>>;
3
+ export declare function connectOnce(url: URL, signal: AbortSignal): Promise<Result<WebSocket>>;
@@ -0,0 +1,56 @@
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 { ConnectInterval } from './config';
11
+ import { abortableSleep, failure, success, } from '@4players/odin-common';
12
+ export function connect(url, signal) {
13
+ return __awaiter(this, void 0, void 0, function* () {
14
+ const start = performance.now();
15
+ let connection = failure('no connection was attempted');
16
+ for (let failures = 0; failures < ConnectInterval.length && signal.aborted === false; failures++) {
17
+ const nextReconnectAt = start + whenToConnect(failures);
18
+ if ((yield abortableSleep(nextReconnectAt - performance.now(), signal)) ===
19
+ 'aborted') {
20
+ return failure('aborted');
21
+ }
22
+ connection = yield connectOnce(url, signal);
23
+ if (connection.type === 'Success') {
24
+ return connection;
25
+ }
26
+ }
27
+ return connection;
28
+ });
29
+ }
30
+ export function connectOnce(url, signal) {
31
+ return __awaiter(this, void 0, void 0, function* () {
32
+ let webSocket;
33
+ try {
34
+ webSocket = new WebSocket(url);
35
+ }
36
+ catch (reason) {
37
+ return failure(`unable to create websocket; ${reason}`);
38
+ }
39
+ const abort = new AbortController();
40
+ const onOpen = new Promise((accept) => webSocket.addEventListener('open', () => accept(success(webSocket)), {
41
+ signal: abort.signal,
42
+ }));
43
+ const onClose = new Promise((accept) => webSocket.addEventListener('close', (event) => accept(failure(event.reason)), { signal: abort.signal }));
44
+ const onAbort = new Promise((accept) => signal.addEventListener('abort', () => accept(failure('aborted')), {
45
+ signal: abort.signal,
46
+ }));
47
+ const result = yield Promise.race([onOpen, onClose, onAbort]);
48
+ abort.abort();
49
+ return result;
50
+ });
51
+ }
52
+ function whenToConnect(failures) {
53
+ const past_connects = ConnectInterval.slice(0, failures).reduce((a, b) => a + b, 0);
54
+ const interval = ConnectInterval[failures] * Math.random();
55
+ return past_connects + interval;
56
+ }