arnacon-webrtc-service 0.1.21 → 0.1.22

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arnacon-webrtc-service",
3
- "version": "0.1.21",
3
+ "version": "0.1.22",
4
4
  "description": "Arnacon WebRTC core runtime and service modules",
5
5
  "main": "./webRTCservice/core.js",
6
6
  "type": "commonjs",
@@ -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.connectedSessions) {
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.connectedSessions) {
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.connectedSessions) {
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
- const calleeWallet = destination.wallet;
216
- const calleeEns = destination.ensName || calleeWallet;
217
- if (!calleeWallet || !calleeEns) continue;
218
- const walletKey = String(calleeWallet).toLowerCase();
219
- group.pendingWallets.add(walletKey);
220
- addPendingEntry(walletKey, {
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;
@@ -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
  });