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
|
@@ -0,0 +1,558 @@
|
|
|
1
|
+
import { RPC, API_VERSION } from "./rpc.js";
|
|
2
|
+
import { assert, loadRequirements, randId, waitFor } from "./utils.js";
|
|
3
|
+
import { getRTCService, registerRTCService } from "./webrtc-client.js";
|
|
4
|
+
|
|
5
|
+
export { RPC, API_VERSION };
|
|
6
|
+
export { loadRequirements };
|
|
7
|
+
export { getRTCService, registerRTCService };
|
|
8
|
+
|
|
9
|
+
const MAX_RETRY = 10000;
|
|
10
|
+
|
|
11
|
+
class WebsocketRPCConnection {
|
|
12
|
+
constructor(
|
|
13
|
+
server_url,
|
|
14
|
+
client_id,
|
|
15
|
+
workspace,
|
|
16
|
+
token,
|
|
17
|
+
timeout = 60,
|
|
18
|
+
WebSocketClass = null,
|
|
19
|
+
) {
|
|
20
|
+
assert(server_url && client_id, "server_url and client_id are required");
|
|
21
|
+
this._server_url = server_url;
|
|
22
|
+
this._client_id = client_id;
|
|
23
|
+
this._workspace = workspace;
|
|
24
|
+
this._token = token;
|
|
25
|
+
this._reconnection_token = null;
|
|
26
|
+
this._websocket = null;
|
|
27
|
+
this._handle_message = null;
|
|
28
|
+
this._handle_connect = null; // Connection open event handler
|
|
29
|
+
this._disconnect_handler = null; // Disconnection event handler
|
|
30
|
+
this._timeout = timeout * 1000; // Convert seconds to milliseconds
|
|
31
|
+
this._WebSocketClass = WebSocketClass || WebSocket; // Allow overriding the WebSocket class
|
|
32
|
+
this._closing = false;
|
|
33
|
+
this._legacy_auth = null;
|
|
34
|
+
this.connection_info = null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
on_message(handler) {
|
|
38
|
+
assert(handler, "handler is required");
|
|
39
|
+
this._handle_message = handler;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
on_connect(handler) {
|
|
43
|
+
this._handle_connect = handler;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
on_disconnected(handler) {
|
|
47
|
+
this._disconnect_handler = handler;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async _attempt_connection(server_url, attempt_fallback = true) {
|
|
51
|
+
return new Promise((resolve, reject) => {
|
|
52
|
+
this._legacy_auth = false;
|
|
53
|
+
const websocket = new this._WebSocketClass(server_url);
|
|
54
|
+
websocket.binaryType = "arraybuffer";
|
|
55
|
+
|
|
56
|
+
websocket.onopen = () => {
|
|
57
|
+
console.info("WebSocket connection established");
|
|
58
|
+
resolve(websocket);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
websocket.onerror = (event) => {
|
|
62
|
+
console.error("WebSocket connection error:", event);
|
|
63
|
+
reject(event);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
websocket.onclose = (event) => {
|
|
67
|
+
if (event.code === 1003 && attempt_fallback) {
|
|
68
|
+
console.info(
|
|
69
|
+
"Received 1003 error, attempting connection with query parameters.",
|
|
70
|
+
);
|
|
71
|
+
this._attempt_connection_with_query_params(server_url)
|
|
72
|
+
.then(resolve)
|
|
73
|
+
.catch(reject);
|
|
74
|
+
} else if (this._disconnect_handler) {
|
|
75
|
+
this._disconnect_handler(this, event.reason);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async _attempt_connection_with_query_params(server_url) {
|
|
82
|
+
// Initialize an array to hold parts of the query string
|
|
83
|
+
const queryParamsParts = [];
|
|
84
|
+
|
|
85
|
+
// Conditionally add each parameter if it has a non-empty value
|
|
86
|
+
if (this._client_id)
|
|
87
|
+
queryParamsParts.push(`client_id=${encodeURIComponent(this._client_id)}`);
|
|
88
|
+
if (this._workspace)
|
|
89
|
+
queryParamsParts.push(`workspace=${encodeURIComponent(this._workspace)}`);
|
|
90
|
+
if (this._token)
|
|
91
|
+
queryParamsParts.push(`token=${encodeURIComponent(this._token)}`);
|
|
92
|
+
if (this._reconnection_token)
|
|
93
|
+
queryParamsParts.push(
|
|
94
|
+
`reconnection_token=${encodeURIComponent(this._reconnection_token)}`,
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
// Join the parts with '&' to form the final query string, prepend '?' if there are any parameters
|
|
98
|
+
const queryString =
|
|
99
|
+
queryParamsParts.length > 0 ? `?${queryParamsParts.join("&")}` : "";
|
|
100
|
+
|
|
101
|
+
// Construct the full URL by appending the query string if it exists
|
|
102
|
+
const full_url = server_url + queryString;
|
|
103
|
+
|
|
104
|
+
this._legacy_auth = true; // Assuming this flag is needed for some other logic
|
|
105
|
+
return await this._attempt_connection(full_url, false);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async open() {
|
|
109
|
+
if (this._closing || this._websocket) {
|
|
110
|
+
return; // Avoid opening a new connection if closing or already open
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
this._websocket = await this._attempt_connection(this._server_url);
|
|
114
|
+
if (!this._legacy_auth) {
|
|
115
|
+
// Send authentication info as the first message if connected without query params
|
|
116
|
+
const authInfo = JSON.stringify({
|
|
117
|
+
client_id: this._client_id,
|
|
118
|
+
workspace: this._workspace,
|
|
119
|
+
token: this._token,
|
|
120
|
+
reconnection_token: this._reconnection_token,
|
|
121
|
+
});
|
|
122
|
+
this._websocket.send(authInfo);
|
|
123
|
+
// Wait for the first message from the server
|
|
124
|
+
await waitFor(
|
|
125
|
+
new Promise((resolve, reject) => {
|
|
126
|
+
this._websocket.onmessage = (event) => {
|
|
127
|
+
const data = event.data;
|
|
128
|
+
const first_message = JSON.parse(data);
|
|
129
|
+
if (!first_message.success) {
|
|
130
|
+
const error = first_message.error || "Unknown error";
|
|
131
|
+
console.error("Failed to connect, " + error);
|
|
132
|
+
this.connection_info = null;
|
|
133
|
+
reject(new Error(error));
|
|
134
|
+
} else if (first_message) {
|
|
135
|
+
console.log(
|
|
136
|
+
"Successfully connected: " + JSON.stringify(first_message),
|
|
137
|
+
);
|
|
138
|
+
this.connection_info = first_message;
|
|
139
|
+
if(this.connection_info.reconnection_token) {
|
|
140
|
+
this._reconnection_token = this.connection_info.reconnection_token;
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
resolve();
|
|
144
|
+
};
|
|
145
|
+
}),
|
|
146
|
+
this._timeout / 1000.0,
|
|
147
|
+
"Failed to receive the first message from the server",
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
this._websocket.onmessage = (event) => {
|
|
152
|
+
this._handle_message(event.data);
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
if (this._handle_connect) {
|
|
156
|
+
await this._handle_connect(this);
|
|
157
|
+
}
|
|
158
|
+
} catch (error) {
|
|
159
|
+
console.error("Failed to connect to", this._server_url, error);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async emit_message(data) {
|
|
164
|
+
if (this._closing) {
|
|
165
|
+
throw new Error("Connection is closing");
|
|
166
|
+
}
|
|
167
|
+
if (!this._websocket || this._websocket.readyState !== WebSocket.OPEN) {
|
|
168
|
+
await this.open();
|
|
169
|
+
}
|
|
170
|
+
try {
|
|
171
|
+
this._websocket.send(data);
|
|
172
|
+
} catch (exp) {
|
|
173
|
+
console.error(`Failed to send data, error: ${exp}`);
|
|
174
|
+
throw exp;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
disconnect(reason) {
|
|
179
|
+
this._closing = true;
|
|
180
|
+
if (this._websocket && this._websocket.readyState === WebSocket.OPEN) {
|
|
181
|
+
this._websocket.close(1000, reason);
|
|
182
|
+
console.info(`WebSocket connection disconnected (${reason})`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function normalizeServerUrl(server_url) {
|
|
188
|
+
if (!server_url) throw new Error("server_url is required");
|
|
189
|
+
if (server_url.startsWith("http://")) {
|
|
190
|
+
server_url =
|
|
191
|
+
server_url.replace("http://", "ws://").replace(/\/$/, "") + "/ws";
|
|
192
|
+
} else if (server_url.startsWith("https://")) {
|
|
193
|
+
server_url =
|
|
194
|
+
server_url.replace("https://", "wss://").replace(/\/$/, "") + "/ws";
|
|
195
|
+
}
|
|
196
|
+
return server_url;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export async function login(config) {
|
|
200
|
+
const service_id = config.login_service_id || "public/*:hypha-login";
|
|
201
|
+
const timeout = config.login_timeout || 60;
|
|
202
|
+
const callback = config.login_callback;
|
|
203
|
+
|
|
204
|
+
const server = await connectToServer({
|
|
205
|
+
name: "initial login client",
|
|
206
|
+
server_url: config.server_url,
|
|
207
|
+
});
|
|
208
|
+
try {
|
|
209
|
+
const svc = await server.get_service(service_id);
|
|
210
|
+
const context = await svc.start();
|
|
211
|
+
if (callback) {
|
|
212
|
+
await callback(context);
|
|
213
|
+
} else {
|
|
214
|
+
console.log(`Please open your browser and login at ${context.login_url}`);
|
|
215
|
+
}
|
|
216
|
+
return await svc.check(context.key, timeout);
|
|
217
|
+
} catch (error) {
|
|
218
|
+
throw error;
|
|
219
|
+
} finally {
|
|
220
|
+
await server.disconnect();
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export async function connectToServer(config) {
|
|
225
|
+
if (config.server) {
|
|
226
|
+
config.server_url = config.server_url || config.server.url;
|
|
227
|
+
config.WebSocketClass =
|
|
228
|
+
config.WebSocketClass || config.server.WebSocketClass;
|
|
229
|
+
}
|
|
230
|
+
let clientId = config.client_id;
|
|
231
|
+
if (!clientId) {
|
|
232
|
+
clientId = randId();
|
|
233
|
+
config.client_id = clientId;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
let server_url = normalizeServerUrl(config.server_url);
|
|
237
|
+
|
|
238
|
+
let connection = new WebsocketRPCConnection(
|
|
239
|
+
server_url,
|
|
240
|
+
clientId,
|
|
241
|
+
config.workspace,
|
|
242
|
+
config.token,
|
|
243
|
+
config.method_timeout || 60,
|
|
244
|
+
config.WebSocketClass,
|
|
245
|
+
);
|
|
246
|
+
await connection.open();
|
|
247
|
+
assert(connection.connection_info, "Failed to connect to the server");
|
|
248
|
+
if(config.workspace && connection.connection_info.workspace !== config.workspace) {
|
|
249
|
+
throw new Error(`Connected to the wrong workspace: ${connection.connection_info.workspace}, expected: ${config.workspace}`);
|
|
250
|
+
}
|
|
251
|
+
const workspace = connection.connection_info.workspace;
|
|
252
|
+
const manager_id = connection.connection_info.manager_id;
|
|
253
|
+
const rpc = new RPC(connection, {
|
|
254
|
+
client_id: clientId,
|
|
255
|
+
workspace,
|
|
256
|
+
manager_id,
|
|
257
|
+
default_context: { connection_type: "websocket" },
|
|
258
|
+
name: config.name,
|
|
259
|
+
method_timeout: config.method_timeout,
|
|
260
|
+
app_id: config.app_id,
|
|
261
|
+
});
|
|
262
|
+
const wm = await rpc.get_manager_service();
|
|
263
|
+
wm.rpc = rpc;
|
|
264
|
+
|
|
265
|
+
async function _export(api) {
|
|
266
|
+
api.id = "default";
|
|
267
|
+
api.name = api.name || config.name || api.id;
|
|
268
|
+
api.description = api.description || config.description;
|
|
269
|
+
await rpc.register_service(api, true);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async function getPlugin(query) {
|
|
273
|
+
return await wm.get_service(query + ":default");
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
async function disconnect() {
|
|
277
|
+
await rpc.disconnect();
|
|
278
|
+
await connection.disconnect();
|
|
279
|
+
}
|
|
280
|
+
if(connection.connection_info){
|
|
281
|
+
wm.config = Object.assign(wm.config, connection.connection_info);
|
|
282
|
+
}
|
|
283
|
+
wm.export = _export;
|
|
284
|
+
wm.getPlugin = getPlugin;
|
|
285
|
+
wm.listPlugins = wm.listServices;
|
|
286
|
+
wm.disconnect = disconnect;
|
|
287
|
+
wm.registerCodec = rpc.register_codec.bind(rpc);
|
|
288
|
+
wm.emit = rpc.emit;
|
|
289
|
+
wm.on = rpc.on;
|
|
290
|
+
if (rpc.manager_id) {
|
|
291
|
+
rpc.on("force-exit", async (message) => {
|
|
292
|
+
if (message.from === "*/" + rpc.manager_id) {
|
|
293
|
+
console.log("Disconnecting from server, reason:", message.reason);
|
|
294
|
+
await disconnect();
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
if (config.webrtc) {
|
|
299
|
+
await registerRTCService(wm, clientId + "-rtc", config.webrtc_config);
|
|
300
|
+
}
|
|
301
|
+
if (wm.get_service || wm.getService) {
|
|
302
|
+
const _get_service = wm.get_service || wm.getService;
|
|
303
|
+
wm.get_service = async function (query, webrtc, webrtc_config) {
|
|
304
|
+
assert(
|
|
305
|
+
[undefined, true, false, "auto"].includes(webrtc),
|
|
306
|
+
"webrtc must be true, false or 'auto'",
|
|
307
|
+
);
|
|
308
|
+
const svc = await _get_service(query);
|
|
309
|
+
if (webrtc === true || webrtc === "auto") {
|
|
310
|
+
if (svc.id.includes(":") && svc.id.includes("/")) {
|
|
311
|
+
const client = svc.id.split(":")[0];
|
|
312
|
+
try {
|
|
313
|
+
// Assuming that the client registered a webrtc service with the client_id + "-rtc"
|
|
314
|
+
const peer = await getRTCService(
|
|
315
|
+
wm,
|
|
316
|
+
client + ":" + client.split("/")[1] + "-rtc",
|
|
317
|
+
webrtc_config,
|
|
318
|
+
);
|
|
319
|
+
const rtcSvc = await peer.get_service(svc.id.split(":")[1]);
|
|
320
|
+
rtcSvc._webrtc = true;
|
|
321
|
+
rtcSvc._peer = peer;
|
|
322
|
+
rtcSvc._service = svc;
|
|
323
|
+
return rtcSvc;
|
|
324
|
+
} catch (e) {
|
|
325
|
+
console.warn(
|
|
326
|
+
"Failed to get webrtc service, using websocket connection",
|
|
327
|
+
e,
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
if (webrtc === true) {
|
|
332
|
+
throw new Error("Failed to get the service via webrtc");
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return svc;
|
|
336
|
+
};
|
|
337
|
+
wm.getService = wm.get_service;
|
|
338
|
+
}
|
|
339
|
+
return wm;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
class LocalWebSocket {
|
|
343
|
+
constructor(url, client_id, workspace) {
|
|
344
|
+
this.url = url;
|
|
345
|
+
this.onopen = () => {};
|
|
346
|
+
this.onmessage = () => {};
|
|
347
|
+
this.onclose = () => {};
|
|
348
|
+
this.onerror = () => {};
|
|
349
|
+
this.client_id = client_id;
|
|
350
|
+
this.workspace = workspace;
|
|
351
|
+
const context = typeof window !== "undefined" ? window : self;
|
|
352
|
+
const isWindow = typeof window !== "undefined";
|
|
353
|
+
this.postMessage = (message) => {
|
|
354
|
+
if (isWindow) {
|
|
355
|
+
window.parent.postMessage(message, "*");
|
|
356
|
+
} else {
|
|
357
|
+
self.postMessage(message);
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
this.readyState = WebSocket.CONNECTING;
|
|
362
|
+
context.addEventListener(
|
|
363
|
+
"message",
|
|
364
|
+
(event) => {
|
|
365
|
+
const { type, data, to } = event.data;
|
|
366
|
+
if (to !== this.client_id) {
|
|
367
|
+
console.debug("message not for me", to, this.client_id);
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
switch (type) {
|
|
371
|
+
case "message":
|
|
372
|
+
if (this.readyState === WebSocket.OPEN && this.onmessage) {
|
|
373
|
+
this.onmessage({ data: data });
|
|
374
|
+
}
|
|
375
|
+
break;
|
|
376
|
+
case "connected":
|
|
377
|
+
this.readyState = WebSocket.OPEN;
|
|
378
|
+
this.onopen(event);
|
|
379
|
+
break;
|
|
380
|
+
case "closed":
|
|
381
|
+
this.readyState = WebSocket.CLOSED;
|
|
382
|
+
this.onclose(event);
|
|
383
|
+
break;
|
|
384
|
+
default:
|
|
385
|
+
break;
|
|
386
|
+
}
|
|
387
|
+
},
|
|
388
|
+
false,
|
|
389
|
+
);
|
|
390
|
+
|
|
391
|
+
if (!this.client_id) throw new Error("client_id is required");
|
|
392
|
+
if (!this.workspace) throw new Error("workspace is required");
|
|
393
|
+
this.postMessage({
|
|
394
|
+
type: "connect",
|
|
395
|
+
url: this.url,
|
|
396
|
+
from: this.client_id,
|
|
397
|
+
workspace: this.workspace,
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
send(data) {
|
|
402
|
+
if (this.readyState === WebSocket.OPEN) {
|
|
403
|
+
this.postMessage({
|
|
404
|
+
type: "message",
|
|
405
|
+
data: data,
|
|
406
|
+
from: this.client_id,
|
|
407
|
+
workspace: this.workspace,
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
close() {
|
|
413
|
+
this.readyState = WebSocket.CLOSING;
|
|
414
|
+
this.postMessage({
|
|
415
|
+
type: "close",
|
|
416
|
+
from: this.client_id,
|
|
417
|
+
workspace: this.workspace,
|
|
418
|
+
});
|
|
419
|
+
this.onclose();
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
addEventListener(type, listener) {
|
|
423
|
+
if (type === "message") {
|
|
424
|
+
this.onmessage = listener;
|
|
425
|
+
}
|
|
426
|
+
if (type === "open") {
|
|
427
|
+
this.onopen = listener;
|
|
428
|
+
}
|
|
429
|
+
if (type === "close") {
|
|
430
|
+
this.onclose = listener;
|
|
431
|
+
}
|
|
432
|
+
if (type === "error") {
|
|
433
|
+
this.onerror = listener;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
export function setupLocalClient({
|
|
439
|
+
enable_execution = false,
|
|
440
|
+
on_ready = null,
|
|
441
|
+
}) {
|
|
442
|
+
return new Promise((resolve, reject) => {
|
|
443
|
+
const context = typeof window !== "undefined" ? window : self;
|
|
444
|
+
const isWindow = typeof window !== "undefined";
|
|
445
|
+
context.addEventListener(
|
|
446
|
+
"message",
|
|
447
|
+
(event) => {
|
|
448
|
+
const {
|
|
449
|
+
type,
|
|
450
|
+
server_url,
|
|
451
|
+
workspace,
|
|
452
|
+
client_id,
|
|
453
|
+
token,
|
|
454
|
+
method_timeout,
|
|
455
|
+
name,
|
|
456
|
+
config,
|
|
457
|
+
} = event.data;
|
|
458
|
+
|
|
459
|
+
if (type === "initializeHyphaClient") {
|
|
460
|
+
if (!server_url || !workspace || !client_id) {
|
|
461
|
+
console.error("server_url, workspace, and client_id are required.");
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
if (!server_url.startsWith("https://local-hypha-server:")) {
|
|
466
|
+
console.error(
|
|
467
|
+
"server_url should start with https://local-hypha-server:",
|
|
468
|
+
);
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
connectToServer({
|
|
473
|
+
server_url,
|
|
474
|
+
workspace,
|
|
475
|
+
client_id,
|
|
476
|
+
token,
|
|
477
|
+
method_timeout,
|
|
478
|
+
name,
|
|
479
|
+
}).then(async (server) => {
|
|
480
|
+
globalThis.api = server;
|
|
481
|
+
try {
|
|
482
|
+
// for iframe
|
|
483
|
+
if (isWindow && enable_execution) {
|
|
484
|
+
function loadScript(script) {
|
|
485
|
+
return new Promise((resolve, reject) => {
|
|
486
|
+
const scriptElement = document.createElement("script");
|
|
487
|
+
scriptElement.innerHTML = script.content;
|
|
488
|
+
scriptElement.lang = script.lang;
|
|
489
|
+
|
|
490
|
+
scriptElement.onload = () => resolve();
|
|
491
|
+
scriptElement.onerror = (e) => reject(e);
|
|
492
|
+
|
|
493
|
+
document.head.appendChild(scriptElement);
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
if (config.styles && config.styles.length > 0) {
|
|
497
|
+
for (const style of config.styles) {
|
|
498
|
+
const styleElement = document.createElement("style");
|
|
499
|
+
styleElement.innerHTML = style.content;
|
|
500
|
+
styleElement.lang = style.lang;
|
|
501
|
+
document.head.appendChild(styleElement);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
if (config.links && config.links.length > 0) {
|
|
505
|
+
for (const link of config.links) {
|
|
506
|
+
const linkElement = document.createElement("a");
|
|
507
|
+
linkElement.href = link.url;
|
|
508
|
+
linkElement.innerText = link.text;
|
|
509
|
+
document.body.appendChild(linkElement);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
if (config.windows && config.windows.length > 0) {
|
|
513
|
+
for (const w of config.windows) {
|
|
514
|
+
document.body.innerHTML = w.content;
|
|
515
|
+
break;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
if (config.scripts && config.scripts.length > 0) {
|
|
519
|
+
for (const script of config.scripts) {
|
|
520
|
+
if (script.lang !== "javascript")
|
|
521
|
+
throw new Error("Only javascript scripts are supported");
|
|
522
|
+
await loadScript(script); // Await the loading of each script
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
// for web worker
|
|
527
|
+
else if (
|
|
528
|
+
!isWindow &&
|
|
529
|
+
enable_execution &&
|
|
530
|
+
config.scripts &&
|
|
531
|
+
config.scripts.length > 0
|
|
532
|
+
) {
|
|
533
|
+
for (const script of config.scripts) {
|
|
534
|
+
if (script.lang !== "javascript")
|
|
535
|
+
throw new Error("Only javascript scripts are supported");
|
|
536
|
+
eval(script.content);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
if (on_ready) {
|
|
541
|
+
await on_ready(server, config);
|
|
542
|
+
}
|
|
543
|
+
resolve(server);
|
|
544
|
+
} catch (e) {
|
|
545
|
+
reject(e);
|
|
546
|
+
}
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
},
|
|
550
|
+
false,
|
|
551
|
+
);
|
|
552
|
+
if (isWindow) {
|
|
553
|
+
window.parent.postMessage({ type: "hyphaClientReady" }, "*");
|
|
554
|
+
} else {
|
|
555
|
+
self.postMessage({ type: "hyphaClientReady" });
|
|
556
|
+
}
|
|
557
|
+
});
|
|
558
|
+
}
|