@decentnetwork/beagle 0.1.120 → 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.
@@ -1,4 +1,4 @@
1
- window.__DK_UI_VERSION="0.1.120";
1
+ window.__DK_UI_VERSION="0.1.122";
2
2
  "use strict";
3
3
  (() => {
4
4
  // packages/beagle-ui/dist/beagle-ui.js
@@ -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, _log, log_fn).call(this, `renegotiation answer failed: ${err.message}`);
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, _log, log_fn).call(this, `setRemoteDescription(answer) failed: ${err.message}`);
831
+ __privateMethod(this, _negotiationFailed, negotiationFailed_fn).call(this, session, "answer", err);
760
832
  }
761
833
  })();
762
834
  };
@@ -0,0 +1,25 @@
1
+ export interface FriendStoreEntry {
2
+ requestedAt?: number;
3
+ acceptedAt?: number;
4
+ /** CryptoPunks id the friend advertised in its userinfo (Beagle's avatar). */
5
+ punkId?: number;
6
+ platform?: string;
7
+ appVersion?: string;
8
+ }
9
+ /** A punk id as the store or a backend may carry it: a finite non-negative
10
+ * number. Punk 0 is a real CryptoPunk; a string or a negative is not one. */
11
+ export declare function punkIdOf(v: unknown): number | undefined;
12
+ /** Parse the store file's JSON (an array, `{friends:[…]}`, or an object keyed
13
+ * by id — the SDK has written all three) into a map by userid. */
14
+ export declare function parseFriendStore(raw: unknown): Map<string, FriendStoreEntry>;
15
+ /** The friend store next to `keyFile`, by userid. Unreadable or absent (a
16
+ * fresh install has no friends yet): whatever we last read, else nothing —
17
+ * the list still renders, just without stamps or pushed avatars. */
18
+ export declare function readFriendStore(keyFile: string | undefined): Map<string, FriendStoreEntry>;
19
+ /** Which avatar to show for a friend. What they advertised over Carrier wins
20
+ * over the directory: it is what they are using on their device right now,
21
+ * it needs no gateway, and it is the only avatar a friend who never
22
+ * registered a beagles.eth name will ever have. The backend's own report
23
+ * first, then the SDK's store on disk (an older daemon reports none), then
24
+ * the beagles.eth record. */
25
+ export declare function pickPunk(pushed: unknown, stored: number | undefined, directory: number | null | undefined): number | null;
@@ -0,0 +1,75 @@
1
+ // The SDK's own friend store (`<keyFile>.friends.json`, next to the identity)
2
+ // is the source of truth both backends write, and it is a file in our own
3
+ // config dir, so the UI server reads it for what the backend's friend list
4
+ // does not report. Two things live here:
5
+ //
6
+ // - the stamps (requestedAt / acceptedAt): a daemon backend reports only
7
+ // acceptedAt (as lastSeen) and nothing for a request we sent, so a friend
8
+ // just added had no stamp at all and sorted to the bottom;
9
+ // - what the friend pushed about itself (punk avatar, platform, app
10
+ // version): lan ≤ 0.1.308 dropped these from its friends-list, so on a
11
+ // daemon backend only a beagles.eth record could supply an avatar — on
12
+ // mac-dev 8 of 16 pushed punks never reached the screen (2026-10-31).
13
+ //
14
+ // Cached by mtime: the list is polled every 2.5s per tab.
15
+ import { readFileSync, statSync } from "node:fs";
16
+ /** A punk id as the store or a backend may carry it: a finite non-negative
17
+ * number. Punk 0 is a real CryptoPunk; a string or a negative is not one. */
18
+ export function punkIdOf(v) {
19
+ return typeof v === "number" && Number.isFinite(v) && v >= 0 ? v : undefined;
20
+ }
21
+ /** Parse the store file's JSON (an array, `{friends:[…]}`, or an object keyed
22
+ * by id — the SDK has written all three) into a map by userid. */
23
+ export function parseFriendStore(raw) {
24
+ const recs = Array.isArray(raw)
25
+ ? raw
26
+ : raw && typeof raw === "object" && Array.isArray(raw.friends)
27
+ ? raw.friends
28
+ : Object.values(raw ?? {});
29
+ const map = new Map();
30
+ for (const r of recs) {
31
+ if (!r || typeof r !== "object")
32
+ continue;
33
+ const id = r.userid || r.pubkey;
34
+ if (!id)
35
+ continue;
36
+ map.set(id, {
37
+ requestedAt: typeof r.requestedAt === "number" ? r.requestedAt : undefined,
38
+ acceptedAt: typeof r.acceptedAt === "number" ? r.acceptedAt : undefined,
39
+ punkId: punkIdOf(r.punkId),
40
+ platform: typeof r.platform === "string" ? r.platform : undefined,
41
+ appVersion: typeof r.appVersion === "string" ? r.appVersion : undefined,
42
+ });
43
+ }
44
+ return map;
45
+ }
46
+ let cache = null;
47
+ /** The friend store next to `keyFile`, by userid. Unreadable or absent (a
48
+ * fresh install has no friends yet): whatever we last read, else nothing —
49
+ * the list still renders, just without stamps or pushed avatars. */
50
+ export function readFriendStore(keyFile) {
51
+ const empty = new Map();
52
+ if (!keyFile)
53
+ return empty;
54
+ const path = `${keyFile}.friends.json`;
55
+ try {
56
+ const st = statSync(path);
57
+ if (cache && cache.path === path && cache.mtimeMs === st.mtimeMs)
58
+ return cache.map;
59
+ const map = parseFriendStore(JSON.parse(readFileSync(path, "utf-8")));
60
+ cache = { path, mtimeMs: st.mtimeMs, map };
61
+ return map;
62
+ }
63
+ catch {
64
+ return cache?.path === path ? cache.map : empty;
65
+ }
66
+ }
67
+ /** Which avatar to show for a friend. What they advertised over Carrier wins
68
+ * over the directory: it is what they are using on their device right now,
69
+ * it needs no gateway, and it is the only avatar a friend who never
70
+ * registered a beagles.eth name will ever have. The backend's own report
71
+ * first, then the SDK's store on disk (an older daemon reports none), then
72
+ * the beagles.eth record. */
73
+ export function pickPunk(pushed, stored, directory) {
74
+ return punkIdOf(pushed) ?? stored ?? directory ?? null;
75
+ }
package/dist/server.js CHANGED
@@ -25,6 +25,7 @@ import * as peerAddr from "@decentnetwork/peer";
25
25
  import { bridgeRoster } from "./bridge-roster.js";
