wireweave 0.3.37 → 0.3.40
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/README.md +1 -32
- package/package.json +1 -1
- package/src/chat.js +9 -2
- package/src/data.js +5 -27
- package/src/relay-pool.js +6 -7
- package/src/voice.js +2 -8
package/README.md
CHANGED
|
@@ -83,38 +83,7 @@ session.broadcast(new Uint8Array(payload)); // → all open peers
|
|
|
83
83
|
session.send(somePeerPubkey, new Uint8Array(p)); // → one peer
|
|
84
84
|
```
|
|
85
85
|
|
|
86
|
-
Options: `dataChannelOptions` (default `{ ordered: true }`)
|
|
87
|
-
|
|
88
|
-
`setIceServers(list)` / `getIceServers()` (also exported from `wireweave/data` and `wireweave/voice`) override the module-wide default ICE server list for every session created afterward — useful for pointing at your own TURN infrastructure once, instead of passing `iceServers` to each session.
|
|
89
|
-
|
|
90
|
-
## Node hosts behind NAT: `createPeerConnection`
|
|
91
|
-
|
|
92
|
-
Both `DataSession` and `VoiceSession` accept a `createPeerConnection(config)` option. It defaults to a plain `new RTCPeerConnection(config)` (browser-shaped, works as-is with any WebRTC-polyfilled `globalThis`) — wireweave itself never imports a Node-specific WebRTC binding. A Node host that is itself likely to sit behind a restrictive NAT (a CLI tool, a headless server) can instead construct its own natively-tuned peer and hand it back:
|
|
93
|
-
|
|
94
|
-
```js
|
|
95
|
-
import * as ndc from 'node-datachannel';
|
|
96
|
-
import { RTCPeerConnection as PolyfillRTCPeerConnection } from 'node-datachannel/polyfill';
|
|
97
|
-
|
|
98
|
-
const session = createDataSession({
|
|
99
|
-
fsm, xstate, relayPool: pool, auth, namespace: 'mygame',
|
|
100
|
-
createPeerConnection: (config) => {
|
|
101
|
-
// node-datachannel's native RtcConfig is richer than the W3C shape:
|
|
102
|
-
// enableIceUdpMux shares one UDP port across all peer connections
|
|
103
|
-
// (fewer ports to traverse through a firewall); portRangeBegin/End
|
|
104
|
-
// pins ICE to a fixed range you can port-forward; proxyServer routes
|
|
105
|
-
// ICE through a SOCKS5/HTTP proxy on networks that block direct UDP/TCP.
|
|
106
|
-
const nativePc = new ndc.PeerConnection('peer', {
|
|
107
|
-
iceServers: config.iceServers.map(s => s.urls),
|
|
108
|
-
enableIceUdpMux: true,
|
|
109
|
-
// portRangeBegin: 50000, portRangeEnd: 51000,
|
|
110
|
-
// proxyServer: { type: 'Socks5', ip: '127.0.0.1', port: 1080 }
|
|
111
|
-
});
|
|
112
|
-
return new PolyfillRTCPeerConnection({ peerConnection: nativePc });
|
|
113
|
-
}
|
|
114
|
-
});
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
This is opt-in and additive — omit `createPeerConnection` and Node hosts behave exactly as before (a plain polyfilled `RTCPeerConnection`).
|
|
86
|
+
Options: `dataChannelOptions` (default `{ ordered: true }`) and `iceServers` (override the default STUN/TURN list). Construct multiple `DataSession`s in the same page to use distinct rooms / channel configurations.
|
|
118
87
|
|
|
119
88
|
## game / 3-mode usage pattern
|
|
120
89
|
|
package/package.json
CHANGED
package/src/chat.js
CHANGED
|
@@ -24,7 +24,7 @@ export class Chat extends EventTarget {
|
|
|
24
24
|
return this._sendTimes[0] + this.rateLimitWindowMs - now;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
async send(content, { announcement = false } = {}) {
|
|
27
|
+
async send(content, { announcement = false, replyTo = null } = {}) {
|
|
28
28
|
const { channelId, serverId } = this.getChannelContext();
|
|
29
29
|
if (!this.auth.isLoggedIn() || !channelId) return;
|
|
30
30
|
if (announcement && !this.isAdmin(serverId)) return;
|
|
@@ -37,6 +37,7 @@ export class Chat extends EventTarget {
|
|
|
37
37
|
this._sendTimes.push(Date.now());
|
|
38
38
|
const chanHex = await hexChannelId(channelId, serverId);
|
|
39
39
|
const tags = [['e', chanHex, '', 'root']];
|
|
40
|
+
if (replyTo?.id) tags.push(['e', replyTo.id, '', 'reply']);
|
|
40
41
|
if (announcement) tags.push(['t', 'announcement']);
|
|
41
42
|
const signed = await this.auth.sign({ kind: 42, created_at: Math.floor(Date.now() / 1000), tags, content: trimmed });
|
|
42
43
|
this.pool.publish(signed);
|
|
@@ -82,7 +83,13 @@ export class Chat extends EventTarget {
|
|
|
82
83
|
_eventToMsg(event) {
|
|
83
84
|
const tTags = (event.tags || []).filter(t => t[0] === 't').map(t => t[1]);
|
|
84
85
|
this._fetchProfile(event.pubkey);
|
|
85
|
-
|
|
86
|
+
const replyTag = (event.tags || []).find(t => t[0] === 'e' && t[3] === 'reply');
|
|
87
|
+
let replyTo = null;
|
|
88
|
+
if (replyTag) {
|
|
89
|
+
const cached = this.messages.find(m => m.id === replyTag[1]);
|
|
90
|
+
replyTo = cached ? { id: cached.id, userId: cached.userId, content: cached.content } : { id: replyTag[1] };
|
|
91
|
+
}
|
|
92
|
+
return { id: event.id, type: 'text', userId: event.pubkey, content: event.content, timestamp: event.created_at * 1000, tags: tTags, replyTo };
|
|
86
93
|
}
|
|
87
94
|
|
|
88
95
|
_addMessage(msg) {
|
package/src/data.js
CHANGED
|
@@ -1,23 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
// peers sit behind symmetric or restricted-cone NAT (typical home routers,
|
|
3
|
-
// most carrier-grade NAT, corporate networks). UDP, TCP, and TLS TURN variants
|
|
4
|
-
// are included so at least one path survives strict egress filtering. Kept in
|
|
5
|
-
// sync with voice.js's DEFAULT_ICE_SERVERS.
|
|
6
|
-
const DEFAULT_ICE_SERVERS = [
|
|
1
|
+
const ICE_SERVERS = [
|
|
7
2
|
{ urls: 'stun:stun.l.google.com:19302' },
|
|
8
3
|
{ urls: 'stun:stun1.l.google.com:19302' },
|
|
9
4
|
{ urls: 'stun:stun.cloudflare.com:3478' },
|
|
10
|
-
{ urls: '
|
|
11
|
-
{ urls: 'turn:
|
|
12
|
-
{ urls: 'turn:global.relay.metered.ca:80?transport=tcp', username: 'openrelayproject', credential: 'openrelayproject' },
|
|
13
|
-
{ urls: 'turn:global.relay.metered.ca:443', username: 'openrelayproject', credential: 'openrelayproject' },
|
|
14
|
-
{ urls: 'turns:global.relay.metered.ca:443?transport=tcp', username: 'openrelayproject', credential: 'openrelayproject' },
|
|
15
|
-
// Legacy hostname kept as a low-priority fallback in case some deployments still resolve it.
|
|
5
|
+
{ urls: 'turn:openrelay.metered.ca:80', username: 'openrelayproject', credential: 'openrelayproject' },
|
|
6
|
+
{ urls: 'turn:openrelay.metered.ca:443', username: 'openrelayproject', credential: 'openrelayproject' },
|
|
16
7
|
{ urls: 'turn:openrelay.metered.ca:443?transport=tcp', username: 'openrelayproject', credential: 'openrelayproject' }
|
|
17
8
|
];
|
|
18
|
-
let ICE_SERVERS = DEFAULT_ICE_SERVERS;
|
|
19
|
-
export const setIceServers = (list) => { if (Array.isArray(list) && list.length) ICE_SERVERS = list; };
|
|
20
|
-
export const getIceServers = () => ICE_SERVERS.slice();
|
|
21
9
|
|
|
22
10
|
const PRESENCE_EXPIRY = 300000;
|
|
23
11
|
const HEARTBEAT = 30000;
|
|
@@ -29,24 +17,14 @@ const deriveRoomId = async (namespace, room) => {
|
|
|
29
17
|
return 'wwdata' + Array.from(new Uint8Array(h)).map(b => b.toString(16).padStart(2, '0')).join('').slice(0, 16);
|
|
30
18
|
};
|
|
31
19
|
|
|
32
|
-
// Default: a plain browser-shaped RTCPeerConnection. Node hosts that want
|
|
33
|
-
// deeper NAT-traversal tuning (ICE/UDP port muxing, a fixed port range for
|
|
34
|
-
// port-forwarded firewalls, a SOCKS5/HTTP proxy for locked-down networks)
|
|
35
|
-
// can pass createPeerConnection to construct their own native peer (e.g.
|
|
36
|
-
// node-datachannel's PeerConnection with its richer RtcConfig) and hand it
|
|
37
|
-
// back wrapped as an RTCPeerConnection-shaped object — wireweave itself never
|
|
38
|
-
// depends on any Node-specific WebRTC binding.
|
|
39
|
-
const defaultCreatePeerConnection = (config) => new RTCPeerConnection(config);
|
|
40
|
-
|
|
41
20
|
export class DataSession extends EventTarget {
|
|
42
|
-
constructor({ fsm, xstate, relayPool, auth, namespace = '', dataChannelOptions = { ordered: true }, iceServers = null
|
|
21
|
+
constructor({ fsm, xstate, relayPool, auth, namespace = '', dataChannelOptions = { ordered: true }, iceServers = null }) {
|
|
43
22
|
super();
|
|
44
23
|
if (!fsm || !xstate || !relayPool || !auth) throw new Error('DataSession: missing deps');
|
|
45
24
|
this.fsm = fsm; this.xstate = xstate; this.pool = relayPool; this.auth = auth;
|
|
46
25
|
this.namespace = namespace;
|
|
47
26
|
this.dcOptions = dataChannelOptions;
|
|
48
27
|
this.iceServers = iceServers || ICE_SERVERS;
|
|
49
|
-
this.createPeerConnection = createPeerConnection;
|
|
50
28
|
this.actor = null;
|
|
51
29
|
this.room = ''; this.roomId = '';
|
|
52
30
|
this.peers = new Map(); this.participants = new Map();
|
|
@@ -174,7 +152,7 @@ export class DataSession extends EventTarget {
|
|
|
174
152
|
fsmActor.start();
|
|
175
153
|
const peer = { pc: null, dc: null, pendingCandidates: [], bufferedCandidates: [], iceTimer: null, disconnectTimer: null, failCount: 0, state: 'new', fsm: fsmActor, remoteDescSet: false };
|
|
176
154
|
this.peers.set(peerPubkey, peer);
|
|
177
|
-
const pc =
|
|
155
|
+
const pc = new RTCPeerConnection({ iceServers: this.iceServers, bundlePolicy: 'max-bundle' });
|
|
178
156
|
peer.pc = pc;
|
|
179
157
|
const isOfferer = this.auth.pubkey > peerPubkey;
|
|
180
158
|
this._wirePeer(peer, peerPubkey, fsmActor, isOfferer);
|
package/src/relay-pool.js
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
|
-
// Ordered fastest-first by measured connect latency; dead relays removed so
|
|
2
|
-
// signaling reaches a live relay immediately instead of racing dead hosts.
|
|
3
|
-
// (relay.nostr.band / nostr.wine / relay.current.fyi / relay.0xchat.com were
|
|
4
|
-
// unreachable — DNS-dead or refusing connections — and only added connect
|
|
5
|
-
// latency and console noise. Re-audit periodically.)
|
|
6
1
|
const DEFAULT_RELAYS = [
|
|
2
|
+
'wss://relay.damus.io',
|
|
7
3
|
'wss://relay.primal.net',
|
|
8
4
|
'wss://nos.lol',
|
|
5
|
+
'wss://relay.snort.social',
|
|
6
|
+
'wss://relay.nostr.band',
|
|
7
|
+
'wss://nostr.wine',
|
|
8
|
+
'wss://relay.current.fyi',
|
|
9
9
|
'wss://offchain.pub',
|
|
10
10
|
'wss://nostr-pub.wellorder.net',
|
|
11
|
-
'wss://relay.
|
|
12
|
-
'wss://relay.damus.io'
|
|
11
|
+
'wss://relay.0xchat.com'
|
|
13
12
|
];
|
|
14
13
|
|
|
15
14
|
const SEEN_MAX = 10000;
|
package/src/voice.js
CHANGED
|
@@ -41,19 +41,13 @@ const deriveRoomId = async (serverId, channel) => {
|
|
|
41
41
|
return 'zellous' + Array.from(new Uint8Array(h)).map(b => b.toString(16).padStart(2, '0')).join('').slice(0, 16);
|
|
42
42
|
};
|
|
43
43
|
|
|
44
|
-
// See data.js's defaultCreatePeerConnection for why this hook exists: it lets
|
|
45
|
-
// a Node host inject a natively-tuned peer (ICE/UDP muxing, fixed port range,
|
|
46
|
-
// proxy passthrough) without wireweave depending on any Node WebRTC binding.
|
|
47
|
-
const defaultCreatePeerConnection = (config) => new RTCPeerConnection(config);
|
|
48
|
-
|
|
49
44
|
export class VoiceSession extends EventTarget {
|
|
50
|
-
constructor({ fsm, xstate, relayPool, auth, mediaDevices, bans = null, serverId = '', onAudioTrack = null, onVideoTrack = null
|
|
45
|
+
constructor({ fsm, xstate, relayPool, auth, mediaDevices, bans = null, serverId = '', onAudioTrack = null, onVideoTrack = null }) {
|
|
51
46
|
super();
|
|
52
47
|
if (!fsm || !xstate || !relayPool || !auth || !mediaDevices) throw new Error('VoiceSession: missing deps');
|
|
53
48
|
this.fsm = fsm; this.xstate = xstate; this.pool = relayPool; this.auth = auth; this.md = mediaDevices; this.bans = bans;
|
|
54
49
|
this.serverId = serverId;
|
|
55
50
|
this.onAudioTrack = onAudioTrack; this.onVideoTrack = onVideoTrack;
|
|
56
|
-
this.createPeerConnection = createPeerConnection;
|
|
57
51
|
this.actor = null;
|
|
58
52
|
this.channelName = ''; this.roomId = '';
|
|
59
53
|
this.peers = new Map(); this.participants = new Map();
|
|
@@ -526,7 +520,7 @@ export class VoiceSession extends EventTarget {
|
|
|
526
520
|
fsmActor.start();
|
|
527
521
|
const peer = { pc: null, audioEl: null, pendingCandidates: [], bufferedCandidates: [], iceTimer: null, disconnectTimer: null, failCount: 0, state: 'new', fsm: fsmActor, _stallInterval: null, remoteDescSet: false, trackEndedRestart: false };
|
|
528
522
|
this.peers.set(peerPubkey, peer);
|
|
529
|
-
const pc =
|
|
523
|
+
const pc = new RTCPeerConnection({ iceServers: ICE_SERVERS, bundlePolicy: 'max-bundle', iceCandidatePoolSize: 4, iceTransportPolicy: 'all' });
|
|
530
524
|
peer.pc = pc;
|
|
531
525
|
const isOfferer = this.auth.pubkey > peerPubkey;
|
|
532
526
|
if (isOfferer) {
|