hypha-rpc 0.20.57 → 0.20.59
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/dist/hypha-rpc-websocket.js +267 -104
- package/dist/hypha-rpc-websocket.js.map +1 -1
- package/dist/hypha-rpc-websocket.min.js +1 -1
- package/dist/hypha-rpc-websocket.min.js.map +1 -1
- package/dist/hypha-rpc-websocket.min.mjs +1 -1
- package/dist/hypha-rpc-websocket.min.mjs.map +1 -1
- package/dist/hypha-rpc-websocket.mjs +267 -104
- package/dist/hypha-rpc-websocket.mjs.map +1 -1
- package/package.json +1 -1
- package/src/rpc.js +105 -36
- package/src/webrtc-client.js +162 -68
|
@@ -941,8 +941,8 @@ class RPC extends _utils__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
|
|
|
941
941
|
timer.clear();
|
|
942
942
|
}
|
|
943
943
|
if (clear_after_called && self._object_store[session_id]) {
|
|
944
|
-
//
|
|
945
|
-
|
|
944
|
+
// Simple cleanup - let the session manager handle the logic
|
|
945
|
+
self._cleanup_session_if_needed(session_id, name);
|
|
946
946
|
}
|
|
947
947
|
}
|
|
948
948
|
};
|
|
@@ -950,6 +950,47 @@ class RPC extends _utils__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
|
|
|
950
950
|
return [encoded, wrapped_callback];
|
|
951
951
|
}
|
|
952
952
|
|
|
953
|
+
// Clean session management - all logic in one place
|
|
954
|
+
_cleanup_session_if_needed(session_id, callback_name) {
|
|
955
|
+
const store = this._get_session_store(session_id, false);
|
|
956
|
+
if (!store) return;
|
|
957
|
+
|
|
958
|
+
// Promise sessions: let the promise manager decide cleanup
|
|
959
|
+
if (store._promise_manager) {
|
|
960
|
+
if (store._promise_manager.should_cleanup_on_callback(callback_name)) {
|
|
961
|
+
store._promise_manager.settle();
|
|
962
|
+
delete this._object_store[session_id];
|
|
963
|
+
}
|
|
964
|
+
return;
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
// Regular sessions: cleanup immediately
|
|
968
|
+
delete this._object_store[session_id];
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
// Clean helper to identify promise method calls by session type
|
|
972
|
+
_is_promise_method_call(method_path) {
|
|
973
|
+
const session_id = method_path.split(".")[0];
|
|
974
|
+
const session = this._get_session_store(session_id, false);
|
|
975
|
+
return session && session._promise_manager;
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
// Clean Promise Manager - encapsulates all promise lifecycle logic
|
|
979
|
+
_create_promise_manager() {
|
|
980
|
+
return {
|
|
981
|
+
settled: false,
|
|
982
|
+
settle() {
|
|
983
|
+
this.settled = true;
|
|
984
|
+
},
|
|
985
|
+
is_settled() {
|
|
986
|
+
return this.settled;
|
|
987
|
+
},
|
|
988
|
+
should_cleanup_on_callback(callback_name) {
|
|
989
|
+
return callback_name === "resolve" || callback_name === "reject";
|
|
990
|
+
},
|
|
991
|
+
};
|
|
992
|
+
}
|
|
993
|
+
|
|
953
994
|
async _encode_promise(
|
|
954
995
|
resolve,
|
|
955
996
|
reject,
|
|
@@ -960,12 +1001,17 @@ class RPC extends _utils__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
|
|
|
960
1001
|
description,
|
|
961
1002
|
) {
|
|
962
1003
|
let store = this._get_session_store(session_id, true);
|
|
963
|
-
(
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
1004
|
+
if (!store) {
|
|
1005
|
+
console.warn(
|
|
1006
|
+
`Failed to create session store ${session_id}, session management may be impaired`,
|
|
1007
|
+
);
|
|
1008
|
+
store = {};
|
|
1009
|
+
}
|
|
967
1010
|
let encoded = {};
|
|
968
1011
|
|
|
1012
|
+
// Clean promise lifecycle management
|
|
1013
|
+
store._promise_manager = this._create_promise_manager();
|
|
1014
|
+
|
|
969
1015
|
if (timer && reject && this._method_timeout) {
|
|
970
1016
|
[encoded.heartbeat, store.heartbeat] = this._encode_callback(
|
|
971
1017
|
"heartbeat",
|
|
@@ -974,12 +1020,9 @@ class RPC extends _utils__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
|
|
|
974
1020
|
false,
|
|
975
1021
|
null,
|
|
976
1022
|
local_workspace,
|
|
977
|
-
// `heartbeat (${description})`,
|
|
978
1023
|
);
|
|
979
1024
|
store.timer = timer;
|
|
980
1025
|
encoded.interval = this._method_timeout / 2;
|
|
981
|
-
} else {
|
|
982
|
-
timer = null;
|
|
983
1026
|
}
|
|
984
1027
|
|
|
985
1028
|
[encoded.resolve, store.resolve] = this._encode_callback(
|
|
@@ -1043,7 +1086,19 @@ class RPC extends _utils__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
|
|
|
1043
1086
|
}
|
|
1044
1087
|
|
|
1045
1088
|
// Wait for all chunk uploads to finish
|
|
1046
|
-
|
|
1089
|
+
try {
|
|
1090
|
+
await Promise.all(tasks);
|
|
1091
|
+
} catch (error) {
|
|
1092
|
+
// If any chunk fails, clean up the message cache
|
|
1093
|
+
try {
|
|
1094
|
+
await message_cache.remove(message_id);
|
|
1095
|
+
} catch (cleanupError) {
|
|
1096
|
+
console.error(
|
|
1097
|
+
`Failed to clean up message cache after error: ${cleanupError}`,
|
|
1098
|
+
);
|
|
1099
|
+
}
|
|
1100
|
+
throw error;
|
|
1101
|
+
}
|
|
1047
1102
|
} else {
|
|
1048
1103
|
// 3) Legacy version (sequential appends):
|
|
1049
1104
|
await message_cache.create(message_id, !!session_id);
|
|
@@ -1181,13 +1236,24 @@ class RPC extends _utils__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
|
|
|
1181
1236
|
);
|
|
1182
1237
|
// By default, hypha will clear the session after the method is called
|
|
1183
1238
|
// However, if the args contains _rintf === true, we will not clear the session
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1239
|
+
|
|
1240
|
+
// Helper function to recursively check for _rintf objects
|
|
1241
|
+
function hasInterfaceObject(obj) {
|
|
1242
|
+
if (!obj || typeof obj !== "object") return false;
|
|
1243
|
+
if (obj._rintf === true) return true;
|
|
1244
|
+
if (Array.isArray(obj)) {
|
|
1245
|
+
return obj.some((item) => hasInterfaceObject(item));
|
|
1189
1246
|
}
|
|
1247
|
+
if (obj.constructor === Object) {
|
|
1248
|
+
return Object.values(obj).some((value) =>
|
|
1249
|
+
hasInterfaceObject(value),
|
|
1250
|
+
);
|
|
1251
|
+
}
|
|
1252
|
+
return false;
|
|
1190
1253
|
}
|
|
1254
|
+
|
|
1255
|
+
let clear_after_called = !hasInterfaceObject(args);
|
|
1256
|
+
|
|
1191
1257
|
const promiseData = await self._encode_promise(
|
|
1192
1258
|
resolve,
|
|
1193
1259
|
reject,
|
|
@@ -1344,7 +1410,14 @@ class RPC extends _utils__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
|
|
|
1344
1410
|
try {
|
|
1345
1411
|
method = indexObject(this._object_store, data["method"]);
|
|
1346
1412
|
} catch (e) {
|
|
1347
|
-
//
|
|
1413
|
+
// Clean promise method error handling
|
|
1414
|
+
if (this._is_promise_method_call(data["method"])) {
|
|
1415
|
+
console.debug(
|
|
1416
|
+
`Promise method ${data["method"]} not available (session settled or cleaned up), ignoring: ${method_name}`,
|
|
1417
|
+
);
|
|
1418
|
+
return;
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1348
1421
|
throw new Error(
|
|
1349
1422
|
`Method not found: ${method_name} at ${this._client_id}`,
|
|
1350
1423
|
);
|
|
@@ -1478,7 +1551,8 @@ class RPC extends _utils__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
|
|
|
1478
1551
|
const last_index = levels.length - 1;
|
|
1479
1552
|
for (let level of levels.slice(0, last_index)) {
|
|
1480
1553
|
if (!store[level]) {
|
|
1481
|
-
|
|
1554
|
+
// Instead of returning null, create intermediate sessions as needed
|
|
1555
|
+
store[level] = {};
|
|
1482
1556
|
}
|
|
1483
1557
|
store = store[level];
|
|
1484
1558
|
}
|
|
@@ -1692,6 +1766,9 @@ class RPC extends _utils__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
|
|
|
1692
1766
|
nj.NdArray &&
|
|
1693
1767
|
aObject instanceof nj.NdArray
|
|
1694
1768
|
) {
|
|
1769
|
+
if (!aObject.selection || !aObject.selection.data) {
|
|
1770
|
+
throw new Error("Invalid NumJS array: missing selection or data");
|
|
1771
|
+
}
|
|
1695
1772
|
const dtype = (0,_utils__WEBPACK_IMPORTED_MODULE_0__.typedArrayToDtype)(aObject.selection.data);
|
|
1696
1773
|
bObject = {
|
|
1697
1774
|
_rtype: "ndarray",
|
|
@@ -1784,10 +1861,7 @@ class RPC extends _utils__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
|
|
|
1784
1861
|
local_workspace,
|
|
1785
1862
|
),
|
|
1786
1863
|
};
|
|
1787
|
-
} else if (
|
|
1788
|
-
aObject.constructor instanceof Object ||
|
|
1789
|
-
Array.isArray(aObject)
|
|
1790
|
-
) {
|
|
1864
|
+
} else if (aObject.constructor === Object || Array.isArray(aObject)) {
|
|
1791
1865
|
bObject = isarray ? [] : {};
|
|
1792
1866
|
const keys = Object.keys(aObject);
|
|
1793
1867
|
for (let k of keys) {
|
|
@@ -1861,23 +1935,18 @@ class RPC extends _utils__WEBPACK_IMPORTED_MODULE_0__.MessageEmitter {
|
|
|
1861
1935
|
|
|
1862
1936
|
// Create an async generator proxy
|
|
1863
1937
|
async function* asyncGeneratorProxy() {
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
break;
|
|
1871
|
-
}
|
|
1872
|
-
yield next_item;
|
|
1873
|
-
} catch (error) {
|
|
1874
|
-
console.error("Error in generator:", error);
|
|
1875
|
-
throw error;
|
|
1938
|
+
while (true) {
|
|
1939
|
+
try {
|
|
1940
|
+
const next_item = await gen_method();
|
|
1941
|
+
// Check for StopIteration signal
|
|
1942
|
+
if (next_item && next_item._rtype === "stop_iteration") {
|
|
1943
|
+
break;
|
|
1876
1944
|
}
|
|
1945
|
+
yield next_item;
|
|
1946
|
+
} catch (error) {
|
|
1947
|
+
console.error("Error in generator:", error);
|
|
1948
|
+
throw error;
|
|
1877
1949
|
}
|
|
1878
|
-
} catch (error) {
|
|
1879
|
-
console.error("Error in generator:", error);
|
|
1880
|
-
throw error;
|
|
1881
1950
|
}
|
|
1882
1951
|
}
|
|
1883
1952
|
bObject = asyncGeneratorProxy();
|
|
@@ -2791,6 +2860,21 @@ async function _createOffer(params, server, config, onInit, context) {
|
|
|
2791
2860
|
let answer = await pc.createAnswer();
|
|
2792
2861
|
await pc.setLocalDescription(answer);
|
|
2793
2862
|
|
|
2863
|
+
// Wait for ICE candidates to be gathered (important for Firefox)
|
|
2864
|
+
await new Promise((resolveIce) => {
|
|
2865
|
+
if (pc.iceGatheringState === "complete") {
|
|
2866
|
+
resolveIce();
|
|
2867
|
+
} else {
|
|
2868
|
+
pc.addEventListener("icegatheringstatechange", () => {
|
|
2869
|
+
if (pc.iceGatheringState === "complete") {
|
|
2870
|
+
resolveIce();
|
|
2871
|
+
}
|
|
2872
|
+
});
|
|
2873
|
+
// Don't wait forever for ICE gathering
|
|
2874
|
+
setTimeout(resolveIce, 5000);
|
|
2875
|
+
}
|
|
2876
|
+
});
|
|
2877
|
+
|
|
2794
2878
|
return {
|
|
2795
2879
|
sdp: pc.localDescription.sdp,
|
|
2796
2880
|
type: pc.localDescription.type,
|
|
@@ -2810,30 +2894,80 @@ async function getRTCService(server, service_id, config) {
|
|
|
2810
2894
|
});
|
|
2811
2895
|
|
|
2812
2896
|
return new Promise(async (resolve, reject) => {
|
|
2897
|
+
let resolved = false;
|
|
2898
|
+
const timeout = setTimeout(() => {
|
|
2899
|
+
if (!resolved) {
|
|
2900
|
+
resolved = true;
|
|
2901
|
+
pc.close();
|
|
2902
|
+
reject(new Error("WebRTC Connection timeout"));
|
|
2903
|
+
}
|
|
2904
|
+
}, 30000); // Increase timeout to 30 seconds
|
|
2905
|
+
|
|
2813
2906
|
try {
|
|
2814
2907
|
pc.addEventListener(
|
|
2815
2908
|
"connectionstatechange",
|
|
2816
2909
|
() => {
|
|
2910
|
+
console.log("WebRTC Connection state: ", pc.connectionState);
|
|
2817
2911
|
if (pc.connectionState === "failed") {
|
|
2818
|
-
|
|
2819
|
-
|
|
2912
|
+
if (!resolved) {
|
|
2913
|
+
resolved = true;
|
|
2914
|
+
clearTimeout(timeout);
|
|
2915
|
+
pc.close();
|
|
2916
|
+
reject(new Error("WebRTC Connection failed"));
|
|
2917
|
+
}
|
|
2820
2918
|
} else if (pc.connectionState === "closed") {
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2919
|
+
if (!resolved) {
|
|
2920
|
+
resolved = true;
|
|
2921
|
+
clearTimeout(timeout);
|
|
2922
|
+
reject(new Error("WebRTC Connection closed"));
|
|
2923
|
+
}
|
|
2924
|
+
} else if (pc.connectionState === "connected") {
|
|
2925
|
+
console.log("WebRTC Connection established successfully");
|
|
2824
2926
|
}
|
|
2825
2927
|
},
|
|
2826
2928
|
false,
|
|
2827
2929
|
);
|
|
2828
2930
|
|
|
2931
|
+
// Add ICE connection state change handler for better debugging
|
|
2932
|
+
pc.addEventListener("iceconnectionstatechange", () => {
|
|
2933
|
+
console.log("ICE Connection state: ", pc.iceConnectionState);
|
|
2934
|
+
if (pc.iceConnectionState === "failed") {
|
|
2935
|
+
if (!resolved) {
|
|
2936
|
+
resolved = true;
|
|
2937
|
+
clearTimeout(timeout);
|
|
2938
|
+
pc.close();
|
|
2939
|
+
reject(new Error("ICE Connection failed"));
|
|
2940
|
+
}
|
|
2941
|
+
}
|
|
2942
|
+
});
|
|
2943
|
+
|
|
2829
2944
|
if (config.on_init) {
|
|
2830
2945
|
await config.on_init(pc);
|
|
2831
2946
|
delete config.on_init;
|
|
2832
2947
|
}
|
|
2948
|
+
|
|
2833
2949
|
let channel = pc.createDataChannel(config.peer_id, { ordered: true });
|
|
2834
2950
|
channel.binaryType = "arraybuffer";
|
|
2951
|
+
|
|
2952
|
+
// Wait for ICE gathering to complete before creating offer
|
|
2835
2953
|
const offer = await pc.createOffer();
|
|
2836
2954
|
await pc.setLocalDescription(offer);
|
|
2955
|
+
|
|
2956
|
+
// Wait for ICE candidates to be gathered (important for Firefox)
|
|
2957
|
+
await new Promise((resolveIce) => {
|
|
2958
|
+
if (pc.iceGatheringState === "complete") {
|
|
2959
|
+
resolveIce();
|
|
2960
|
+
} else {
|
|
2961
|
+
pc.addEventListener("icegatheringstatechange", () => {
|
|
2962
|
+
if (pc.iceGatheringState === "complete") {
|
|
2963
|
+
resolveIce();
|
|
2964
|
+
}
|
|
2965
|
+
});
|
|
2966
|
+
// Don't wait forever for ICE gathering
|
|
2967
|
+
setTimeout(resolveIce, 5000);
|
|
2968
|
+
}
|
|
2969
|
+
});
|
|
2970
|
+
|
|
2837
2971
|
const svc = await server.getService(service_id);
|
|
2838
2972
|
const answer = await svc.offer({
|
|
2839
2973
|
sdp: pc.localDescription.sdp,
|
|
@@ -2843,77 +2977,102 @@ async function getRTCService(server, service_id, config) {
|
|
|
2843
2977
|
channel.onopen = () => {
|
|
2844
2978
|
config.channel = channel;
|
|
2845
2979
|
config.workspace = answer.workspace;
|
|
2846
|
-
//
|
|
2847
|
-
// This is needed for safari to work
|
|
2980
|
+
// Increase timeout for Firefox compatibility
|
|
2848
2981
|
setTimeout(async () => {
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
service_id: {
|
|
2876
|
-
type: "string",
|
|
2877
|
-
description:
|
|
2878
|
-
"Service ID. This should be a service id in the format: 'workspace/service_id', 'workspace/client_id:service_id' or 'workspace/client_id:service_id@app_id'",
|
|
2879
|
-
},
|
|
2880
|
-
config: {
|
|
2982
|
+
if (!resolved) {
|
|
2983
|
+
try {
|
|
2984
|
+
const rpc = await _setupRPC(config);
|
|
2985
|
+
pc.rpc = rpc;
|
|
2986
|
+
async function get_service(name, ...args) {
|
|
2987
|
+
(0,_utils__WEBPACK_IMPORTED_MODULE_1__.assert)(
|
|
2988
|
+
!name.includes(":"),
|
|
2989
|
+
"WebRTC service name should not contain ':'",
|
|
2990
|
+
);
|
|
2991
|
+
(0,_utils__WEBPACK_IMPORTED_MODULE_1__.assert)(
|
|
2992
|
+
!name.includes("/"),
|
|
2993
|
+
"WebRTC service name should not contain '/'",
|
|
2994
|
+
);
|
|
2995
|
+
return await rpc.get_remote_service(
|
|
2996
|
+
config.workspace + "/" + config.peer_id + ":" + name,
|
|
2997
|
+
...args,
|
|
2998
|
+
);
|
|
2999
|
+
}
|
|
3000
|
+
async function disconnect() {
|
|
3001
|
+
await rpc.disconnect();
|
|
3002
|
+
pc.close();
|
|
3003
|
+
}
|
|
3004
|
+
pc.getService = (0,_utils_schema_js__WEBPACK_IMPORTED_MODULE_2__.schemaFunction)(get_service, {
|
|
3005
|
+
name: "getService",
|
|
3006
|
+
description: "Get a remote service via webrtc",
|
|
3007
|
+
parameters: {
|
|
2881
3008
|
type: "object",
|
|
2882
|
-
|
|
3009
|
+
properties: {
|
|
3010
|
+
service_id: {
|
|
3011
|
+
type: "string",
|
|
3012
|
+
description:
|
|
3013
|
+
"Service ID. This should be a service id in the format: 'workspace/service_id', 'workspace/client_id:service_id' or 'workspace/client_id:service_id@app_id'",
|
|
3014
|
+
},
|
|
3015
|
+
config: {
|
|
3016
|
+
type: "object",
|
|
3017
|
+
description: "Options for the service",
|
|
3018
|
+
},
|
|
3019
|
+
},
|
|
3020
|
+
required: ["id"],
|
|
2883
3021
|
},
|
|
2884
|
-
}
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
name: "registerCodec",
|
|
2895
|
-
description: "Register a codec for the webrtc connection",
|
|
2896
|
-
parameters: {
|
|
2897
|
-
type: "object",
|
|
2898
|
-
properties: {
|
|
2899
|
-
codec: {
|
|
3022
|
+
});
|
|
3023
|
+
pc.disconnect = (0,_utils_schema_js__WEBPACK_IMPORTED_MODULE_2__.schemaFunction)(disconnect, {
|
|
3024
|
+
name: "disconnect",
|
|
3025
|
+
description: "Disconnect from the webrtc connection via webrtc",
|
|
3026
|
+
parameters: { type: "object", properties: {} },
|
|
3027
|
+
});
|
|
3028
|
+
pc.registerCodec = (0,_utils_schema_js__WEBPACK_IMPORTED_MODULE_2__.schemaFunction)(rpc.register_codec, {
|
|
3029
|
+
name: "registerCodec",
|
|
3030
|
+
description: "Register a codec for the webrtc connection",
|
|
3031
|
+
parameters: {
|
|
2900
3032
|
type: "object",
|
|
2901
|
-
description: "Codec to register",
|
|
2902
3033
|
properties: {
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
3034
|
+
codec: {
|
|
3035
|
+
type: "object",
|
|
3036
|
+
description: "Codec to register",
|
|
3037
|
+
properties: {
|
|
3038
|
+
name: { type: "string" },
|
|
3039
|
+
type: {},
|
|
3040
|
+
encoder: { type: "function" },
|
|
3041
|
+
decoder: { type: "function" },
|
|
3042
|
+
},
|
|
3043
|
+
},
|
|
2907
3044
|
},
|
|
2908
3045
|
},
|
|
2909
|
-
}
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
3046
|
+
});
|
|
3047
|
+
resolved = true;
|
|
3048
|
+
clearTimeout(timeout);
|
|
3049
|
+
resolve(pc);
|
|
3050
|
+
} catch (e) {
|
|
3051
|
+
if (!resolved) {
|
|
3052
|
+
resolved = true;
|
|
3053
|
+
clearTimeout(timeout);
|
|
3054
|
+
reject(e);
|
|
3055
|
+
}
|
|
3056
|
+
}
|
|
3057
|
+
}
|
|
3058
|
+
}, 1000); // Increase timeout to 1 second for Firefox
|
|
2914
3059
|
};
|
|
2915
3060
|
|
|
2916
|
-
channel.onclose = () =>
|
|
3061
|
+
channel.onclose = () => {
|
|
3062
|
+
if (!resolved) {
|
|
3063
|
+
resolved = true;
|
|
3064
|
+
clearTimeout(timeout);
|
|
3065
|
+
reject(new Error("Data channel closed"));
|
|
3066
|
+
}
|
|
3067
|
+
};
|
|
3068
|
+
|
|
3069
|
+
channel.onerror = (error) => {
|
|
3070
|
+
if (!resolved) {
|
|
3071
|
+
resolved = true;
|
|
3072
|
+
clearTimeout(timeout);
|
|
3073
|
+
reject(new Error(`Data channel error: ${error}`));
|
|
3074
|
+
}
|
|
3075
|
+
};
|
|
2917
3076
|
|
|
2918
3077
|
await pc.setRemoteDescription(
|
|
2919
3078
|
new RTCSessionDescription({
|
|
@@ -2922,7 +3081,11 @@ async function getRTCService(server, service_id, config) {
|
|
|
2922
3081
|
}),
|
|
2923
3082
|
);
|
|
2924
3083
|
} catch (e) {
|
|
2925
|
-
|
|
3084
|
+
if (!resolved) {
|
|
3085
|
+
resolved = true;
|
|
3086
|
+
clearTimeout(timeout);
|
|
3087
|
+
reject(e);
|
|
3088
|
+
}
|
|
2926
3089
|
}
|
|
2927
3090
|
});
|
|
2928
3091
|
}
|