26
26
  import { isSupervised } from "./relaunch.js";
27
27
  import { historyStats } from "./assistant-stats.js";
28
+ import { readFriendStore, pickPunk } from "./friend-store.js";
28
29
  import { createDiscoverFetch } from "./discover-cache.js";
29
30
  import { applyWalletBind } from "@decentnetwork/beagle-ui/wallet-proof";
30
31
  // Directory holding the built desktop UI bundle (index.html, app.js, vendor/).
@@ -61,50 +62,6 @@ async function ensFetchJson(path) {
61
62
  clearTimeout(timer);
62
63
  }
63
64
  }
64
- /** requestedAt / acceptedAt per friend, read from the SDK's own friend store
65
- * (`<keyFile>.friends.json`, next to the identity). The daemon's friends-list
66
- * reports neither for a request we sent and only acceptedAt (as lastSeen)
67
- * otherwise, so a friend just added had no stamp at all and sorted to the
68
- * bottom — the third time this was reported. The store is the source of
69
- * truth both backends write, and it is a file in our own config dir, so
70
- * read it. Cached by mtime: the list is polled every 2.5s per tab. */
71
- let friendStampsCache = null;
72
- function friendStoreStamps(keyFile) {
73
- const empty = new Map();
74
- if (!keyFile)
75
- return empty;
76
- const path = `${keyFile}.friends.json`;
77
- try {
78
- const st = statSync(path);
79
- if (friendStampsCache && friendStampsCache.mtimeMs === st.mtimeMs)
80
- return friendStampsCache.map;
81
- const raw = JSON.parse(readFileSync(path, "utf-8"));
82
- const recs = Array.isArray(raw)
83
- ? raw
84
- : raw && typeof raw === "object" && Array.isArray(raw.friends)
85
- ? raw.friends
86
- : Object.values(raw ?? {});
87
- const map = new Map();
88
- for (const r of recs) {
89
- if (!r || typeof r !== "object")
90
- continue;
91
- const id = r.userid || r.pubkey;
92
- if (!id)
93
- continue;
94
- map.set(id, {
95
- requestedAt: typeof r.requestedAt === "number" ? r.requestedAt : undefined,
96
- acceptedAt: typeof r.acceptedAt === "number" ? r.acceptedAt : undefined,
97
- });
98
- }
99
- friendStampsCache = { mtimeMs: st.mtimeMs, map };
100
- return map;
101
- }
102
- catch {
103
- // Unreadable or absent (a fresh install has no friends yet): whatever we
104
- // last read, else nothing — the list still renders, just without stamps.
105
- return friendStampsCache?.map ?? empty;
106
- }
107
- }
108
65
  /** The machine-local service-peer overlay. Absent file = nothing adopted yet,
109
66
  * which is the normal state, not an error. */
