@4players/odin-plugin-web 1.0.0-alpha.13 → 1.0.0-alpha.15
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/lib/common/msgpack.d.ts +2 -0
- package/lib/common/msgpack.js +117 -0
- package/lib/plugin/config.d.ts +5 -3
- package/lib/plugin/config.js +15 -3
- package/lib/plugin/connectionPool.d.ts +3 -2
- package/lib/plugin/connectionPool.js +31 -13
- package/lib/plugin/deviceManager.d.ts +12 -0
- package/lib/plugin/deviceManager.js +120 -0
- package/lib/plugin/h3/mainConnection.d.ts +40 -0
- package/lib/plugin/h3/mainConnection.js +197 -0
- package/lib/plugin/index.d.ts +17 -6
- package/lib/plugin/index.js +158 -56
- package/lib/plugin/mainConnection.d.ts +23 -30
- package/lib/plugin/mainConnection.js +20 -291
- package/lib/plugin/{audioController.d.ts → mediaController.d.ts} +6 -46
- package/lib/plugin/mediaController.js +215 -0
- package/lib/plugin/medias/audio-capture.d.ts +29 -0
- package/lib/plugin/medias/audio-capture.js +203 -0
- package/lib/plugin/medias/audio-playback.d.ts +13 -0
- package/lib/plugin/medias/audio-playback.js +24 -0
- package/lib/plugin/medias/video-capture.d.ts +15 -0
- package/lib/plugin/medias/video-capture.js +70 -0
- package/lib/plugin/medias/video-playback.d.ts +13 -0
- package/lib/plugin/medias/video-playback.js +20 -0
- package/lib/plugin/roomConnection.d.ts +5 -2
- package/lib/plugin/roomConnection.js +54 -31
- package/lib/plugin/rpcConnection.d.ts +13 -7
- package/lib/plugin/rpcConnection.js +103 -33
- package/lib/plugin/token.d.ts +0 -2
- package/lib/plugin/token.js +0 -11
- package/lib/plugin/webrtc/mainConnection.d.ts +52 -0
- package/lib/plugin/webrtc/mainConnection.js +455 -0
- package/lib/plugin/{websocket.js → webrtc/websocket.js} +2 -2
- package/lib/version.d.ts +1 -0
- package/lib/version.js +2 -0
- package/lib/worker/index.js +1 -1
- package/package.json +18 -11
- package/lib/plugin/audioController.js +0 -513
- /package/lib/plugin/{websocket.d.ts → webrtc/websocket.d.ts} +0 -0
|
@@ -0,0 +1,203 @@
|
|
|
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 { success } from '@4players/odin-common';
|
|
11
|
+
import { Silence } from '../../worker/common';
|
|
12
|
+
import { createCaptureWorker } from '../../worker';
|
|
13
|
+
import { openCaptureDevice, stopMediaStream, enumerateDevices, } from '../deviceManager';
|
|
14
|
+
const defaultVad = {
|
|
15
|
+
voiceActivity: {
|
|
16
|
+
attackThreshold: 0.9,
|
|
17
|
+
releaseThreshold: 0.8,
|
|
18
|
+
},
|
|
19
|
+
volumeGate: {
|
|
20
|
+
attackThreshold: -30,
|
|
21
|
+
releaseThreshold: -40,
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
const defaultApm = {
|
|
25
|
+
echoCanceller: false,
|
|
26
|
+
highPassFilter: false,
|
|
27
|
+
preAmplifier: false,
|
|
28
|
+
captureLevelAdjustment: false,
|
|
29
|
+
noiseSuppression: 'None',
|
|
30
|
+
transientSuppressor: false,
|
|
31
|
+
gainController: false,
|
|
32
|
+
};
|
|
33
|
+
export class AudioCapture {
|
|
34
|
+
get vad() {
|
|
35
|
+
return this._Vad;
|
|
36
|
+
}
|
|
37
|
+
get apm() {
|
|
38
|
+
return this._Apm;
|
|
39
|
+
}
|
|
40
|
+
get volume() {
|
|
41
|
+
return this._Volume;
|
|
42
|
+
}
|
|
43
|
+
get activity() {
|
|
44
|
+
var _a, _b;
|
|
45
|
+
return (_b = (_a = this._Capture) === null || _a === void 0 ? void 0 : _a.worker.activity) !== null && _b !== void 0 ? _b : Silence;
|
|
46
|
+
}
|
|
47
|
+
get speechProbability() {
|
|
48
|
+
var _a, _b;
|
|
49
|
+
return (_b = (_a = this._Capture) === null || _a === void 0 ? void 0 : _a.worker.speechProbability) !== null && _b !== void 0 ? _b : 0;
|
|
50
|
+
}
|
|
51
|
+
get deviceId() {
|
|
52
|
+
var _a, _b;
|
|
53
|
+
return (_b = (_a = this._DeviceParameters) === null || _a === void 0 ? void 0 : _a.device) === null || _b === void 0 ? void 0 : _b.device.deviceId;
|
|
54
|
+
}
|
|
55
|
+
constructor(controller, parameters) {
|
|
56
|
+
this.controller = controller;
|
|
57
|
+
this._Medias = new Map();
|
|
58
|
+
this._Vad = defaultVad;
|
|
59
|
+
this._Apm = defaultApm;
|
|
60
|
+
this._Volume = 1;
|
|
61
|
+
this._DeviceCb = () => __awaiter(this, void 0, void 0, function* () {
|
|
62
|
+
var _a, _b;
|
|
63
|
+
const devices = yield enumerateDevices();
|
|
64
|
+
const isListed = devices.some((device) => {
|
|
65
|
+
var _a, _b;
|
|
66
|
+
return (device.device.deviceId ===
|
|
67
|
+
((_b = (_a = this._DeviceParameters) === null || _a === void 0 ? void 0 : _a.device) === null || _b === void 0 ? void 0 : _b.device.deviceId));
|
|
68
|
+
});
|
|
69
|
+
if (!isListed) {
|
|
70
|
+
(_b = (_a = this._DeviceParameters) === null || _a === void 0 ? void 0 : _a.onStatusChanged) === null || _b === void 0 ? void 0 : _b.call(_a, 'stopped');
|
|
71
|
+
navigator.mediaDevices.removeEventListener('devicechange', this._DeviceCb);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
console.log('Eyvor AudioCapture constructor');
|
|
75
|
+
if (parameters.apm) {
|
|
76
|
+
this.setApm(parameters.apm);
|
|
77
|
+
}
|
|
78
|
+
if (parameters.vad) {
|
|
79
|
+
this.setVad(parameters.vad);
|
|
80
|
+
}
|
|
81
|
+
if (parameters.volume) {
|
|
82
|
+
this.setVolume(parameters.volume);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
start(room, media, uid) {
|
|
86
|
+
var _a;
|
|
87
|
+
let entry = this._Medias.get(media.connectionId);
|
|
88
|
+
if (entry === undefined) {
|
|
89
|
+
this._Medias.set(media.connectionId, (entry = new Set()));
|
|
90
|
+
}
|
|
91
|
+
if (entry.has(media.mediaId))
|
|
92
|
+
return;
|
|
93
|
+
entry.add(media.mediaId);
|
|
94
|
+
if (((_a = room.mainConnection) === null || _a === void 0 ? void 0 : _a.id) === media.connectionId) {
|
|
95
|
+
room
|
|
96
|
+
.typedRequest('StartMedia', {
|
|
97
|
+
media_id: media.mediaId,
|
|
98
|
+
properties: { kind: 'audio', uid },
|
|
99
|
+
})
|
|
100
|
+
.catch((reason) => console.error(`StartMedia got rejected; ${reason}`));
|
|
101
|
+
}
|
|
102
|
+
this._UpdateMedias();
|
|
103
|
+
}
|
|
104
|
+
stop(room, media) {
|
|
105
|
+
var _a;
|
|
106
|
+
const entry = this._Medias.get(media.connectionId);
|
|
107
|
+
if (!(entry === null || entry === void 0 ? void 0 : entry.delete(media.mediaId)))
|
|
108
|
+
return;
|
|
109
|
+
if (((_a = room.mainConnection) === null || _a === void 0 ? void 0 : _a.id) === media.connectionId) {
|
|
110
|
+
room
|
|
111
|
+
.typedRequest('StopMedia', { media_id: media.mediaId })
|
|
112
|
+
.catch((reason) => console.error(`StopMedia got rejected; ${reason}`));
|
|
113
|
+
}
|
|
114
|
+
this._UpdateMedias();
|
|
115
|
+
}
|
|
116
|
+
setVad(value) {
|
|
117
|
+
this._Vad = value;
|
|
118
|
+
if (this._Capture) {
|
|
119
|
+
this._Capture.worker.setVad(value);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
setApm(config) {
|
|
123
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
124
|
+
this._Apm = config;
|
|
125
|
+
if (this._DeviceParameters) {
|
|
126
|
+
yield this.setDevice(this._DeviceParameters);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
setDevice(parameters) {
|
|
131
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
132
|
+
var _a;
|
|
133
|
+
console.log('Eyvor AudioCapture setDevice() parameters: ', parameters);
|
|
134
|
+
const rollbackParams = this._DeviceParameters;
|
|
135
|
+
this._DeviceParameters = parameters;
|
|
136
|
+
if (this._Volume === 'muted') {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
const device = yield openCaptureDevice(this.controller, parameters, this._Apm);
|
|
140
|
+
if (device.type !== 'Success') {
|
|
141
|
+
console.error(`unable to open capture device; ${device.reason}`);
|
|
142
|
+
this._DeviceParameters = rollbackParams;
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
if (this._DeviceParameters.onStatusChanged) {
|
|
146
|
+
navigator.mediaDevices.addEventListener('devicechange', this._DeviceCb);
|
|
147
|
+
}
|
|
148
|
+
if (this._Capture !== undefined) {
|
|
149
|
+
stopMediaStream(this._Capture.source.mediaStream);
|
|
150
|
+
this._Capture.source.disconnect(this._Capture.worker.worklet);
|
|
151
|
+
}
|
|
152
|
+
if (this._Capture === undefined ||
|
|
153
|
+
this._Capture.worker.worklet.context !== device.value.context) {
|
|
154
|
+
(_a = this._Capture) === null || _a === void 0 ? void 0 : _a.worker.close();
|
|
155
|
+
this._Capture = {
|
|
156
|
+
worker: yield createCaptureWorker(device.value.context, this.controller.sink),
|
|
157
|
+
source: device.value,
|
|
158
|
+
};
|
|
159
|
+
yield this._UpdateMedias();
|
|
160
|
+
this.setVolume(this.volume);
|
|
161
|
+
this.setVad(this.vad);
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
this._Capture.source = device.value;
|
|
165
|
+
}
|
|
166
|
+
this._Capture.source.connect(this._Capture.worker.worklet);
|
|
167
|
+
return success();
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
setVolume(value) {
|
|
171
|
+
var _a;
|
|
172
|
+
this._Volume = value;
|
|
173
|
+
if (value === 'muted') {
|
|
174
|
+
this.close();
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
if (!this._Capture && this._DeviceParameters) {
|
|
178
|
+
this.setDevice(this._DeviceParameters);
|
|
179
|
+
}
|
|
180
|
+
(_a = this._Capture) === null || _a === void 0 ? void 0 : _a.worker.setVolume(value);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
close() {
|
|
184
|
+
if (this._Capture !== undefined) {
|
|
185
|
+
this._Capture.worker.close();
|
|
186
|
+
stopMediaStream(this._Capture.source.mediaStream);
|
|
187
|
+
this._Capture.source.disconnect(this._Capture.worker.worklet);
|
|
188
|
+
this.controller.audioContexts.release(this._Capture.source.context);
|
|
189
|
+
}
|
|
190
|
+
navigator.mediaDevices.removeEventListener('devicechange', this._DeviceCb);
|
|
191
|
+
this._Capture = undefined;
|
|
192
|
+
}
|
|
193
|
+
_UpdateMedias() {
|
|
194
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
195
|
+
if (this._Capture !== undefined) {
|
|
196
|
+
const connections = Array.from(this._Medias).map(([key, value]) => {
|
|
197
|
+
return { id: key, mediaIds: Array.from(value) };
|
|
198
|
+
});
|
|
199
|
+
this._Capture.worker.setMedias(connections);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Backend } from '@4players/odin-common';
|
|
2
|
+
import { GlobalMediaId, MediaController } from '../mediaController';
|
|
3
|
+
export declare class AudioPlayback implements Backend.AudioPlayback {
|
|
4
|
+
readonly controller: MediaController;
|
|
5
|
+
readonly uid: Backend.UID;
|
|
6
|
+
get volume(): Backend.Volume;
|
|
7
|
+
get activity(): Backend.Activity;
|
|
8
|
+
constructor(controller: MediaController, uid: Backend.UID);
|
|
9
|
+
start(id: GlobalMediaId): void;
|
|
10
|
+
stop(id: GlobalMediaId): void;
|
|
11
|
+
setVolume(value: Backend.Volume): void;
|
|
12
|
+
close(): void;
|
|
13
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export class AudioPlayback {
|
|
2
|
+
get volume() {
|
|
3
|
+
return this.controller.playbackGetVolume(this.uid);
|
|
4
|
+
}
|
|
5
|
+
get activity() {
|
|
6
|
+
return this.controller.playbackGetActivity(this.uid);
|
|
7
|
+
}
|
|
8
|
+
constructor(controller, uid) {
|
|
9
|
+
this.controller = controller;
|
|
10
|
+
this.uid = uid;
|
|
11
|
+
}
|
|
12
|
+
start(id) {
|
|
13
|
+
this.controller.playbackStart(this.uid, id);
|
|
14
|
+
}
|
|
15
|
+
stop(id) {
|
|
16
|
+
this.controller.playbackStop(this.uid, id);
|
|
17
|
+
}
|
|
18
|
+
setVolume(value) {
|
|
19
|
+
this.controller.playbackSetVolume(this.uid, value);
|
|
20
|
+
}
|
|
21
|
+
close() {
|
|
22
|
+
this.controller.closePlayback(this.uid);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { GlobalMediaId, MediaController } from '../mediaController';
|
|
2
|
+
import { Backend } from '@4players/odin-common';
|
|
3
|
+
import { RoomConnection } from '../roomConnection';
|
|
4
|
+
export declare class VideoCapture implements Backend.VideoCapture {
|
|
5
|
+
readonly controller: MediaController;
|
|
6
|
+
readonly customType?: string | undefined;
|
|
7
|
+
private _mediaStream?;
|
|
8
|
+
private readonly _Medias;
|
|
9
|
+
constructor(controller: MediaController, customType?: string | undefined);
|
|
10
|
+
get mediaStream(): MediaStream | undefined;
|
|
11
|
+
setDevice(ms: MediaStream): Promise<void>;
|
|
12
|
+
start(room: RoomConnection, media: GlobalMediaId, uid: Backend.UID): void;
|
|
13
|
+
stop(room: RoomConnection, media: GlobalMediaId): void;
|
|
14
|
+
close(): void;
|
|
15
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
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 { stopMediaStream } from '../deviceManager';
|
|
11
|
+
export class VideoCapture {
|
|
12
|
+
constructor(controller, customType) {
|
|
13
|
+
this.controller = controller;
|
|
14
|
+
this.customType = customType;
|
|
15
|
+
this._Medias = new Map();
|
|
16
|
+
}
|
|
17
|
+
get mediaStream() {
|
|
18
|
+
return this._mediaStream;
|
|
19
|
+
}
|
|
20
|
+
setDevice(ms) {
|
|
21
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
22
|
+
// @TODO: Stop old ms before
|
|
23
|
+
this._mediaStream = ms;
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
start(room, media, uid) {
|
|
27
|
+
var _a;
|
|
28
|
+
console.log('Eyvor VideoCapture start()');
|
|
29
|
+
let entry = this._Medias.get(media.connectionId);
|
|
30
|
+
if (entry === undefined) {
|
|
31
|
+
this._Medias.set(media.connectionId, (entry = new Set()));
|
|
32
|
+
}
|
|
33
|
+
if (entry.has(media.mediaId))
|
|
34
|
+
return;
|
|
35
|
+
entry.add(media.mediaId);
|
|
36
|
+
if (((_a = room.mainConnection) === null || _a === void 0 ? void 0 : _a.id) === media.connectionId) {
|
|
37
|
+
const properties = {
|
|
38
|
+
kind: 'video',
|
|
39
|
+
codec: 'VP8',
|
|
40
|
+
uid,
|
|
41
|
+
};
|
|
42
|
+
if (this.customType) {
|
|
43
|
+
properties.customType = this.customType;
|
|
44
|
+
}
|
|
45
|
+
room
|
|
46
|
+
.typedRequest('StartMedia', {
|
|
47
|
+
media_id: media.mediaId,
|
|
48
|
+
properties,
|
|
49
|
+
})
|
|
50
|
+
.catch((reason) => console.error(`StartMedia got rejected; ${reason}`));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
stop(room, media) {
|
|
54
|
+
var _a;
|
|
55
|
+
if (this._mediaStream) {
|
|
56
|
+
stopMediaStream(this._mediaStream);
|
|
57
|
+
}
|
|
58
|
+
const entry = this._Medias.get(media.connectionId);
|
|
59
|
+
if (!(entry === null || entry === void 0 ? void 0 : entry.delete(media.mediaId)))
|
|
60
|
+
return;
|
|
61
|
+
if (((_a = room.mainConnection) === null || _a === void 0 ? void 0 : _a.id) === media.connectionId) {
|
|
62
|
+
room
|
|
63
|
+
.typedRequest('StopMedia', { media_id: media.mediaId })
|
|
64
|
+
.catch((reason) => console.error(`StopMedia got rejected; ${reason}`));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
close() {
|
|
68
|
+
throw new Error('Method not implemented.');
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Backend } from '@4players/odin-common';
|
|
2
|
+
export declare class VideoPlayback implements Backend.VideoPlayback {
|
|
3
|
+
readonly uid: Backend.UID;
|
|
4
|
+
readonly customType?: string | undefined;
|
|
5
|
+
private _mediaStream?;
|
|
6
|
+
constructor(uid: Backend.UID, customType?: string | undefined);
|
|
7
|
+
/**
|
|
8
|
+
* Sets the MediaStream once.
|
|
9
|
+
*/
|
|
10
|
+
setMediaStream(ms?: MediaStream): void;
|
|
11
|
+
get mediaStream(): MediaStream | undefined;
|
|
12
|
+
close(): void;
|
|
13
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export class VideoPlayback {
|
|
2
|
+
constructor(uid, customType) {
|
|
3
|
+
this.uid = uid;
|
|
4
|
+
this.customType = customType;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Sets the MediaStream once.
|
|
8
|
+
*/
|
|
9
|
+
setMediaStream(ms) {
|
|
10
|
+
if (this._mediaStream)
|
|
11
|
+
return;
|
|
12
|
+
this._mediaStream = ms;
|
|
13
|
+
}
|
|
14
|
+
get mediaStream() {
|
|
15
|
+
return this._mediaStream;
|
|
16
|
+
}
|
|
17
|
+
close() {
|
|
18
|
+
throw new Error('Method not implemented.');
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -2,13 +2,16 @@ import { Backend, RoomCommands, Result, Infer } from '@4players/odin-common';
|
|
|
2
2
|
import { ConnectionPool } from './connectionPool';
|
|
3
3
|
import { MainConnection } from './mainConnection';
|
|
4
4
|
import { Token } from './token';
|
|
5
|
-
|
|
5
|
+
import Transport = Backend.Transport;
|
|
6
|
+
export declare class RoomConnection implements RoomConnection {
|
|
6
7
|
readonly connectionPool: ConnectionPool;
|
|
8
|
+
readonly transport: Transport;
|
|
7
9
|
readonly onEvent: Backend.OnEvent;
|
|
8
10
|
readonly gateway: URL | undefined;
|
|
9
11
|
readonly roomId: string;
|
|
10
12
|
private _RoomConnection;
|
|
11
13
|
private _MainConnection;
|
|
14
|
+
private _OwnPeerId;
|
|
12
15
|
private readonly _Close;
|
|
13
16
|
private _Token;
|
|
14
17
|
private _UserData;
|
|
@@ -22,7 +25,7 @@ export declare class RoomConnection {
|
|
|
22
25
|
get position(): [number, number, number];
|
|
23
26
|
get closed(): boolean;
|
|
24
27
|
get mainConnection(): MainConnection | undefined;
|
|
25
|
-
constructor(connectionPool: ConnectionPool, onEvent: Backend.OnEvent, gateway: URL | undefined, token: Token, roomId: string, userData: Uint8Array, position: [number, number, number]);
|
|
28
|
+
constructor(connectionPool: ConnectionPool, transport: Transport, onEvent: Backend.OnEvent, gateway: URL | undefined, token: Token, roomId: string, userData: Uint8Array, position: [number, number, number]);
|
|
26
29
|
request(name: string, properties: unknown): Promise<Result<unknown> | 'closed'>;
|
|
27
30
|
typedRequest<Name extends Extract<keyof RoomCommands, string>>(name: Name, properties: Infer<RoomCommands[Name]['request']>): Promise<Result<Infer<RoomCommands[Name]['response']>> | 'closed'>;
|
|
28
31
|
close(): void;
|
|
@@ -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';
|
|
11
|
-
import { RpcConnection } from './rpcConnection';
|
|
10
|
+
import { Selector, failure, sleep, success, RoomCommandsRpc, RoomNotificationsRpc, } from '@4players/odin-common';
|
|
12
11
|
import { Token } from './token';
|
|
13
|
-
import { ConnectTimeout, MinTokenLifetime, MinTokenRefreshInterval, } from './config';
|
|
12
|
+
import { ConnectTimeout, logger, MinTokenLifetime, MinTokenRefreshInterval, } from './config';
|
|
14
13
|
export class RoomConnection {
|
|
15
14
|
get token() {
|
|
16
15
|
return this._Token;
|
|
@@ -27,8 +26,9 @@ export class RoomConnection {
|
|
|
27
26
|
get mainConnection() {
|
|
28
27
|
return this._MainConnection;
|
|
29
28
|
}
|
|
30
|
-
constructor(connectionPool, onEvent, gateway, token, roomId, userData, position) {
|
|
29
|
+
constructor(connectionPool, transport, onEvent, gateway, token, roomId, userData, position) {
|
|
31
30
|
this.connectionPool = connectionPool;
|
|
31
|
+
this.transport = transport;
|
|
32
32
|
this.onEvent = onEvent;
|
|
33
33
|
this.gateway = gateway;
|
|
34
34
|
this.roomId = roomId;
|
|
@@ -42,7 +42,7 @@ export class RoomConnection {
|
|
|
42
42
|
this._Token = token;
|
|
43
43
|
this._UserData = userData;
|
|
44
44
|
this._Position = position;
|
|
45
|
-
this._Main().then(({ reason }) =>
|
|
45
|
+
this._Main().then(({ reason }) => logger.info(['Connection'], `RoomConnection: Main() resolved; ${reason}`));
|
|
46
46
|
}
|
|
47
47
|
request(name, properties) {
|
|
48
48
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -79,11 +79,12 @@ export class RoomConnection {
|
|
|
79
79
|
return Math.max(earliest, latest);
|
|
80
80
|
}
|
|
81
81
|
_Main() {
|
|
82
|
-
var _a;
|
|
83
82
|
return __awaiter(this, void 0, void 0, function* () {
|
|
83
|
+
var _a;
|
|
84
84
|
let closeReason = 'unable to establish connection';
|
|
85
85
|
/// as long as we are asked to continue
|
|
86
86
|
while (this._Close.signal.aborted === false) {
|
|
87
|
+
logger.debug(['Connection', 'RoomConnection._Main() loop']);
|
|
87
88
|
const result = yield this._ConnectToServer();
|
|
88
89
|
if (result.type === 'Failure') {
|
|
89
90
|
closeReason = result.reason;
|
|
@@ -92,6 +93,7 @@ export class RoomConnection {
|
|
|
92
93
|
(_a = this._RoomConnection) === null || _a === void 0 ? void 0 : _a.close();
|
|
93
94
|
this._RoomConnection = undefined;
|
|
94
95
|
this._MainConnection = undefined;
|
|
96
|
+
this._OwnPeerId = undefined;
|
|
95
97
|
// can the token be used to request a better token from a gateway?
|
|
96
98
|
if (this.gateway !== undefined && this._Token.hasAudience('gateway')) {
|
|
97
99
|
this._ChangeStatus('Joining', `requesting new token from ${this.gateway}`);
|
|
@@ -128,20 +130,20 @@ export class RoomConnection {
|
|
|
128
130
|
while (token.upstream !== undefined) {
|
|
129
131
|
token = token.upstream;
|
|
130
132
|
}
|
|
131
|
-
if (!token.hasAudience('sfu') || token.
|
|
133
|
+
if (!token.hasAudience('sfu') || token.claims.adr === undefined) {
|
|
132
134
|
return success('continue');
|
|
133
135
|
}
|
|
134
|
-
this._ChangeStatus('Joining', `connecting to ${token.
|
|
136
|
+
this._ChangeStatus('Joining', `connecting to ${token.claims.adr}`);
|
|
135
137
|
// are we already connected to the server?
|
|
136
138
|
if (this._MainConnection === undefined ||
|
|
137
139
|
this._MainConnection.closed ||
|
|
138
|
-
this._MainConnection.
|
|
140
|
+
this._MainConnection.address !== token.claims.adr) {
|
|
139
141
|
// no? release the not fitting connection
|
|
140
142
|
if (this._MainConnection) {
|
|
141
143
|
this.connectionPool.releaseConnection(this._MainConnection);
|
|
142
144
|
}
|
|
143
145
|
// connect to the right server
|
|
144
|
-
const mainConnection = yield this.connectionPool.acquireConnection(token.
|
|
146
|
+
const mainConnection = yield this.connectionPool.acquireConnection(this.transport, token.claims.adr, this._Close.signal);
|
|
145
147
|
if (mainConnection === 'closed')
|
|
146
148
|
return failure('connection pool closed'); // connection pool is closed
|
|
147
149
|
if (mainConnection.type === 'Failure') {
|
|
@@ -153,6 +155,7 @@ export class RoomConnection {
|
|
|
153
155
|
return success('continue');
|
|
154
156
|
}
|
|
155
157
|
this._MainConnection = mainConnection.value;
|
|
158
|
+
this._OwnPeerId = undefined;
|
|
156
159
|
}
|
|
157
160
|
// join room
|
|
158
161
|
const result = yield Promise.race([
|
|
@@ -165,20 +168,22 @@ export class RoomConnection {
|
|
|
165
168
|
if (result === 'closed')
|
|
166
169
|
return failure('room connection closed'); // room connection was closed
|
|
167
170
|
if (result !== 'timeout') {
|
|
168
|
-
const [
|
|
169
|
-
if (
|
|
171
|
+
const [rpc] = result;
|
|
172
|
+
if (rpc === 'closed')
|
|
170
173
|
return success('continue'); // connection was lost, before joinRoom finished
|
|
171
|
-
if (
|
|
174
|
+
if (rpc.type === 'Failure') {
|
|
172
175
|
// join was rejected by the server
|
|
173
|
-
return failure(`unable to join room; ${
|
|
176
|
+
return failure(`unable to join room; ${rpc.reason}`);
|
|
174
177
|
}
|
|
175
178
|
// room connection is open, make it available
|
|
176
|
-
this._RoomConnection =
|
|
177
|
-
this._ChangeStatus('Joined', `joined
|
|
179
|
+
this._RoomConnection = rpc.value;
|
|
180
|
+
this._ChangeStatus('Joined', `joined ${this.roomId}`);
|
|
178
181
|
// TODO(sven): handle this
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
+
this._MainConnection
|
|
183
|
+
.waitForConnected()
|
|
184
|
+
.catch((error) => console.error('main connection failed to connect', error));
|
|
185
|
+
// Handle the open connection until it gets closed
|
|
186
|
+
yield this._HandleConnection(this._MainConnection);
|
|
182
187
|
}
|
|
183
188
|
// try to reconnect
|
|
184
189
|
return success('continue');
|
|
@@ -191,14 +196,15 @@ export class RoomConnection {
|
|
|
191
196
|
};
|
|
192
197
|
this.onEvent('RoomStatusChanged', properties);
|
|
193
198
|
}
|
|
194
|
-
_HandleConnection(main
|
|
199
|
+
_HandleConnection(main) {
|
|
195
200
|
return __awaiter(this, void 0, void 0, function* () {
|
|
201
|
+
var _a;
|
|
196
202
|
if (this._RoomConnection === undefined)
|
|
197
203
|
return;
|
|
198
204
|
const selector = new Selector([
|
|
199
205
|
() => sleep(this.renewTokenIn, 'renewToken'),
|
|
200
206
|
() => sleep(500, 'tick'),
|
|
201
|
-
() => __awaiter(this, void 0, void 0, function* () { var
|
|
207
|
+
() => __awaiter(this, void 0, void 0, function* () { var _b; return yield ((_b = this._RoomConnection) === null || _b === void 0 ? void 0 : _b.recv()); }),
|
|
202
208
|
() => this._Closed,
|
|
203
209
|
() => main.closePromise,
|
|
204
210
|
]);
|
|
@@ -207,10 +213,11 @@ export class RoomConnection {
|
|
|
207
213
|
switch (value) {
|
|
208
214
|
case undefined:
|
|
209
215
|
case 'closed':
|
|
210
|
-
|
|
216
|
+
logger.debug(['Connection'], 'RoomConnection._HandleConnection: closed');
|
|
217
|
+
(_a = this._RoomConnection) === null || _a === void 0 ? void 0 : _a.close();
|
|
211
218
|
return;
|
|
212
219
|
case 'renewToken':
|
|
213
|
-
this._RenewToken(
|
|
220
|
+
this._RenewToken();
|
|
214
221
|
break;
|
|
215
222
|
case 'tick':
|
|
216
223
|
break;
|
|
@@ -222,12 +229,23 @@ export class RoomConnection {
|
|
|
222
229
|
});
|
|
223
230
|
}
|
|
224
231
|
_HandleEvent(event) {
|
|
232
|
+
if (event.name === 'RoomUpdated') {
|
|
233
|
+
const roomUpdated = RoomNotificationsRpc.RoomUpdated.safeParse(event.properties);
|
|
234
|
+
if (!roomUpdated.success)
|
|
235
|
+
return;
|
|
236
|
+
for (const update of roomUpdated.data.updates) {
|
|
237
|
+
switch (update.kind) {
|
|
238
|
+
case 'Joined':
|
|
239
|
+
this._OwnPeerId = update.own_peer_id;
|
|
240
|
+
break;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
225
244
|
this.onEvent(event.name, event.properties);
|
|
226
245
|
}
|
|
227
246
|
_HandleRequests(name, properties, response) {
|
|
228
247
|
switch (name) {
|
|
229
|
-
case 'SetPeerPosition':
|
|
230
|
-
// eslint-disable-next-line no-case-declarations
|
|
248
|
+
case 'SetPeerPosition': {
|
|
231
249
|
const setPeerPosition = RoomCommandsRpc.SetPeerPosition.request.safeParse(properties);
|
|
232
250
|
if (setPeerPosition.success) {
|
|
233
251
|
const value = setPeerPosition.data.position;
|
|
@@ -237,8 +255,8 @@ export class RoomConnection {
|
|
|
237
255
|
console.warn('unable to process SetPeerPosition command');
|
|
238
256
|
}
|
|
239
257
|
break;
|
|
240
|
-
|
|
241
|
-
|
|
258
|
+
}
|
|
259
|
+
case 'UpdatePeer': {
|
|
242
260
|
const updatePeer = RoomCommandsRpc.UpdatePeer.request.safeParse(properties);
|
|
243
261
|
if (updatePeer.success) {
|
|
244
262
|
this._UserData = updatePeer.data.user_data;
|
|
@@ -247,28 +265,33 @@ export class RoomConnection {
|
|
|
247
265
|
console.warn('unable to process UpdatePeer command');
|
|
248
266
|
}
|
|
249
267
|
break;
|
|
268
|
+
}
|
|
250
269
|
}
|
|
251
270
|
}
|
|
252
|
-
_RenewToken(
|
|
271
|
+
_RenewToken() {
|
|
253
272
|
return __awaiter(this, void 0, void 0, function* () {
|
|
254
273
|
this._LastTokenRenew = performance.now();
|
|
255
274
|
if (this.token.timeLeft > MinTokenLifetime)
|
|
256
275
|
return;
|
|
257
|
-
|
|
276
|
+
if (this._MainConnection === undefined || this._OwnPeerId === undefined) {
|
|
277
|
+
return failure('connection is not established');
|
|
278
|
+
}
|
|
279
|
+
const response = yield this._MainConnection.getReconnectToken(this._OwnPeerId);
|
|
258
280
|
if (response.type === 'Success') {
|
|
259
281
|
if (response.value.timeLeft > this._Token.timeLeft) {
|
|
260
282
|
this._Token = response.value;
|
|
261
283
|
}
|
|
284
|
+
return success();
|
|
262
285
|
}
|
|
263
286
|
else {
|
|
264
|
-
|
|
287
|
+
return failure(`${this._MainConnection.address} rejected reconnect token request; ${response.reason}`);
|
|
265
288
|
}
|
|
266
289
|
});
|
|
267
290
|
}
|
|
268
291
|
}
|
|
269
292
|
function contactGateway(gateway, token, signal) {
|
|
270
|
-
var _a, _b;
|
|
271
293
|
return __awaiter(this, void 0, void 0, function* () {
|
|
294
|
+
var _a, _b;
|
|
272
295
|
try {
|
|
273
296
|
const response = yield fetch(gateway, {
|
|
274
297
|
body: '{"jsonrpc":"2.0","id":1,"method":"Connect","params":{}}',
|
|
@@ -8,15 +8,17 @@ export interface RpcRequest {
|
|
|
8
8
|
timeoutId: ReturnType<typeof setTimeout> | undefined;
|
|
9
9
|
}
|
|
10
10
|
export declare class RpcConnection {
|
|
11
|
-
readonly
|
|
11
|
+
private readonly stream;
|
|
12
12
|
private readonly _SendQueue;
|
|
13
13
|
private readonly _RecvQueue;
|
|
14
|
+
private readonly _EventQueue;
|
|
14
15
|
private readonly _Requests;
|
|
15
16
|
private readonly _Close;
|
|
17
|
+
private readonly _Writer;
|
|
16
18
|
private _NextId;
|
|
17
19
|
get closed(): boolean;
|
|
18
20
|
get closeSignal(): AbortSignal;
|
|
19
|
-
constructor(
|
|
21
|
+
constructor(stream: WebSocket | WebTransportBidirectionalStream);
|
|
20
22
|
request(name: string, properties: unknown): Promise<Result<unknown> | 'closed'>;
|
|
21
23
|
request(name: string, properties: unknown, timeout: number): Promise<Result<unknown> | 'closed' | 'timeout'>;
|
|
22
24
|
notify(name: string, properties: unknown): void;
|
|
@@ -25,7 +27,7 @@ export declare class RpcConnection {
|
|
|
25
27
|
private _TimeoutRequest;
|
|
26
28
|
private _Send;
|
|
27
29
|
private _OnWebSocketOpen;
|
|
28
|
-
private
|
|
30
|
+
private _OnRpcMessage;
|
|
29
31
|
private _OnResponse;
|
|
30
32
|
private _OnEvent;
|
|
31
33
|
private _Cleanup;
|
|
@@ -37,14 +39,18 @@ export interface TypedRpcMessage<Name extends string, Properties> extends RpcMes
|
|
|
37
39
|
export type RealizedEvents<E extends Notifications> = {
|
|
38
40
|
[Name in Extract<keyof E, string>]: TypedRpcMessage<Name, Infer<E[Name]>>;
|
|
39
41
|
}[Extract<keyof E, string>];
|
|
40
|
-
export declare class TypedRpcConnection<C extends Commands, E extends Notifications>
|
|
41
|
-
readonly
|
|
42
|
-
readonly
|
|
43
|
-
|
|
42
|
+
export declare class TypedRpcConnection<C extends Commands, E extends Notifications> {
|
|
43
|
+
readonly rpcConnection: RpcConnection;
|
|
44
|
+
private readonly _Commands;
|
|
45
|
+
private readonly _Events;
|
|
46
|
+
get closed(): boolean;
|
|
47
|
+
get closeSignal(): AbortSignal;
|
|
48
|
+
constructor(rpcConnection: RpcConnection, _Commands: C, _Events: E);
|
|
44
49
|
request<Name extends Extract<keyof C, string>>(name: Name, properties: Infer<C[Name]['request']>): Promise<Result<Infer<C[Name]['response']>> | 'closed'>;
|
|
45
50
|
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
51
|
notify<Name extends Extract<keyof C, string>>(name: Name, properties: Infer<C[Name]['request']>): void;
|
|
47
52
|
recv(): Promise<RealizedEvents<E> | 'closed'>;
|
|
53
|
+
close(): void;
|
|
48
54
|
}
|
|
49
55
|
export declare function parseRpc(buffer: ArrayBuffer): Result<MessagePackRpc>;
|
|
50
56
|
export declare function parseRpcMessage<E extends Notifications>(events: E, rpc: RpcMessage): RealizedEvents<E> | undefined;
|