@decentnetwork/lan 0.1.255 → 0.1.256
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.
|
@@ -27,6 +27,17 @@ export interface PeerManagerOptions {
|
|
|
27
27
|
* signals over socket.io instead of failing. */
|
|
28
28
|
offlineCall?: OfflineCallConfig;
|
|
29
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* True when a Carrier invite send failed for a reason that will recur
|
|
32
|
+
* identically on every retry, so retrying or waiting for a reconnect is
|
|
33
|
+
* pointless and the signal must take another transport or be lost.
|
|
34
|
+
*
|
|
35
|
+
* The one that bit us: the native invite channel caps application data at
|
|
36
|
+
* CARRIER_MAX_INVITE_DATA_LEN (8192, from carrier.h — it is a wire constant
|
|
37
|
+
* shared with the iOS/Android SDK, not something we can raise unilaterally),
|
|
38
|
+
* while a Chrome SDP offer/answer runs 8.4-9.8 KB.
|
|
39
|
+
*/
|
|
40
|
+
export declare function isPermanentInviteFailure(err: unknown): boolean;
|
|
30
41
|
export declare class PeerManager extends EventEmitter {
|
|
31
42
|
private peer;
|
|
32
43
|
private identity;
|
|
@@ -9,6 +9,29 @@ import { PacketSession } from "./packet-session.js";
|
|
|
9
9
|
import { FrameCodec } from "./frame.js";
|
|
10
10
|
import { FRAME_OPCODE_HANDSHAKE_ACK, PACKET_ID_DL_SESSION, PACKET_ID_DL_DORA, PACKET_ID_DL_IP, PACKET_ID_DL_RATE, } from "./types.js";
|
|
11
11
|
import { Logger } from "../utils/logger.js";
|
|
12
|
+
/**
|
|
13
|
+
* True when a Carrier invite send failed for a reason that will recur
|
|
14
|
+
* identically on every retry, so retrying or waiting for a reconnect is
|
|
15
|
+
* pointless and the signal must take another transport or be lost.
|
|
16
|
+
*
|
|
17
|
+
* The one that bit us: the native invite channel caps application data at
|
|
18
|
+
* CARRIER_MAX_INVITE_DATA_LEN (8192, from carrier.h — it is a wire constant
|
|
19
|
+
* shared with the iOS/Android SDK, not something we can raise unilaterally),
|
|
20
|
+
* while a Chrome SDP offer/answer runs 8.4-9.8 KB.
|
|
21
|
+
*/
|
|
22
|
+
export function isPermanentInviteFailure(err) {
|
|
23
|
+
return /invite data must be/i.test(err?.message ?? "");
|
|
24
|
+
}
|
|
25
|
+
/** Best-effort RtcSignal type ("offer"/"answer"/"candidate"/"bye") for logs. */
|
|
26
|
+
function signalKind(data) {
|
|
27
|
+
try {
|
|
28
|
+
const text = typeof data === "string" ? data : Buffer.from(data).toString("utf-8");
|
|
29
|
+
return String(JSON.parse(text).type ?? "signal");
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return "signal";
|
|
33
|
+
}
|
|
34
|
+
}
|
|
12
35
|
export class PeerManager extends EventEmitter {
|
|
13
36
|
peer = null;
|
|
14
37
|
identity = null;
|
|
@@ -181,7 +204,21 @@ export class PeerManager extends EventEmitter {
|
|
|
181
204
|
// A call already committed to Carrier must NOT split to socket.io on a
|
|
182
205
|
// transient flap (it breaks ICE). Drop this signal; ICE recovers via the
|
|
183
206
|
// other candidates / a retried offer.
|
|
207
|
+
//
|
|
208
|
+
// That reasoning only holds for a TRANSIENT failure. An oversize payload
|
|
209
|
+
// fails identically on every retry, so "ICE recovers via a retried offer"
|
|
210
|
+
// is false — the retry is the same too-big offer. Chrome's SDP runs
|
|
211
|
+
// 8.4-9.8 KB against the 8192-byte native invite cap (carrier.h), which
|
|
212
|
+
// silently killed every desktop→phone call: the offer never left, so the
|
|
213
|
+
// phone never rang, and on an inbound call our answer never left, so it
|
|
214
|
+
// rang and then never connected. Route a permanently-undeliverable signal
|
|
215
|
+
// to the offline bridge instead of dropping it on the floor.
|
|
184
216
|
if (callId && this.offlineBridge?.isOnlineCall(callId)) {
|
|
217
|
+
if (this.offlineBridge.enabled && isPermanentInviteFailure(err)) {
|
|
218
|
+
this.logger.warn(`sendCallSignal: ${signalKind(data)} for call ${callId} cannot go over Carrier (${err.message}) — offline path to ${userid}`);
|
|
219
|
+
await this.offlineBridge.sendSignal(userid, data, hasVideo);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
185
222
|
this.logger.info(`sendCallSignal: dropped a Carrier signal for online call ${callId} (${err.message})`);
|
|
186
223
|
return;
|
|
187
224
|
}
|
package/dist/ui/desktop/app.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
window.__DK_UI_VERSION="0.1.
|
|
1
|
+
window.__DK_UI_VERSION="0.1.256";
|
|
2
2
|
const ICON_PATHS = {
|
|
3
3
|
// ---- tab bar (the four must feel like one set) ----
|
|
4
4
|
users: '<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/>',
|
|
@@ -2454,6 +2454,40 @@ const CALL_ICE_SERVERS = [
|
|
|
2454
2454
|
{ urls: "turn:tokyo.fi.chat:3478", username: "allcom", credential: "allcompass" },
|
|
2455
2455
|
{ urls: "turn:tokyo.fi.chat:3478?transport=tcp", username: "allcom", credential: "allcompass" }
|
|
2456
2456
|
];
|
|
2457
|
+
const VIDEO_CODEC_KEEP = /^video\/(H264|VP8|rtx|red|ulpfec)$/i;
|
|
2458
|
+
const SDP_BUDGET = 8192;
|
|
2459
|
+
function trimVideoCodecs(pc) {
|
|
2460
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
2461
|
+
const supported = (_c = (_b = (_a = window.RTCRtpSender) == null ? void 0 : _a.getCapabilities) == null ? void 0 : _b.call(_a, "video")) == null ? void 0 : _c.codecs;
|
|
2462
|
+
if (!supported) return;
|
|
2463
|
+
const keep = supported.filter((c) => VIDEO_CODEC_KEEP.test(c.mimeType));
|
|
2464
|
+
if (!keep.length || keep.length === supported.length) return;
|
|
2465
|
+
for (const t of pc.getTransceivers()) {
|
|
2466
|
+
const kind = ((_e = (_d = t.receiver) == null ? void 0 : _d.track) == null ? void 0 : _e.kind) || ((_g = (_f = t.sender) == null ? void 0 : _f.track) == null ? void 0 : _g.kind);
|
|
2467
|
+
if (kind !== "video" || !t.setCodecPreferences) continue;
|
|
2468
|
+
try {
|
|
2469
|
+
t.setCodecPreferences(keep);
|
|
2470
|
+
} catch (e) {
|
|
2471
|
+
console.warn("[call] setCodecPreferences failed", e);
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2474
|
+
}
|
|
2475
|
+
function createLeanPeerConnection(config) {
|
|
2476
|
+
const pc = new RTCPeerConnection(config);
|
|
2477
|
+
for (const method of ["createOffer", "createAnswer"]) {
|
|
2478
|
+
const original = pc[method].bind(pc);
|
|
2479
|
+
pc[method] = async (...args) => {
|
|
2480
|
+
trimVideoCodecs(pc);
|
|
2481
|
+
const desc = await original(...args);
|
|
2482
|
+
const bytes = new TextEncoder().encode(desc.sdp || "").length;
|
|
2483
|
+
console[bytes > SDP_BUDGET ? "warn" : "log"](
|
|
2484
|
+
`[call] ${method} SDP ${bytes}B / ${SDP_BUDGET}B budget` + (bytes > SDP_BUDGET ? " \u2014 too large for the Carrier invite channel, falling back to the offline path" : "")
|
|
2485
|
+
);
|
|
2486
|
+
return desc;
|
|
2487
|
+
};
|
|
2488
|
+
}
|
|
2489
|
+
return pc;
|
|
2490
|
+
}
|
|
2457
2491
|
function useCallController(selfId, onCallLog) {
|
|
2458
2492
|
const [incoming, setIncoming] = React.useState(null);
|
|
2459
2493
|
const [active, setActive] = React.useState(null);
|
|
@@ -2476,7 +2510,7 @@ function useCallController(selfId, onCallLog) {
|
|
|
2476
2510
|
const engine = new CallEngine({
|
|
2477
2511
|
selfId,
|
|
2478
2512
|
signaling,
|
|
2479
|
-
createPeerConnection: (c) =>
|
|
2513
|
+
createPeerConnection: (c) => createLeanPeerConnection(c),
|
|
2480
2514
|
getLocalMedia: (k) => navigator.mediaDevices.getUserMedia({ audio: k.audio, video: k.video }),
|
|
2481
2515
|
getDisplayMedia: (c) => navigator.mediaDevices.getDisplayMedia(c || { video: true, audio: false }),
|
|
2482
2516
|
iceServers: CALL_ICE_SERVERS,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decentnetwork/lan",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.256",
|
|
4
4
|
"description": "Private virtual LAN for self-hosted services and AI agents, built on Elastos Carrier. NAT-traversal, name service, ACL, all over a peer-to-peer mesh — no public IP required.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|