110
67
  function readLocalServicePeers(path) {
@@ -2071,7 +2028,7 @@ export function startBeagleServer(opts) {
2071
2028
  beagleVer: beagleVersion(),
2072
2029
  };
2073
2030
  const fl = (flist.ok ? (flist.data?.friends ?? []) : []);
2074
- const stamps = friendStoreStamps(opts.callIce?.keyFile);
2031
+ const stamps = readFriendStore(opts.callIce?.keyFile);
2075
2032
  const peers = fl.map((f) => {
2076
2033
  const uid = f.userid ?? "";
2077
2034
  const sess = sessByUserid.get(uid);
@@ -2126,11 +2083,13 @@ export function startBeagleServer(opts) {
2126
2083
  requestedAt: f.requestedAt ?? stamp?.requestedAt ?? 0,
2127
2084
  wire: "163",
2128
2085
  avatarUrl: ensPub.get(uid)?.avatarUrl ?? null,
2129
- // What they advertised over Carrier wins over the directory: it is
2130
- // what they are using on their device right now, it needs no
2131
- // gateway, and it is the only avatar a friend who never registered
2132
- // a beagles.eth name will ever have.
2133
- punkId: f.punkId ?? ensPub.get(uid)?.punkId ?? null,
2086
+ // What they advertised over Carrier wins over the directory (see
2087
+ // pickPunk). The daemon's list only carries the pushed punk from
2088
+ // lan 0.1.309; the SDK's store on disk covers an older daemon.
2089
+ punkId: pickPunk(f.punkId, stamp?.punkId, ensPub.get(uid)?.punkId),
2090
+ // The client they advertised with their profile, for the UI.
2091
+ platform: f.platform ?? stamp?.platform ?? null,
2092
+ appVersion: f.appVersion ?? stamp?.appVersion ?? null,
2134
2093
  };
2135
2094
  });
2136
2095
  const pend = pending.ok ? (pending.data?.pending ?? []) : [];
@@ -2339,7 +2298,7 @@ export function startBeagleServer(opts) {
2339
2298
  opts.call({ op: "friends-list" }),
2340
2299
  ensPublicByUserid(300),
2341
2300
  ]);
2342
- const stamps = friendStoreStamps(opts.callIce?.keyFile);
2301
+ const stamps = readFriendStore(opts.callIce?.keyFile);
2343
2302
  const stats = historyStats(opts.messagesPath);
2344
2303
  const fl = flist.ok ? (flist.data?.friends ?? []) : [];
2345
2304
  const friends = fl.map((f) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decentnetwork/beagle",
3
- "version": "0.1.120",
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.16",
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"