@decentnetwork/beagle 0.1.121 → 0.1.122
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/chat-components.d.ts +67 -0
- package/dist/chat-components.js +208 -0
- package/dist/desktop/app.js +1 -1
- package/dist/desktop/vendor/peer-webrtc.js +82 -10
- package/package.json +2 -2
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/** Custom packet id for the components channel.
|
|
2
|
+
*
|
|
3
|
+
* 160-191 is toxcore's LOSSLESS range — a form must not be silently dropped.
|
|
4
|
+
* decentlan owns 161 (session), 162 (dora), 163 (IP), 164 (rate); 165 is the
|
|
5
|
+
* first free slot. Clients that don't implement this id never see the packet,
|
|
6
|
+
* which is exactly the backward-compatibility story we want. */
|
|
7
|
+
export declare const PACKET_ID_CHAT_COMPONENTS = 165;
|
|
8
|
+
/** Payload version. Receivers ignore payloads they don't know how to read
|
|
9
|
+
* rather than best-effort parsing a future shape. */
|
|
10
|
+
export declare const COMPONENTS_WIRE_VERSION = 1;
|
|
11
|
+
export declare const MAX_COMPONENTS = 12;
|
|
12
|
+
export declare const MAX_OPTIONS = 40;
|
|
13
|
+
export declare const MAX_LABEL_LEN = 120;
|
|
14
|
+
export declare const MAX_ID_LEN = 64;
|
|
15
|
+
export declare const MAX_VALUE_LEN = 512;
|
|
16
|
+
export declare const MAX_CUSTOM_ID_LEN = 128;
|
|
17
|
+
/** Hard ceiling on a decoded payload before we even try to parse it. */
|
|
18
|
+
export declare const MAX_PAYLOAD_BYTES: number;
|
|
19
|
+
export type ComponentType = "select" | "submit" | "text" | "button" | "attachment";
|
|
20
|
+
export interface SelectOption {
|
|
21
|
+
label: string;
|
|
22
|
+
value: string;
|
|
23
|
+
}
|
|
24
|
+
export interface ChatComponent {
|
|
25
|
+
type: ComponentType;
|
|
26
|
+
id: string;
|
|
27
|
+
label?: string;
|
|
28
|
+
required?: boolean;
|
|
29
|
+
default?: string;
|
|
30
|
+
placeholder?: string;
|
|
31
|
+
options?: SelectOption[];
|
|
32
|
+
}
|
|
33
|
+
export interface ComponentsMessage {
|
|
34
|
+
v: number;
|
|
35
|
+
type: "components";
|
|
36
|
+
/** Chosen by the agent; echoed back so it can route the reply. */
|
|
37
|
+
custom_id: string;
|
|
38
|
+
components: ChatComponent[];
|
|
39
|
+
}
|
|
40
|
+
export interface InteractionMessage {
|
|
41
|
+
v: number;
|
|
42
|
+
type: "interaction";
|
|
43
|
+
custom_id: string;
|
|
44
|
+
/** id of the submit/button that fired. */
|
|
45
|
+
component: string;
|
|
46
|
+
values: Record<string, string | {
|
|
47
|
+
fileId: string;
|
|
48
|
+
name: string;
|
|
49
|
+
size: number;
|
|
50
|
+
}>;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Validate a remote components payload. Returns null when nothing renderable
|
|
54
|
+
* survives — the caller then shows only the text message, which is the correct
|
|
55
|
+
* degraded state, never an error.
|
|
56
|
+
*/
|
|
57
|
+
export declare function parseComponentsMessage(raw: unknown): ComponentsMessage | null;
|
|
58
|
+
/** Build the interaction reply. Values are the user's own input, but they are
|
|
59
|
+
* bounded here too so a pathological local state can't emit an oversized
|
|
60
|
+
* packet at the peer. */
|
|
61
|
+
export declare function buildInteraction(customId: string, component: string, values: Record<string, string>): InteractionMessage;
|
|
62
|
+
/** Decode a received packet. Returns null for anything malformed or oversized —
|
|
63
|
+
* a peer must not be able to make us throw on the receive path. */
|
|
64
|
+
export declare function decodePacket(data: Uint8Array): ComponentsMessage | InteractionMessage | null;
|
|
65
|
+
/** Validate an inbound interaction (the agent side of the channel). */
|
|
66
|
+
export declare function parseInteractionMessage(raw: unknown): InteractionMessage | null;
|
|
67
|
+
export declare function encodePacket(msg: ComponentsMessage | InteractionMessage): Uint8Array;
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
// Brief 30 — interactive components in chat.
|
|
2
|
+
//
|
|
3
|
+
// An agent declares a form as DATA; this client renders it with its own trusted
|
|
4
|
+
// widgets. No HTML crosses the wire, no URL is fetched, no script runs. The UI's
|
|
5
|
+
// escape-everything policy (dkHtmlEscape) is untouched — components are a
|
|
6
|
+
// separate, whitelisted channel, not an escape hatch from it.
|
|
7
|
+
//
|
|
8
|
+
// Wire shape: a sibling packet, not a field on the chat message. Answering the
|
|
9
|
+
// brief's first open question that way makes "old clients still see the
|
|
10
|
+
// message" a property of the protocol rather than a rule senders must remember:
|
|
11
|
+
// the human-readable text goes out as an ordinary chat message that every
|
|
12
|
+
// existing client already renders, and the component payload rides a custom
|
|
13
|
+
// packet id that any client not built for it never even receives.
|
|
14
|
+
//
|
|
15
|
+
// EVERY value here arrives from a remote peer. The validator is the trust
|
|
16
|
+
// boundary: it whitelists types, caps every count and length, and drops what it
|
|
17
|
+
// does not understand instead of guessing.
|
|
18
|
+
/** Custom packet id for the components channel.
|
|
19
|
+
*
|
|
20
|
+
* 160-191 is toxcore's LOSSLESS range — a form must not be silently dropped.
|
|
21
|
+
* decentlan owns 161 (session), 162 (dora), 163 (IP), 164 (rate); 165 is the
|
|
22
|
+
* first free slot. Clients that don't implement this id never see the packet,
|
|
23
|
+
* which is exactly the backward-compatibility story we want. */
|
|
24
|
+
export const PACKET_ID_CHAT_COMPONENTS = 165;
|
|
25
|
+
/** Payload version. Receivers ignore payloads they don't know how to read
|
|
26
|
+
* rather than best-effort parsing a future shape. */
|
|
27
|
+
export const COMPONENTS_WIRE_VERSION = 1;
|
|
28
|
+
// --- caps -------------------------------------------------------------------
|
|
29
|
+
// "The client caps sizes/counts itself — a peer must not be able to freeze
|
|
30
|
+
// another peer's UI." (Brief 30, Security). These are deliberately small: a
|
|
31
|
+
// legitimate form is a handful of fields, and anything larger is either a bug
|
|
32
|
+
// or an attempt to hang the renderer.
|
|
33
|
+
export const MAX_COMPONENTS = 12;
|
|
34
|
+
export const MAX_OPTIONS = 40;
|
|
35
|
+
export const MAX_LABEL_LEN = 120;
|
|
36
|
+
export const MAX_ID_LEN = 64;
|
|
37
|
+
export const MAX_VALUE_LEN = 512;
|
|
38
|
+
export const MAX_CUSTOM_ID_LEN = 128;
|
|
39
|
+
/** Hard ceiling on a decoded payload before we even try to parse it. */
|
|
40
|
+
export const MAX_PAYLOAD_BYTES = 64 * 1024;
|
|
41
|
+
/** Types this build actually renders. Anything else is dropped — forward
|
|
42
|
+
* compatibility per the brief: an older client must ignore, not break. */
|
|
43
|
+
const RENDERABLE = new Set(["select", "submit", "text"]);
|
|
44
|
+
const str = (v, max) => {
|
|
45
|
+
if (typeof v !== "string")
|
|
46
|
+
return undefined;
|
|
47
|
+
const s = v.trim();
|
|
48
|
+
if (!s || s.length > max)
|
|
49
|
+
return undefined;
|
|
50
|
+
return s;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Validate a remote components payload. Returns null when nothing renderable
|
|
54
|
+
* survives — the caller then shows only the text message, which is the correct
|
|
55
|
+
* degraded state, never an error.
|
|
56
|
+
*/
|
|
57
|
+
export function parseComponentsMessage(raw) {
|
|
58
|
+
if (!raw || typeof raw !== "object")
|
|
59
|
+
return null;
|
|
60
|
+
const o = raw;
|
|
61
|
+
if (o.type !== "components")
|
|
62
|
+
return null;
|
|
63
|
+
if (o.v !== COMPONENTS_WIRE_VERSION)
|
|
64
|
+
return null;
|
|
65
|
+
const customId = str(o.custom_id, MAX_CUSTOM_ID_LEN);
|
|
66
|
+
if (!customId)
|
|
67
|
+
return null;
|
|
68
|
+
if (!Array.isArray(o.components))
|
|
69
|
+
return null;
|
|
70
|
+
const seen = new Set();
|
|
71
|
+
const out = [];
|
|
72
|
+
for (const entry of o.components.slice(0, MAX_COMPONENTS)) {
|
|
73
|
+
if (!entry || typeof entry !== "object")
|
|
74
|
+
continue;
|
|
75
|
+
const c = entry;
|
|
76
|
+
const type = typeof c.type === "string" ? c.type : "";
|
|
77
|
+
if (!RENDERABLE.has(type))
|
|
78
|
+
continue; // unknown/unsupported: drop, don't guess
|
|
79
|
+
const id = str(c.id, MAX_ID_LEN);
|
|
80
|
+
if (!id || seen.has(id))
|
|
81
|
+
continue; // duplicate ids would alias each other's values
|
|
82
|
+
seen.add(id);
|
|
83
|
+
const component = { type: type, id };
|
|
84
|
+
const label = str(c.label, MAX_LABEL_LEN);
|
|
85
|
+
if (label)
|
|
86
|
+
component.label = label;
|
|
87
|
+
if (c.required === true)
|
|
88
|
+
component.required = true;
|
|
89
|
+
const placeholder = str(c.placeholder, MAX_LABEL_LEN);
|
|
90
|
+
if (placeholder)
|
|
91
|
+
component.placeholder = placeholder;
|
|
92
|
+
if (type === "select") {
|
|
93
|
+
if (!Array.isArray(c.options))
|
|
94
|
+
continue; // a select with no options is unusable
|
|
95
|
+
const options = [];
|
|
96
|
+
const seenValues = new Set();
|
|
97
|
+
for (const raw of c.options.slice(0, MAX_OPTIONS)) {
|
|
98
|
+
if (!raw || typeof raw !== "object")
|
|
99
|
+
continue;
|
|
100
|
+
const opt = raw;
|
|
101
|
+
const value = str(opt.value, MAX_VALUE_LEN);
|
|
102
|
+
if (!value || seenValues.has(value))
|
|
103
|
+
continue;
|
|
104
|
+
seenValues.add(value);
|
|
105
|
+
options.push({ label: str(opt.label, MAX_LABEL_LEN) ?? value, value });
|
|
106
|
+
}
|
|
107
|
+
if (!options.length)
|
|
108
|
+
continue;
|
|
109
|
+
component.options = options;
|
|
110
|
+
// A default that isn't one of the options would submit a value the agent
|
|
111
|
+
// never offered; ignore it rather than pass it through.
|
|
112
|
+
const def = str(c.default, MAX_VALUE_LEN);
|
|
113
|
+
if (def && options.some((o2) => o2.value === def))
|
|
114
|
+
component.default = def;
|
|
115
|
+
}
|
|
116
|
+
if (type === "text") {
|
|
117
|
+
const def = str(c.default, MAX_VALUE_LEN);
|
|
118
|
+
if (def)
|
|
119
|
+
component.default = def;
|
|
120
|
+
}
|
|
121
|
+
out.push(component);
|
|
122
|
+
}
|
|
123
|
+
// A form with no way to submit can never be completed — treat it as noise.
|
|
124
|
+
if (!out.some((c) => c.type === "submit"))
|
|
125
|
+
return null;
|
|
126
|
+
// ...and a submit button with nothing to fill in is equally pointless.
|
|
127
|
+
if (!out.some((c) => c.type === "select" || c.type === "text"))
|
|
128
|
+
return null;
|
|
129
|
+
return { v: COMPONENTS_WIRE_VERSION, type: "components", custom_id: customId, components: out };
|
|
130
|
+
}
|
|
131
|
+
/** Build the interaction reply. Values are the user's own input, but they are
|
|
132
|
+
* bounded here too so a pathological local state can't emit an oversized
|
|
133
|
+
* packet at the peer. */
|
|
134
|
+
export function buildInteraction(customId, component, values) {
|
|
135
|
+
const clean = {};
|
|
136
|
+
for (const [k, v] of Object.entries(values)) {
|
|
137
|
+
const key = str(k, MAX_ID_LEN);
|
|
138
|
+
if (!key)
|
|
139
|
+
continue;
|
|
140
|
+
clean[key] = typeof v === "string" ? v.slice(0, MAX_VALUE_LEN) : "";
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
v: COMPONENTS_WIRE_VERSION,
|
|
144
|
+
type: "interaction",
|
|
145
|
+
custom_id: customId.slice(0, MAX_CUSTOM_ID_LEN),
|
|
146
|
+
component: component.slice(0, MAX_ID_LEN),
|
|
147
|
+
values: clean,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
/** Decode a received packet. Returns null for anything malformed or oversized —
|
|
151
|
+
* a peer must not be able to make us throw on the receive path. */
|
|
152
|
+
export function decodePacket(data) {
|
|
153
|
+
if (!data?.length || data.length > MAX_PAYLOAD_BYTES)
|
|
154
|
+
return null;
|
|
155
|
+
let parsed;
|
|
156
|
+
try {
|
|
157
|
+
parsed = JSON.parse(Buffer.from(data).toString("utf-8"));
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
const o = parsed;
|
|
163
|
+
if (!o || typeof o !== "object")
|
|
164
|
+
return null;
|
|
165
|
+
if (o.type === "components")
|
|
166
|
+
return parseComponentsMessage(o);
|
|
167
|
+
if (o.type === "interaction")
|
|
168
|
+
return parseInteractionMessage(o);
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
/** Validate an inbound interaction (the agent side of the channel). */
|
|
172
|
+
export function parseInteractionMessage(raw) {
|
|
173
|
+
if (!raw || typeof raw !== "object")
|
|
174
|
+
return null;
|
|
175
|
+
const o = raw;
|
|
176
|
+
if (o.type !== "interaction" || o.v !== COMPONENTS_WIRE_VERSION)
|
|
177
|
+
return null;
|
|
178
|
+
const customId = str(o.custom_id, MAX_CUSTOM_ID_LEN);
|
|
179
|
+
const component = str(o.component, MAX_ID_LEN);
|
|
180
|
+
if (!customId || !component)
|
|
181
|
+
return null;
|
|
182
|
+
const values = {};
|
|
183
|
+
if (o.values && typeof o.values === "object") {
|
|
184
|
+
for (const [k, v] of Object.entries(o.values).slice(0, MAX_COMPONENTS)) {
|
|
185
|
+
const key = str(k, MAX_ID_LEN);
|
|
186
|
+
if (!key)
|
|
187
|
+
continue;
|
|
188
|
+
if (typeof v === "string") {
|
|
189
|
+
values[key] = v.slice(0, MAX_VALUE_LEN);
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
// File values are {fileId, name, size} — fileId is the content sha256,
|
|
193
|
+
// which is what makes the reference exact (Brief 30 §3).
|
|
194
|
+
if (v && typeof v === "object") {
|
|
195
|
+
const f = v;
|
|
196
|
+
const fileId = str(f.fileId, 128);
|
|
197
|
+
const name = str(f.name, MAX_LABEL_LEN);
|
|
198
|
+
const size = typeof f.size === "number" && Number.isFinite(f.size) ? f.size : undefined;
|
|
199
|
+
if (fileId && name && size !== undefined)
|
|
200
|
+
values[key] = { fileId, name, size };
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return { v: COMPONENTS_WIRE_VERSION, type: "interaction", custom_id: customId, component, values };
|
|
205
|
+
}
|
|
206
|
+
export function encodePacket(msg) {
|
|
207
|
+
return new Uint8Array(Buffer.from(JSON.stringify(msg), "utf-8"));
|
|
208
|
+
}
|
package/dist/desktop/app.js
CHANGED
|
@@ -57,6 +57,8 @@ var PeerWebRTC = (() => {
|
|
|
57
57
|
fitSignalForInvite: () => fitSignalForInvite,
|
|
58
58
|
looksLikeSignal: () => looksLikeSignal,
|
|
59
59
|
mediaDirection: () => mediaDirection,
|
|
60
|
+
orphanPayloadTypeLines: () => orphanPayloadTypeLines,
|
|
61
|
+
sanitizeSdp: () => sanitizeSdp,
|
|
60
62
|
shrinkSdpForInvite: () => shrinkSdpForInvite,
|
|
61
63
|
videoIsSending: () => videoIsSending
|
|
62
64
|
});
|
|
@@ -198,6 +200,29 @@ var PeerWebRTC = (() => {
|
|
|
198
200
|
}
|
|
199
201
|
return join(lines);
|
|
200
202
|
}
|
|
203
|
+
function orphanPayloadTypeLines(sdp) {
|
|
204
|
+
const out = [];
|
|
205
|
+
let pts;
|
|
206
|
+
for (const line of sdp.replace(/\r\n/g, "\n").split("\n")) {
|
|
207
|
+
if (line.startsWith("m=")) {
|
|
208
|
+
pts = new Set(line.split(" ").slice(3));
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
const m = pts && /^a=(?:rtpmap|rtcp-fb|fmtp):(\d+)\b/.exec(line);
|
|
212
|
+
if (m && !pts.has(m[1]))
|
|
213
|
+
out.push(line);
|
|
214
|
+
}
|
|
215
|
+
return out;
|
|
216
|
+
}
|
|
217
|
+
function sanitizeSdp(sdp) {
|
|
218
|
+
const orphans = orphanPayloadTypeLines(sdp);
|
|
219
|
+
if (orphans.length === 0)
|
|
220
|
+
return sdp;
|
|
221
|
+
const drop = new Set(orphans);
|
|
222
|
+
const crlf = sdp.includes("\r\n");
|
|
223
|
+
const kept = sdp.replace(/\r\n/g, "\n").split("\n").filter((l) => !drop.has(l));
|
|
224
|
+
return kept.join(crlf ? "\r\n" : "\n");
|
|
225
|
+
}
|
|
201
226
|
|
|
202
227
|
// node_modules/@decentnetwork/peer-webrtc/dist/signal.js
|
|
203
228
|
var SDP_TYPES = /* @__PURE__ */ new Set([
|
|
@@ -297,7 +322,7 @@ var PeerWebRTC = (() => {
|
|
|
297
322
|
var ICE_RESTART_MAX = 3;
|
|
298
323
|
var ICE_DISCONNECT_GRACE_MS = 4e3;
|
|
299
324
|
var ICE_FAILED_GRACE_MS = 12e4;
|
|
300
|
-
var _opts, _iceServers, _sessions, _restartOffersEnabled, _setOutgoingVideoTrack, setOutgoingVideoTrack_fn, _renegotiate, renegotiate_fn, _createOffer, createOffer_fn, _createAnswer, createAnswer_fn, _preferLeanVideoCodecs, preferLeanVideoCodecs_fn, _restartIce, restartIce_fn, _handleSignal, handleSignal_fn, _onOffer, onOffer_fn, _answerRenegotiation, answerRenegotiation_fn, _onAnswer, onAnswer_fn, _onCandidate, onCandidate_fn, _onBye, onBye_fn, _onAuxiliary, onAuxiliary_fn, _createSession, createSession_fn, _wirePeerConnection, wirePeerConnection_fn, _ensureRemoteStream, ensureRemoteStream_fn, _attachLocalMedia, attachLocalMedia_fn, _flushLocalCandidates, flushLocalCandidates_fn, _drainRemoteCandidates, drainRemoteCandidates_fn, _send, send_fn, _sendByeSafe, sendByeSafe_fn, _cleanup, cleanup_fn, _setState, setState_fn, _normId, normId_fn, _toInfo, _newCallId, newCallId_fn, _log, log_fn;
|
|
325
|
+
var _opts, _iceServers, _sessions, _restartOffersEnabled, _setOutgoingVideoTrack, setOutgoingVideoTrack_fn, _renegotiate, renegotiate_fn, _createOffer, createOffer_fn, _createAnswer, createAnswer_fn, _cleanOutbound, cleanOutbound_fn, _cleanInbound, cleanInbound_fn, _preferLeanVideoCodecs, preferLeanVideoCodecs_fn, _restartIce, restartIce_fn, _handleSignal, handleSignal_fn, _onOffer, onOffer_fn, _answerRenegotiation, answerRenegotiation_fn, _negotiationFailed, negotiationFailed_fn, _onAnswer, onAnswer_fn, _onCandidate, onCandidate_fn, _onBye, onBye_fn, _onAuxiliary, onAuxiliary_fn, _createSession, createSession_fn, _wirePeerConnection, wirePeerConnection_fn, _ensureRemoteStream, ensureRemoteStream_fn, _attachLocalMedia, attachLocalMedia_fn, _flushLocalCandidates, flushLocalCandidates_fn, _drainRemoteCandidates, drainRemoteCandidates_fn, _send, send_fn, _sendByeSafe, sendByeSafe_fn, _cleanup, cleanup_fn, _setState, setState_fn, _normId, normId_fn, _toInfo, _newCallId, newCallId_fn, _log, log_fn;
|
|
301
326
|
var CallEngine = class extends Emitter {
|
|
302
327
|
constructor(opts) {
|
|
303
328
|
var _a, _b;
|
|
@@ -309,10 +334,31 @@ var PeerWebRTC = (() => {
|
|
|
309
334
|
/** Create + send a fresh offer to renegotiate media (e.g. after adding a
|
|
310
335
|
* screen-share track). The peer applies it via #onOffer's renegotiation path. */
|
|
311
336
|
__privateAdd(this, _renegotiate);
|
|
312
|
-
/** createOffer with the lean codec set applied (see #preferLeanVideoCodecs)
|
|
337
|
+
/** createOffer with the lean codec set applied (see #preferLeanVideoCodecs)
|
|
338
|
+
* and the result sanitized (see #cleanOutbound). Every offer — initial,
|
|
339
|
+
* screen-share re-offer, ICE restart — goes through here. */
|
|
313
340
|
__privateAdd(this, _createOffer);
|
|
314
|
-
/** createAnswer with the lean codec set applied. */
|
|
341
|
+
/** createAnswer with the lean codec set applied and the result sanitized. */
|
|
315
342
|
__privateAdd(this, _createAnswer);
|
|
343
|
+
/**
|
|
344
|
+
* Strip orphan codec lines from a description WE produced, before it is
|
|
345
|
+
* applied locally and before it is sent.
|
|
346
|
+
*
|
|
347
|
+
* Firefox's setCodecPreferences drops VP9/AV1 from the m=video line but
|
|
348
|
+
* leaves their `a=fmtp:` / `a=rtcp-fb:` lines in place; Chrome's parser then
|
|
349
|
+
* rejects the whole re-offer ("Failed to parse codecs correctly") and the
|
|
350
|
+
* Firefox peer's screen share never shows up. Measured 2026-10-31 between
|
|
351
|
+
* the Firefox extension and a Chrome desktop, both on 0.2.16. The sanitizer
|
|
352
|
+
* is a no-op on a clean description (Chrome, Safari), so the same object
|
|
353
|
+
* comes back untouched there.
|
|
354
|
+
*/
|
|
355
|
+
__privateAdd(this, _cleanOutbound);
|
|
356
|
+
/**
|
|
357
|
+
* The same cleanup for a description the PEER sent, before setRemoteDescription:
|
|
358
|
+
* Firefox extensions already in the field (AMO 1.4.14 ships 0.2.16) still
|
|
359
|
+
* produce the orphan lines, and a receiver on this engine must accept them.
|
|
360
|
+
*/
|
|
361
|
+
__privateAdd(this, _cleanInbound);
|
|
316
362
|
/**
|
|
317
363
|
* Restrict video to the codecs the peers actually negotiate, so the SDP fits
|
|
318
364
|
* the 8192-byte Carrier invite channel. Chrome otherwise advertises VP9 and
|
|
@@ -335,6 +381,7 @@ var PeerWebRTC = (() => {
|
|
|
335
381
|
__privateAdd(this, _onOffer);
|
|
336
382
|
/** Apply a mid-call re-offer (renegotiation) and send the answer. */
|
|
337
383
|
__privateAdd(this, _answerRenegotiation);
|
|
384
|
+
__privateAdd(this, _negotiationFailed);
|
|
338
385
|
__privateAdd(this, _onAnswer);
|
|
339
386
|
__privateAdd(this, _onCandidate);
|
|
340
387
|
__privateAdd(this, _onBye);
|
|
@@ -450,7 +497,7 @@ var PeerWebRTC = (() => {
|
|
|
450
497
|
throw new Error(`accept: no pending incoming call ${callId}`);
|
|
451
498
|
}
|
|
452
499
|
await __privateMethod(this, _attachLocalMedia, attachLocalMedia_fn).call(this, session);
|
|
453
|
-
await session.pc.setRemoteDescription({ type: "offer", sdp: session.pendingOffer });
|
|
500
|
+
await session.pc.setRemoteDescription({ type: "offer", sdp: __privateMethod(this, _cleanInbound, cleanInbound_fn).call(this, session.pendingOffer, "offer") });
|
|
454
501
|
session.hasReceivedSdp = true;
|
|
455
502
|
session.pendingOffer = void 0;
|
|
456
503
|
await __privateMethod(this, _drainRemoteCandidates, drainRemoteCandidates_fn).call(this, session);
|
|
@@ -638,12 +685,30 @@ var PeerWebRTC = (() => {
|
|
|
638
685
|
_createOffer = new WeakSet();
|
|
639
686
|
createOffer_fn = async function(session, opts) {
|
|
640
687
|
__privateMethod(this, _preferLeanVideoCodecs, preferLeanVideoCodecs_fn).call(this, session.pc);
|
|
641
|
-
return session.pc.createOffer(opts);
|
|
688
|
+
return __privateMethod(this, _cleanOutbound, cleanOutbound_fn).call(this, await session.pc.createOffer(opts));
|
|
642
689
|
};
|
|
643
690
|
_createAnswer = new WeakSet();
|
|
644
691
|
createAnswer_fn = async function(session) {
|
|
645
692
|
__privateMethod(this, _preferLeanVideoCodecs, preferLeanVideoCodecs_fn).call(this, session.pc);
|
|
646
|
-
return session.pc.createAnswer();
|
|
693
|
+
return __privateMethod(this, _cleanOutbound, cleanOutbound_fn).call(this, await session.pc.createAnswer());
|
|
694
|
+
};
|
|
695
|
+
_cleanOutbound = new WeakSet();
|
|
696
|
+
cleanOutbound_fn = function(desc) {
|
|
697
|
+
var _a, _b;
|
|
698
|
+
const raw = (_a = desc.sdp) != null ? _a : "";
|
|
699
|
+
const sdp = sanitizeSdp(raw);
|
|
700
|
+
if (sdp === raw)
|
|
701
|
+
return desc;
|
|
702
|
+
__privateMethod(this, _log, log_fn).call(this, `${(_b = desc.type) != null ? _b : "sdp"}: removed ${orphanPayloadTypeLines(raw).length} orphan codec line(s) from our own description`);
|
|
703
|
+
return { type: desc.type, sdp };
|
|
704
|
+
};
|
|
705
|
+
_cleanInbound = new WeakSet();
|
|
706
|
+
cleanInbound_fn = function(raw, what) {
|
|
707
|
+
const sdp = sanitizeSdp(raw);
|
|
708
|
+
if (sdp !== raw) {
|
|
709
|
+
__privateMethod(this, _log, log_fn).call(this, `${what}: removed ${orphanPayloadTypeLines(raw).length} orphan codec line(s) from the peer's description`);
|
|
710
|
+
}
|
|
711
|
+
return sdp;
|
|
647
712
|
};
|
|
648
713
|
_preferLeanVideoCodecs = new WeakSet();
|
|
649
714
|
preferLeanVideoCodecs_fn = function(pc) {
|
|
@@ -733,14 +798,21 @@ var PeerWebRTC = (() => {
|
|
|
733
798
|
_answerRenegotiation = new WeakSet();
|
|
734
799
|
answerRenegotiation_fn = async function(session, sdp) {
|
|
735
800
|
try {
|
|
736
|
-
await session.pc.setRemoteDescription({ type: "offer", sdp });
|
|
801
|
+
await session.pc.setRemoteDescription({ type: "offer", sdp: __privateMethod(this, _cleanInbound, cleanInbound_fn).call(this, sdp, "offer") });
|
|
737
802
|
const answer = await __privateMethod(this, _createAnswer, createAnswer_fn).call(this, session);
|
|
738
803
|
await session.pc.setLocalDescription(answer);
|
|
739
804
|
await __privateMethod(this, _send, send_fn).call(this, session.peerId, { type: "answer", sdp: answer.sdp, callId: session.callId });
|
|
740
805
|
} catch (err) {
|
|
741
|
-
__privateMethod(this,
|
|
806
|
+
__privateMethod(this, _negotiationFailed, negotiationFailed_fn).call(this, session, "re-offer", err);
|
|
742
807
|
}
|
|
743
808
|
};
|
|
809
|
+
_negotiationFailed = new WeakSet();
|
|
810
|
+
negotiationFailed_fn = function(session, what, err) {
|
|
811
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
812
|
+
__privateMethod(this, _log, log_fn).call(this, `renegotiation failed (${what}) callId=${session.callId}: ${error.message}`);
|
|
813
|
+
console.warn(`[peer-webrtc] could not apply the peer's ${what} for call ${session.callId}: ${error.message}`);
|
|
814
|
+
this.emit("negotiationFailed", session.callId, error);
|
|
815
|
+
};
|
|
744
816
|
_onAnswer = new WeakSet();
|
|
745
817
|
onAnswer_fn = function(peerId, signal) {
|
|
746
818
|
const session = __privateGet(this, _sessions).get(__privateMethod(this, _normId, normId_fn).call(this, signal.callId));
|
|
@@ -749,14 +821,14 @@ var PeerWebRTC = (() => {
|
|
|
749
821
|
session.peerId = peerId;
|
|
750
822
|
void (async () => {
|
|
751
823
|
try {
|
|
752
|
-
await session.pc.setRemoteDescription({ type: "answer", sdp: signal.sdp });
|
|
824
|
+
await session.pc.setRemoteDescription({ type: "answer", sdp: __privateMethod(this, _cleanInbound, cleanInbound_fn).call(this, signal.sdp, "answer") });
|
|
753
825
|
session.hasReceivedSdp = true;
|
|
754
826
|
if (session.state !== "connected")
|
|
755
827
|
__privateMethod(this, _setState, setState_fn).call(this, session, "connecting");
|
|
756
828
|
await __privateMethod(this, _drainRemoteCandidates, drainRemoteCandidates_fn).call(this, session);
|
|
757
829
|
await __privateMethod(this, _flushLocalCandidates, flushLocalCandidates_fn).call(this, session);
|
|
758
830
|
} catch (err) {
|
|
759
|
-
__privateMethod(this,
|
|
831
|
+
__privateMethod(this, _negotiationFailed, negotiationFailed_fn).call(this, session, "answer", err);
|
|
760
832
|
}
|
|
761
833
|
})();
|
|
762
834
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decentnetwork/beagle",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.122",
|
|
4
4
|
"description": "Beagle — P2P chat, file transfer and calls for regular users, on the Decent Network. No admin privilege required.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"@decentnetwork/chat-components": "^0.1.3",
|
|
31
31
|
"@decentnetwork/lan": "^0.1.283",
|
|
32
32
|
"@decentnetwork/peer": "^0.1.168",
|
|
33
|
-
"@decentnetwork/peer-webrtc": "^0.2.
|
|
33
|
+
"@decentnetwork/peer-webrtc": "^0.2.17",
|
|
34
34
|
"js-yaml": "^4.1.0",
|
|
35
35
|
"ws": "^8.21.1",
|
|
36
36
|
"yargs": "^17.7.2"
|