hypha-rpc 0.1.0-post5
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/.eslintrc +4 -0
- package/.prettierignore +3 -0
- package/LICENSE +21 -0
- package/README.md +27 -0
- package/dist/hypha-rpc-websocket.js +4688 -0
- package/dist/hypha-rpc-websocket.js.map +1 -0
- package/dist/hypha-rpc-websocket.min.js +2 -0
- package/dist/hypha-rpc-websocket.min.js.map +1 -0
- package/index.d.ts +96 -0
- package/index.js +1 -0
- package/karma.conf.js +114 -0
- package/package.json +65 -0
- package/report.html +39 -0
- package/src/rpc.js +1555 -0
- package/src/utils.js +331 -0
- package/src/webrtc-client.js +201 -0
- package/src/websocket-client.js +558 -0
- package/tests/.eslintrc.js +8 -0
- package/tests/websocket_client_test.js +203 -0
- package/webpack.config.js +44 -0
package/src/utils.js
ADDED
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
export function randId() {
|
|
2
|
+
return Math.random().toString(36).substr(2, 10) + new Date().getTime();
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export const dtypeToTypedArray = {
|
|
6
|
+
int8: Int8Array,
|
|
7
|
+
int16: Int16Array,
|
|
8
|
+
int32: Int32Array,
|
|
9
|
+
uint8: Uint8Array,
|
|
10
|
+
uint16: Uint16Array,
|
|
11
|
+
uint32: Uint32Array,
|
|
12
|
+
float32: Float32Array,
|
|
13
|
+
float64: Float64Array,
|
|
14
|
+
array: Array,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export async function loadRequirementsInWindow(requirements) {
|
|
18
|
+
function _importScript(url) {
|
|
19
|
+
//url is URL of external file, implementationCode is the code
|
|
20
|
+
//to be called from the file, location is the location to
|
|
21
|
+
//insert the <script> element
|
|
22
|
+
return new Promise((resolve, reject) => {
|
|
23
|
+
var scriptTag = document.createElement("script");
|
|
24
|
+
scriptTag.src = url;
|
|
25
|
+
scriptTag.type = "text/javascript";
|
|
26
|
+
scriptTag.onload = resolve;
|
|
27
|
+
scriptTag.onreadystatechange = function () {
|
|
28
|
+
if (this.readyState === "loaded" || this.readyState === "complete") {
|
|
29
|
+
resolve();
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
scriptTag.onerror = reject;
|
|
33
|
+
document.head.appendChild(scriptTag);
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// support importScripts outside web worker
|
|
38
|
+
async function importScripts() {
|
|
39
|
+
var args = Array.prototype.slice.call(arguments),
|
|
40
|
+
len = args.length,
|
|
41
|
+
i = 0;
|
|
42
|
+
for (; i < len; i++) {
|
|
43
|
+
await _importScript(args[i]);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (
|
|
48
|
+
requirements &&
|
|
49
|
+
(Array.isArray(requirements) || typeof requirements === "string")
|
|
50
|
+
) {
|
|
51
|
+
try {
|
|
52
|
+
var link_node;
|
|
53
|
+
requirements =
|
|
54
|
+
typeof requirements === "string" ? [requirements] : requirements;
|
|
55
|
+
if (Array.isArray(requirements)) {
|
|
56
|
+
for (var i = 0; i < requirements.length; i++) {
|
|
57
|
+
if (
|
|
58
|
+
requirements[i].toLowerCase().endsWith(".css") ||
|
|
59
|
+
requirements[i].startsWith("css:")
|
|
60
|
+
) {
|
|
61
|
+
if (requirements[i].startsWith("css:")) {
|
|
62
|
+
requirements[i] = requirements[i].slice(4);
|
|
63
|
+
}
|
|
64
|
+
link_node = document.createElement("link");
|
|
65
|
+
link_node.rel = "stylesheet";
|
|
66
|
+
link_node.href = requirements[i];
|
|
67
|
+
document.head.appendChild(link_node);
|
|
68
|
+
} else if (
|
|
69
|
+
requirements[i].toLowerCase().endsWith(".mjs") ||
|
|
70
|
+
requirements[i].startsWith("mjs:")
|
|
71
|
+
) {
|
|
72
|
+
// import esmodule
|
|
73
|
+
if (requirements[i].startsWith("mjs:")) {
|
|
74
|
+
requirements[i] = requirements[i].slice(4);
|
|
75
|
+
}
|
|
76
|
+
await import(/* webpackIgnore: true */ requirements[i]);
|
|
77
|
+
} else if (
|
|
78
|
+
requirements[i].toLowerCase().endsWith(".js") ||
|
|
79
|
+
requirements[i].startsWith("js:")
|
|
80
|
+
) {
|
|
81
|
+
if (requirements[i].startsWith("js:")) {
|
|
82
|
+
requirements[i] = requirements[i].slice(3);
|
|
83
|
+
}
|
|
84
|
+
await importScripts(requirements[i]);
|
|
85
|
+
} else if (requirements[i].startsWith("http")) {
|
|
86
|
+
await importScripts(requirements[i]);
|
|
87
|
+
} else if (requirements[i].startsWith("cache:")) {
|
|
88
|
+
//ignore cache
|
|
89
|
+
} else {
|
|
90
|
+
console.log("Unprocessed requirements url: " + requirements[i]);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
} else {
|
|
94
|
+
throw "unsupported requirements definition";
|
|
95
|
+
}
|
|
96
|
+
} catch (e) {
|
|
97
|
+
throw "failed to import required scripts: " + requirements.toString();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export async function loadRequirementsInWebworker(requirements) {
|
|
103
|
+
if (
|
|
104
|
+
requirements &&
|
|
105
|
+
(Array.isArray(requirements) || typeof requirements === "string")
|
|
106
|
+
) {
|
|
107
|
+
try {
|
|
108
|
+
if (!Array.isArray(requirements)) {
|
|
109
|
+
requirements = [requirements];
|
|
110
|
+
}
|
|
111
|
+
for (var i = 0; i < requirements.length; i++) {
|
|
112
|
+
if (
|
|
113
|
+
requirements[i].toLowerCase().endsWith(".css") ||
|
|
114
|
+
requirements[i].startsWith("css:")
|
|
115
|
+
) {
|
|
116
|
+
throw "unable to import css in a webworker";
|
|
117
|
+
} else if (
|
|
118
|
+
requirements[i].toLowerCase().endsWith(".js") ||
|
|
119
|
+
requirements[i].startsWith("js:")
|
|
120
|
+
) {
|
|
121
|
+
if (requirements[i].startsWith("js:")) {
|
|
122
|
+
requirements[i] = requirements[i].slice(3);
|
|
123
|
+
}
|
|
124
|
+
importScripts(requirements[i]);
|
|
125
|
+
} else if (requirements[i].startsWith("http")) {
|
|
126
|
+
importScripts(requirements[i]);
|
|
127
|
+
} else if (requirements[i].startsWith("cache:")) {
|
|
128
|
+
//ignore cache
|
|
129
|
+
} else {
|
|
130
|
+
console.log("Unprocessed requirements url: " + requirements[i]);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
} catch (e) {
|
|
134
|
+
throw "failed to import required scripts: " + requirements.toString();
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function loadRequirements(requirements) {
|
|
140
|
+
if (
|
|
141
|
+
typeof WorkerGlobalScope !== "undefined" &&
|
|
142
|
+
self instanceof WorkerGlobalScope
|
|
143
|
+
) {
|
|
144
|
+
return loadRequirementsInWebworker(requirements);
|
|
145
|
+
} else {
|
|
146
|
+
return loadRequirementsInWindow(requirements);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function normalizeConfig(config) {
|
|
151
|
+
config.version = config.version || "0.1.0";
|
|
152
|
+
config.description =
|
|
153
|
+
config.description || `[TODO: add description for ${config.name} ]`;
|
|
154
|
+
config.type = config.type || "rpc-window";
|
|
155
|
+
config.id = config.id || randId();
|
|
156
|
+
config.target_origin = config.target_origin || "*";
|
|
157
|
+
config.allow_execution = config.allow_execution || false;
|
|
158
|
+
// remove functions
|
|
159
|
+
config = Object.keys(config).reduce((p, c) => {
|
|
160
|
+
if (typeof config[c] !== "function") p[c] = config[c];
|
|
161
|
+
return p;
|
|
162
|
+
}, {});
|
|
163
|
+
return config;
|
|
164
|
+
}
|
|
165
|
+
export const typedArrayToDtypeMapping = {
|
|
166
|
+
Int8Array: "int8",
|
|
167
|
+
Int16Array: "int16",
|
|
168
|
+
Int32Array: "int32",
|
|
169
|
+
Uint8Array: "uint8",
|
|
170
|
+
Uint16Array: "uint16",
|
|
171
|
+
Uint32Array: "uint32",
|
|
172
|
+
Float32Array: "float32",
|
|
173
|
+
Float64Array: "float64",
|
|
174
|
+
Array: "array",
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const typedArrayToDtypeKeys = [];
|
|
178
|
+
for (const arrType of Object.keys(typedArrayToDtypeMapping)) {
|
|
179
|
+
typedArrayToDtypeKeys.push(eval(arrType));
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function typedArrayToDtype(obj) {
|
|
183
|
+
let dtype = typedArrayToDtypeMapping[obj.constructor.name];
|
|
184
|
+
if (!dtype) {
|
|
185
|
+
const pt = Object.getPrototypeOf(obj);
|
|
186
|
+
for (const arrType of typedArrayToDtypeKeys) {
|
|
187
|
+
if (pt instanceof arrType) {
|
|
188
|
+
dtype = typedArrayToDtypeMapping[arrType.name];
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return dtype;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function cacheUrlInServiceWorker(url) {
|
|
197
|
+
return new Promise(function (resolve, reject) {
|
|
198
|
+
const message = {
|
|
199
|
+
command: "add",
|
|
200
|
+
url: url,
|
|
201
|
+
};
|
|
202
|
+
if (!navigator.serviceWorker || !navigator.serviceWorker.register) {
|
|
203
|
+
reject("Service worker is not supported.");
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
const messageChannel = new MessageChannel();
|
|
207
|
+
messageChannel.port1.onmessage = function (event) {
|
|
208
|
+
if (event.data && event.data.error) {
|
|
209
|
+
reject(event.data.error);
|
|
210
|
+
} else {
|
|
211
|
+
resolve(event.data && event.data.result);
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
if (navigator.serviceWorker && navigator.serviceWorker.controller) {
|
|
216
|
+
navigator.serviceWorker.controller.postMessage(message, [
|
|
217
|
+
messageChannel.port2,
|
|
218
|
+
]);
|
|
219
|
+
} else {
|
|
220
|
+
reject("Service worker controller is not available");
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export async function cacheRequirements(requirements) {
|
|
226
|
+
requirements = requirements || [];
|
|
227
|
+
if (!Array.isArray(requirements)) {
|
|
228
|
+
requirements = [requirements];
|
|
229
|
+
}
|
|
230
|
+
for (let req of requirements) {
|
|
231
|
+
//remove prefix
|
|
232
|
+
if (req.startsWith("js:")) req = req.slice(3);
|
|
233
|
+
if (req.startsWith("css:")) req = req.slice(4);
|
|
234
|
+
if (req.startsWith("cache:")) req = req.slice(6);
|
|
235
|
+
if (!req.startsWith("http")) continue;
|
|
236
|
+
|
|
237
|
+
await cacheUrlInServiceWorker(req).catch((e) => {
|
|
238
|
+
console.error(e);
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export function assert(condition, message) {
|
|
244
|
+
if (!condition) {
|
|
245
|
+
throw new Error(message || "Assertion failed");
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
//#Source https://bit.ly/2neWfJ2
|
|
250
|
+
export function urlJoin(...args) {
|
|
251
|
+
return args
|
|
252
|
+
.join("/")
|
|
253
|
+
.replace(/[\/]+/g, "/")
|
|
254
|
+
.replace(/^(.+):\//, "$1://")
|
|
255
|
+
.replace(/^file:/, "file:/")
|
|
256
|
+
.replace(/\/(\?|&|#[^!])/g, "$1")
|
|
257
|
+
.replace(/\?/g, "&")
|
|
258
|
+
.replace("&", "?");
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export function waitFor(prom, time, error) {
|
|
262
|
+
let timer;
|
|
263
|
+
return Promise.race([
|
|
264
|
+
prom,
|
|
265
|
+
new Promise(
|
|
266
|
+
(_r, rej) =>
|
|
267
|
+
(timer = setTimeout(() => {
|
|
268
|
+
rej(error || "Timeout Error");
|
|
269
|
+
}, time * 1000)),
|
|
270
|
+
),
|
|
271
|
+
]).finally(() => clearTimeout(timer));
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export class MessageEmitter {
|
|
275
|
+
constructor(debug) {
|
|
276
|
+
this._event_handlers = {};
|
|
277
|
+
this._once_handlers = {};
|
|
278
|
+
this._debug = debug;
|
|
279
|
+
}
|
|
280
|
+
emit() {
|
|
281
|
+
throw new Error("emit is not implemented");
|
|
282
|
+
}
|
|
283
|
+
on(event, handler) {
|
|
284
|
+
if (!this._event_handlers[event]) {
|
|
285
|
+
this._event_handlers[event] = [];
|
|
286
|
+
}
|
|
287
|
+
this._event_handlers[event].push(handler);
|
|
288
|
+
}
|
|
289
|
+
once(event, handler) {
|
|
290
|
+
handler.___event_run_once = true;
|
|
291
|
+
this.on(event, handler);
|
|
292
|
+
}
|
|
293
|
+
off(event, handler) {
|
|
294
|
+
if (!event && !handler) {
|
|
295
|
+
// remove all events handlers
|
|
296
|
+
this._event_handlers = {};
|
|
297
|
+
} else if (event && !handler) {
|
|
298
|
+
// remove all hanlders for the event
|
|
299
|
+
if (this._event_handlers[event]) this._event_handlers[event] = [];
|
|
300
|
+
} else {
|
|
301
|
+
// remove a specific handler
|
|
302
|
+
if (this._event_handlers[event]) {
|
|
303
|
+
const idx = this._event_handlers[event].indexOf(handler);
|
|
304
|
+
if (idx >= 0) {
|
|
305
|
+
this._event_handlers[event].splice(idx, 1);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
_fire(event, data) {
|
|
311
|
+
if (this._event_handlers[event]) {
|
|
312
|
+
var i = this._event_handlers[event].length;
|
|
313
|
+
while (i--) {
|
|
314
|
+
const handler = this._event_handlers[event][i];
|
|
315
|
+
try {
|
|
316
|
+
handler(data);
|
|
317
|
+
} catch (e) {
|
|
318
|
+
console.error(e);
|
|
319
|
+
} finally {
|
|
320
|
+
if (handler.___event_run_once) {
|
|
321
|
+
this._event_handlers[event].splice(i, 1);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
} else {
|
|
326
|
+
if (this._debug) {
|
|
327
|
+
console.warn("unhandled event", event, data);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { RPC } from "./rpc.js";
|
|
2
|
+
import { assert, randId } from "./utils.js";
|
|
3
|
+
|
|
4
|
+
class WebRTCConnection {
|
|
5
|
+
constructor(channel) {
|
|
6
|
+
this._data_channel = channel;
|
|
7
|
+
this._handle_message = null;
|
|
8
|
+
this._reconnection_token = null;
|
|
9
|
+
this._data_channel.onmessage = async (event) => {
|
|
10
|
+
let data = event.data;
|
|
11
|
+
if (data instanceof Blob) {
|
|
12
|
+
data = await data.arrayBuffer();
|
|
13
|
+
}
|
|
14
|
+
this._handle_message(data);
|
|
15
|
+
};
|
|
16
|
+
const self = this;
|
|
17
|
+
this._data_channel.onclose = function () {
|
|
18
|
+
console.log("websocket closed");
|
|
19
|
+
self._data_channel = null;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
on_message(handler) {
|
|
24
|
+
assert(handler, "handler is required");
|
|
25
|
+
this._handle_message = handler;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async emit_message(data) {
|
|
29
|
+
assert(this._handle_message, "No handler for message");
|
|
30
|
+
try {
|
|
31
|
+
this._data_channel.send(data);
|
|
32
|
+
} catch (exp) {
|
|
33
|
+
// data = msgpack_unpackb(data);
|
|
34
|
+
console.error(`Failed to send data, error: ${exp}`);
|
|
35
|
+
throw exp;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async disconnect(reason) {
|
|
40
|
+
this._data_channel = null;
|
|
41
|
+
console.info(`data channel connection disconnected (${reason})`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function _setupRPC(config) {
|
|
46
|
+
assert(config.channel, "No channel provided");
|
|
47
|
+
assert(config.workspace, "No workspace provided");
|
|
48
|
+
const channel = config.channel;
|
|
49
|
+
const clientId = config.client_id || randId();
|
|
50
|
+
const connection = new WebRTCConnection(channel);
|
|
51
|
+
config.context = config.context || {};
|
|
52
|
+
config.context.connection_type = "webrtc";
|
|
53
|
+
const rpc = new RPC(connection, {
|
|
54
|
+
client_id: clientId,
|
|
55
|
+
manager_id: null,
|
|
56
|
+
default_context: config.context,
|
|
57
|
+
name: config.name,
|
|
58
|
+
method_timeout: config.method_timeout || 10.0,
|
|
59
|
+
workspace: config.workspace,
|
|
60
|
+
});
|
|
61
|
+
return rpc;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function _createOffer(params, server, config, onInit, context) {
|
|
65
|
+
config = config || {};
|
|
66
|
+
let offer = new RTCSessionDescription({
|
|
67
|
+
sdp: params.sdp,
|
|
68
|
+
type: params.type,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
let pc = new RTCPeerConnection({
|
|
72
|
+
iceServers: config.ice_servers || [
|
|
73
|
+
{ urls: ["stun:stun.l.google.com:19302"] },
|
|
74
|
+
],
|
|
75
|
+
sdpSemantics: "unified-plan",
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
if (server) {
|
|
79
|
+
pc.addEventListener("datachannel", async (event) => {
|
|
80
|
+
const channel = event.channel;
|
|
81
|
+
let ctx = null;
|
|
82
|
+
if (context && context.user) ctx = { user: context.user };
|
|
83
|
+
const rpc = await _setupRPC({
|
|
84
|
+
channel: channel,
|
|
85
|
+
client_id: channel.label,
|
|
86
|
+
workspace: server.config.workspace,
|
|
87
|
+
context: ctx,
|
|
88
|
+
});
|
|
89
|
+
// Map all the local services to the webrtc client
|
|
90
|
+
rpc._services = server.rpc._services;
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (onInit) {
|
|
95
|
+
await onInit(pc);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
await pc.setRemoteDescription(offer);
|
|
99
|
+
|
|
100
|
+
let answer = await pc.createAnswer();
|
|
101
|
+
await pc.setLocalDescription(answer);
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
sdp: pc.localDescription.sdp,
|
|
105
|
+
type: pc.localDescription.type,
|
|
106
|
+
workspace: server.config.workspace,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function getRTCService(server, service_id, config) {
|
|
111
|
+
config = config || {};
|
|
112
|
+
config.peer_id = config.peer_id || randId();
|
|
113
|
+
|
|
114
|
+
const pc = new RTCPeerConnection({
|
|
115
|
+
iceServers: config.ice_servers || [
|
|
116
|
+
{ urls: ["stun:stun.l.google.com:19302"] },
|
|
117
|
+
],
|
|
118
|
+
sdpSemantics: "unified-plan",
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
return new Promise(async (resolve, reject) => {
|
|
122
|
+
try {
|
|
123
|
+
pc.addEventListener(
|
|
124
|
+
"connectionstatechange",
|
|
125
|
+
() => {
|
|
126
|
+
if (pc.connectionState === "failed") {
|
|
127
|
+
pc.close();
|
|
128
|
+
reject(new Error("Connection failed"));
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
false,
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
if (config.on_init) {
|
|
135
|
+
await config.on_init(pc);
|
|
136
|
+
delete config.on_init;
|
|
137
|
+
}
|
|
138
|
+
let channel = pc.createDataChannel(config.peer_id, { ordered: true });
|
|
139
|
+
channel.binaryType = "arraybuffer";
|
|
140
|
+
const offer = await pc.createOffer();
|
|
141
|
+
await pc.setLocalDescription(offer);
|
|
142
|
+
const svc = await server.getService(service_id);
|
|
143
|
+
const answer = await svc.offer({
|
|
144
|
+
sdp: pc.localDescription.sdp,
|
|
145
|
+
type: pc.localDescription.type,
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
channel.onopen = () => {
|
|
149
|
+
config.channel = channel;
|
|
150
|
+
config.workspace = answer.workspace;
|
|
151
|
+
// Wait for the channel to be open before returning the rpc
|
|
152
|
+
// This is needed for safari to work
|
|
153
|
+
setTimeout(async () => {
|
|
154
|
+
const rpc = await _setupRPC(config);
|
|
155
|
+
pc.rpc = rpc;
|
|
156
|
+
async function getService(name) {
|
|
157
|
+
return await rpc.get_remote_service(config.peer_id + ":" + name);
|
|
158
|
+
}
|
|
159
|
+
async function disconnect() {
|
|
160
|
+
await rpc.disconnect();
|
|
161
|
+
pc.close();
|
|
162
|
+
}
|
|
163
|
+
pc.get_service = getService;
|
|
164
|
+
pc.getService = getService;
|
|
165
|
+
pc.disconnect = disconnect;
|
|
166
|
+
pc.register_codec = rpc.register_codec;
|
|
167
|
+
pc.registerCodec = rpc.register_codec;
|
|
168
|
+
resolve(pc);
|
|
169
|
+
}, 500);
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
channel.onclose = () => reject(new Error("Data channel closed"));
|
|
173
|
+
|
|
174
|
+
await pc.setRemoteDescription(
|
|
175
|
+
new RTCSessionDescription({
|
|
176
|
+
sdp: answer.sdp,
|
|
177
|
+
type: answer.type,
|
|
178
|
+
}),
|
|
179
|
+
);
|
|
180
|
+
} catch (e) {
|
|
181
|
+
reject(e);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async function registerRTCService(server, service_id, config) {
|
|
187
|
+
config = config || {
|
|
188
|
+
visibility: "protected",
|
|
189
|
+
require_context: true,
|
|
190
|
+
};
|
|
191
|
+
const onInit = config.on_init;
|
|
192
|
+
delete config.on_init;
|
|
193
|
+
await server.registerService({
|
|
194
|
+
id: service_id,
|
|
195
|
+
config,
|
|
196
|
+
offer: (params, context) =>
|
|
197
|
+
_createOffer(params, server, config, onInit, context),
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export { getRTCService, registerRTCService };
|