arnacon-webrtc-service 0.1.21 → 0.1.23
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/package.json
CHANGED
|
@@ -4,11 +4,18 @@ function createBridgeApi({
|
|
|
4
4
|
sessions,
|
|
5
5
|
pendingBridges,
|
|
6
6
|
pendingInboundCalls,
|
|
7
|
+
createSession,
|
|
8
|
+
createPeerConnection,
|
|
7
9
|
sendNotification,
|
|
8
10
|
sendDataChannelMessage,
|
|
9
11
|
startWebRtcBridge,
|
|
10
12
|
destroySession,
|
|
11
13
|
notiTypeCall,
|
|
14
|
+
MediaStreamTrack,
|
|
15
|
+
waitForIceGathering,
|
|
16
|
+
formatIceCandidates,
|
|
17
|
+
getRelayCandidates,
|
|
18
|
+
embedCandidatesInSdp,
|
|
12
19
|
RTCSessionDescription,
|
|
13
20
|
logger = console,
|
|
14
21
|
}) {
|
|
@@ -126,7 +133,7 @@ function createBridgeApi({
|
|
|
126
133
|
|
|
127
134
|
function dropRingGroupTracking(group) {
|
|
128
135
|
if (!group) return;
|
|
129
|
-
for (const sid of group.
|
|
136
|
+
for (const sid of group.legSessionIds) {
|
|
130
137
|
sessionToRingGroup.delete(sid);
|
|
131
138
|
}
|
|
132
139
|
ringGroups.delete(group.groupId);
|
|
@@ -138,6 +145,85 @@ function createBridgeApi({
|
|
|
138
145
|
sessionToRingGroup.set(sessionId, group.groupId);
|
|
139
146
|
}
|
|
140
147
|
|
|
148
|
+
async function createMultiringLegOffer(group, callerSession, callerNumberLabel, destination, legIndex) {
|
|
149
|
+
const calleeWallet = destination.wallet;
|
|
150
|
+
const calleeEns = destination.ensName || calleeWallet;
|
|
151
|
+
if (!calleeWallet || !calleeEns) return null;
|
|
152
|
+
|
|
153
|
+
const walletKey = String(calleeWallet).toLowerCase();
|
|
154
|
+
const legSessionId = `${group.groupId}-leg${legIndex}`;
|
|
155
|
+
try {
|
|
156
|
+
const legSession = createSession(legSessionId, callerSession.callerEns, calleeEns);
|
|
157
|
+
legSession.isGatewayCaller = true;
|
|
158
|
+
legSession.walletAddress = walletKey;
|
|
159
|
+
legSession.multiRingGroupId = group.groupId;
|
|
160
|
+
legSession.multiRingLeg = true;
|
|
161
|
+
legSession.serviceId = callerSession.serviceId || null;
|
|
162
|
+
|
|
163
|
+
const pc = createPeerConnection(legSessionId);
|
|
164
|
+
if (typeof pc.createDataChannel === "function") {
|
|
165
|
+
try { pc.createDataChannel("chat"); } catch (_) {}
|
|
166
|
+
}
|
|
167
|
+
legSession.localAudioTrack = new MediaStreamTrack({ kind: "audio" });
|
|
168
|
+
pc.addTrack(legSession.localAudioTrack);
|
|
169
|
+
legSession.iceCandidates = [];
|
|
170
|
+
|
|
171
|
+
const offer = await pc.createOffer();
|
|
172
|
+
await pc.setLocalDescription(offer);
|
|
173
|
+
await waitForIceGathering(pc);
|
|
174
|
+
|
|
175
|
+
const gatheredCandidates = formatIceCandidates(legSession).filter((c) => {
|
|
176
|
+
const cand = String(c?.candidate || "").toLowerCase();
|
|
177
|
+
return !cand.includes(" tcp ");
|
|
178
|
+
});
|
|
179
|
+
const srflxAndRelay = gatheredCandidates.filter((c) => {
|
|
180
|
+
const cand = String(c?.candidate || "");
|
|
181
|
+
return cand.includes("typ srflx") || cand.includes("typ relay");
|
|
182
|
+
});
|
|
183
|
+
const candidatesToEmbed = srflxAndRelay.length > 0 ? srflxAndRelay : gatheredCandidates;
|
|
184
|
+
const relayCandidates = getRelayCandidates(gatheredCandidates);
|
|
185
|
+
const offerSdp = embedCandidatesInSdp(offer.sdp, candidatesToEmbed);
|
|
186
|
+
|
|
187
|
+
group.pendingWallets.add(walletKey);
|
|
188
|
+
group.legSessionIds.add(legSessionId);
|
|
189
|
+
sessionToRingGroup.set(legSessionId, group.groupId);
|
|
190
|
+
addPendingEntry(walletKey, {
|
|
191
|
+
kind: "multi",
|
|
192
|
+
groupId: group.groupId,
|
|
193
|
+
callerSessionId: group.callerSessionId,
|
|
194
|
+
walletKey,
|
|
195
|
+
ensName: calleeEns,
|
|
196
|
+
legSessionId,
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
const sourceOffer = callerSession.lastRingOfferPayload || null;
|
|
200
|
+
const callPayload = JSON.stringify({
|
|
201
|
+
type: "offer",
|
|
202
|
+
from: callerNumberLabel || callerSession.callerEns,
|
|
203
|
+
to: calleeEns,
|
|
204
|
+
sessionId: legSessionId,
|
|
205
|
+
label: callerNumberLabel || undefined,
|
|
206
|
+
sdp: offerSdp,
|
|
207
|
+
candidates: relayCandidates,
|
|
208
|
+
callNonce: sourceOffer?.callNonce || null,
|
|
209
|
+
isCall: true,
|
|
210
|
+
multiRingGroupId: group.groupId,
|
|
211
|
+
});
|
|
212
|
+
await sendNotification(callerSession.callerEns, calleeEns, callPayload, notiTypeCall);
|
|
213
|
+
logger.log(`[MR:${group.groupId}] leg invited sessionId=${legSessionId} to=${calleeEns}`);
|
|
214
|
+
return legSessionId;
|
|
215
|
+
} catch (err) {
|
|
216
|
+
closeSessionNow(legSessionId, "mr-leg-offer-failed");
|
|
217
|
+
group.legSessionIds.delete(legSessionId);
|
|
218
|
+
sessionToRingGroup.delete(legSessionId);
|
|
219
|
+
removePendingEntries(walletKey, (entry) => entry.kind === "multi" && entry.groupId === group.groupId && entry.legSessionId === legSessionId);
|
|
220
|
+
if (!hasPendingEntries(walletKey, (entry) => entry.kind === "multi" && entry.groupId === group.groupId)) {
|
|
221
|
+
group.pendingWallets.delete(walletKey);
|
|
222
|
+
}
|
|
223
|
+
throw err;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
141
227
|
function commitReadyWinner(group, winnerSessionId) {
|
|
142
228
|
if (!group) return { handled: false };
|
|
143
229
|
if (group.winnerSessionId && group.winnerSessionId !== winnerSessionId) {
|
|
@@ -153,7 +239,7 @@ function createBridgeApi({
|
|
|
153
239
|
clearRingGroupTimeout(group);
|
|
154
240
|
clearRingGroupPendingEntries(group);
|
|
155
241
|
|
|
156
|
-
for (const sid of group.
|
|
242
|
+
for (const sid of group.legSessionIds) {
|
|
157
243
|
if (sid === winnerSessionId) continue;
|
|
158
244
|
closeSessionNow(sid, "mr-loser-winner-locked");
|
|
159
245
|
}
|
|
@@ -168,10 +254,6 @@ function createBridgeApi({
|
|
|
168
254
|
async function notifyAndBridgeMulti(callerSessionId, destinations) {
|
|
169
255
|
const callerSession = sessions.get(callerSessionId);
|
|
170
256
|
if (!callerSession) throw new Error("Caller session not found");
|
|
171
|
-
const sourceOffer = callerSession.lastRingOfferPayload || null;
|
|
172
|
-
if (!sourceOffer || !sourceOffer.sdp) {
|
|
173
|
-
throw new Error("Multiring requires caller ring offer payload");
|
|
174
|
-
}
|
|
175
257
|
|
|
176
258
|
const targets = Array.isArray(destinations) ? destinations : [];
|
|
177
259
|
if (targets.length === 0) throw new Error("No multiring destinations provided");
|
|
@@ -185,6 +267,7 @@ function createBridgeApi({
|
|
|
185
267
|
winnerSessionId: null,
|
|
186
268
|
closed: false,
|
|
187
269
|
pendingWallets: new Set(),
|
|
270
|
+
legSessionIds: new Set(),
|
|
188
271
|
connectedSessions: new Set(),
|
|
189
272
|
timeoutHandle: null,
|
|
190
273
|
resolve: null,
|
|
@@ -200,7 +283,7 @@ function createBridgeApi({
|
|
|
200
283
|
if (group.closed) return;
|
|
201
284
|
group.closed = true;
|
|
202
285
|
clearRingGroupPendingEntries(group);
|
|
203
|
-
for (const sid of group.
|
|
286
|
+
for (const sid of group.legSessionIds) {
|
|
204
287
|
closeSessionNow(sid, "mr-timeout");
|
|
205
288
|
}
|
|
206
289
|
dropRingGroupTracking(group);
|
|
@@ -211,34 +294,14 @@ function createBridgeApi({
|
|
|
211
294
|
ringGroups.set(groupId, group);
|
|
212
295
|
logger.log(`[MR:${group.groupId}] created callerSessionId=${callerSessionId}`);
|
|
213
296
|
|
|
297
|
+
let legIndex = 0;
|
|
214
298
|
for (const destination of targets) {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
kind: "multi",
|
|
222
|
-
groupId,
|
|
223
|
-
callerSessionId,
|
|
224
|
-
walletKey,
|
|
225
|
-
ensName: calleeEns,
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
const callPayload = JSON.stringify({
|
|
229
|
-
type: "offer",
|
|
230
|
-
from: callerNumberLabel || callerEns,
|
|
231
|
-
to: calleeEns,
|
|
232
|
-
sessionId: `${groupId}-${walletKey.slice(2, 8)}`,
|
|
233
|
-
label: callerNumberLabel || undefined,
|
|
234
|
-
sdp: sourceOffer.sdp,
|
|
235
|
-
candidates: Array.isArray(sourceOffer.candidates) ? sourceOffer.candidates : [],
|
|
236
|
-
callNonce: sourceOffer.callNonce || null,
|
|
237
|
-
isCall: true,
|
|
238
|
-
multiRingGroupId: groupId,
|
|
239
|
-
});
|
|
240
|
-
await sendNotification(callerEns, calleeEns, callPayload, notiTypeCall);
|
|
241
|
-
logger.log(`[MR:${group.groupId}] leg invited wallet=${walletKey} to=${calleeEns}`);
|
|
299
|
+
legIndex += 1;
|
|
300
|
+
try {
|
|
301
|
+
await createMultiringLegOffer(group, callerSession, callerNumberLabel, destination, legIndex);
|
|
302
|
+
} catch (err) {
|
|
303
|
+
logger.error(`[MR:${group.groupId}] failed leg invite #${legIndex}: ${err.message}`);
|
|
304
|
+
}
|
|
242
305
|
}
|
|
243
306
|
|
|
244
307
|
if (group.pendingWallets.size === 0) {
|
|
@@ -365,6 +428,10 @@ function createBridgeApi({
|
|
|
365
428
|
}
|
|
366
429
|
|
|
367
430
|
if (pending.kind === "multi") {
|
|
431
|
+
if (pending.legSessionId && pending.legSessionId !== sessionId) {
|
|
432
|
+
nextList.push(pending);
|
|
433
|
+
continue;
|
|
434
|
+
}
|
|
368
435
|
const group = ringGroups.get(pending.groupId);
|
|
369
436
|
if (!group || group.closed) {
|
|
370
437
|
continue;
|
|
@@ -67,6 +67,7 @@ function createPeerConnectionFactory({
|
|
|
67
67
|
RTCPeerConnection,
|
|
68
68
|
iceServers,
|
|
69
69
|
onDataChannelOpen,
|
|
70
|
+
onPeerConnected = null,
|
|
70
71
|
onDataChannelMessage,
|
|
71
72
|
destroySession,
|
|
72
73
|
logger = console,
|
|
@@ -112,6 +113,9 @@ function createPeerConnectionFactory({
|
|
|
112
113
|
clearTimeout(s.disconnectTimer);
|
|
113
114
|
s.disconnectTimer = null;
|
|
114
115
|
}
|
|
116
|
+
if (typeof onPeerConnected === "function") {
|
|
117
|
+
onPeerConnected(sessionId);
|
|
118
|
+
}
|
|
115
119
|
}
|
|
116
120
|
});
|
|
117
121
|
pc.onTrack.subscribe((track) => {
|
|
@@ -361,11 +361,18 @@ const bridgeApi = createBridgeApi({
|
|
|
361
361
|
sessions,
|
|
362
362
|
pendingBridges,
|
|
363
363
|
pendingInboundCalls,
|
|
364
|
+
createSession: (...args) => createSession(...args),
|
|
365
|
+
createPeerConnection: (...args) => createPeerConnection(...args),
|
|
364
366
|
sendNotification: (...args) => sendNotification(...args),
|
|
365
367
|
sendDataChannelMessage: (...args) => sendDataChannelMessage(...args),
|
|
366
368
|
startWebRtcBridge: (...args) => startWebRtcBridge(...args),
|
|
367
369
|
destroySession: (...args) => destroySession(...args),
|
|
368
370
|
notiTypeCall: NOTI_TYPE_CALL,
|
|
371
|
+
MediaStreamTrack,
|
|
372
|
+
waitForIceGathering: (...args) => waitForIceGathering(...args),
|
|
373
|
+
formatIceCandidates: (...args) => formatIceCandidates(...args),
|
|
374
|
+
getRelayCandidates: (...args) => getRelayCandidates(...args),
|
|
375
|
+
embedCandidatesInSdp: (...args) => embedCandidatesInSdp(...args),
|
|
369
376
|
RTCSessionDescription,
|
|
370
377
|
logger: console,
|
|
371
378
|
});
|
|
@@ -414,6 +421,7 @@ const peerConnectionApi = createPeerConnectionFactory({
|
|
|
414
421
|
RTCPeerConnection,
|
|
415
422
|
iceServers: ICE_SERVERS,
|
|
416
423
|
onDataChannelOpen: (sessionId) => onDataChannelOpen(sessionId),
|
|
424
|
+
onPeerConnected: (sessionId) => onPeerConnected(sessionId),
|
|
417
425
|
onDataChannelMessage: (sessionId, raw) => signalingHandlersApi.onDataChannelMessage(sessionId, raw),
|
|
418
426
|
destroySession: (sessionId, notify) => destroySession(sessionId, notify),
|
|
419
427
|
logger: console,
|
|
@@ -804,6 +812,13 @@ function onDataChannelOpen(sessionId) {
|
|
|
804
812
|
});
|
|
805
813
|
}
|
|
806
814
|
|
|
815
|
+
function onPeerConnected(sessionId) {
|
|
816
|
+
const session = sessions.get(sessionId);
|
|
817
|
+
if (!session || !session.walletAddress) return;
|
|
818
|
+
checkPendingBridge(sessionId, session.walletAddress);
|
|
819
|
+
checkPendingInboundCall(sessionId, session.walletAddress);
|
|
820
|
+
}
|
|
821
|
+
|
|
807
822
|
/**
|
|
808
823
|
* Gateway-as-caller: sends RING + audio SDP offer over the data channel.
|
|
809
824
|
* The callee will respond with an ANSWER + audio SDP, handled in onDataChannelMessage.
|