freertc 0.1.12 → 0.1.13
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 -1
- package/package.json +1 -1
- package/scripts/non-cloudflare-server.mjs +1 -1
- package/src/index.js +77 -95
package/README.md
CHANGED
|
@@ -216,7 +216,7 @@ Quick checks:
|
|
|
216
216
|
Expected `/health` response includes JSON like:
|
|
217
217
|
|
|
218
218
|
```json
|
|
219
|
-
{"ok":true,"version":"0.1.
|
|
219
|
+
{"ok":true,"version":"0.1.13","protocol_version":"1.0","peers":0}
|
|
220
220
|
```
|
|
221
221
|
|
|
222
222
|
## Auto WebRTC two-tab test
|
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
7
7
|
import { WebSocketServer } from 'ws';
|
|
8
8
|
|
|
9
9
|
const PSP_VERSION = '1.0';
|
|
10
|
-
const WORKER_VERSION = '0.1.
|
|
10
|
+
const WORKER_VERSION = '0.1.13';
|
|
11
11
|
const DEFAULT_TTL_MS = 30_000;
|
|
12
12
|
const MAX_TTL_MS = 120_000;
|
|
13
13
|
const MAX_MESSAGE_SIZE = 64 * 1024;
|
package/src/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const PSP_VERSION = "1.0";
|
|
2
|
-
const WORKER_VERSION = "0.1.
|
|
2
|
+
const WORKER_VERSION = "0.1.13";
|
|
3
3
|
|
|
4
4
|
const DISCOVERY_TYPES = new Set(["announce", "withdraw", "discover", "peer_list", "redirect"]);
|
|
5
5
|
const NEGOTIATION_TYPES = new Set(["connect_request", "connect_accept", "connect_reject", "offer", "answer", "ice_candidate", "ice_end", "renegotiate"]);
|
|
@@ -83,6 +83,20 @@ export default {
|
|
|
83
83
|
return jsonResponse({ ok: false, error: "Method not allowed" }, 405);
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
if (url.pathname === "/api/v1/peers") {
|
|
87
|
+
if (request.method === "GET") {
|
|
88
|
+
return handleListPeers(request, env);
|
|
89
|
+
}
|
|
90
|
+
return jsonResponse({ ok: false, error: "Method not allowed" }, 405);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (url.pathname === "/api/v1/relay") {
|
|
94
|
+
if (request.method === "POST") {
|
|
95
|
+
return handleRelayForward(request, env);
|
|
96
|
+
}
|
|
97
|
+
return jsonResponse({ ok: false, error: "Method not allowed" }, 405);
|
|
98
|
+
}
|
|
99
|
+
|
|
86
100
|
return env.ASSETS?.fetch(request) ?? new Response("Not Found", { status: 404 });
|
|
87
101
|
}
|
|
88
102
|
};
|
|
@@ -118,6 +132,51 @@ async function handleRegisterRelay(request, env) {
|
|
|
118
132
|
return jsonResponse({ ok: true, relays });
|
|
119
133
|
}
|
|
120
134
|
|
|
135
|
+
async function handleListPeers(request, env) {
|
|
136
|
+
if (!env.DB) return jsonResponse({ ok: false, error: "No database" }, 503);
|
|
137
|
+
const url = new URL(request.url);
|
|
138
|
+
const network = (url.searchParams.get("network") || "").trim();
|
|
139
|
+
const excludePeerId = (url.searchParams.get("exclude") || "").trim();
|
|
140
|
+
if (!network) return jsonResponse({ ok: false, error: "Missing network" }, 400);
|
|
141
|
+
|
|
142
|
+
const peers = await findPeers(env.DB, network, excludePeerId);
|
|
143
|
+
return jsonResponse({ ok: true, peers });
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async function handleRelayForward(request, env) {
|
|
147
|
+
if (!env.DB) return jsonResponse({ ok: false, error: "No database" }, 503);
|
|
148
|
+
|
|
149
|
+
let body;
|
|
150
|
+
try {
|
|
151
|
+
body = await request.json();
|
|
152
|
+
} catch {
|
|
153
|
+
return jsonResponse({ ok: false, error: "Invalid JSON" }, 400);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const message = body?.message || body;
|
|
157
|
+
if (!validEnvelope(message)) {
|
|
158
|
+
return jsonResponse({ ok: false, error: "Invalid PSP envelope" }, 400);
|
|
159
|
+
}
|
|
160
|
+
if (!RELAY_TYPES.has(message.type)) {
|
|
161
|
+
return jsonResponse({ ok: false, error: "Unsupported relay message type" }, 400);
|
|
162
|
+
}
|
|
163
|
+
if (!message.to || typeof message.to !== "string") {
|
|
164
|
+
return jsonResponse({ ok: false, error: "Missing destination peer" }, 400);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const liveKey = `${message.network}:${message.to}`;
|
|
168
|
+
const live = livePeers.get(liveKey);
|
|
169
|
+
if (live) {
|
|
170
|
+
try {
|
|
171
|
+
live.socket.send(JSON.stringify(message));
|
|
172
|
+
return jsonResponse({ ok: true, delivered: true }, 200);
|
|
173
|
+
} catch {}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
await insertRelayMessage(env.DB, message);
|
|
177
|
+
return jsonResponse({ ok: true, delivered: false, queued: true }, 202);
|
|
178
|
+
}
|
|
179
|
+
|
|
121
180
|
// POST to the global hub; cache returned relay list into own D1 so both sides know each other
|
|
122
181
|
async function registerWithHub(env, selfUrl) {
|
|
123
182
|
const resp = await fetch(`${relayHttpBase(normalizeRelayUrl(env.GLOBAL_RELAY_URL))}/api/v1/relays`, {
|
|
@@ -162,106 +221,30 @@ function relayHttpBase(wsUrl) {
|
|
|
162
221
|
return wsUrl.replace(/^wss?:\/\//, (m) => m === "wss://" ? "https://" : "http://").replace(/\/ws$/, "");
|
|
163
222
|
}
|
|
164
223
|
|
|
165
|
-
//
|
|
166
|
-
async function queryRelayForPeers(relayUrl, network,
|
|
224
|
+
// Query peer list from a remote relay over HTTP.
|
|
225
|
+
async function queryRelayForPeers(relayUrl, network, requesterPeerId) {
|
|
167
226
|
try {
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
const relayPeerId = selfRelayId || "relay-bridge";
|
|
177
|
-
|
|
178
|
-
ws.addEventListener("message", async (ev) => {
|
|
179
|
-
try {
|
|
180
|
-
const msg = JSON.parse(ev.data);
|
|
181
|
-
// Ignore broadcast peer_list updates triggered by announce heartbeats.
|
|
182
|
-
// We only want the direct discover response addressed to this bridge peer.
|
|
183
|
-
if (
|
|
184
|
-
msg.type === "peer_list" &&
|
|
185
|
-
msg.network === network &&
|
|
186
|
-
msg.to === relayPeerId &&
|
|
187
|
-
!gotPeerList
|
|
188
|
-
) {
|
|
189
|
-
gotPeerList = true;
|
|
190
|
-
clearTimeout(timer);
|
|
191
|
-
ws.close();
|
|
192
|
-
resolve((msg.body?.peers || []).map(p => ({ ...p, relay_url: relayUrl })));
|
|
193
|
-
}
|
|
194
|
-
// Cache any relay list the remote sends us via ext
|
|
195
|
-
if (msg.type === "ext" && msg.body?.action === "relay_list" && db) {
|
|
196
|
-
const remoteRelays = msg.body.relays || [];
|
|
197
|
-
await Promise.all(
|
|
198
|
-
remoteRelays.map(r => r.url ? upsertRelay(db, r.url, r.name || null).catch(() => {}) : null)
|
|
199
|
-
);
|
|
200
|
-
}
|
|
201
|
-
} catch {}
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
ws.addEventListener("error", () => { clearTimeout(timer); resolve([]); });
|
|
205
|
-
ws.addEventListener("close", () => { if (!gotPeerList) { clearTimeout(timer); resolve([]); } });
|
|
206
|
-
|
|
207
|
-
// Announce as relay bridge
|
|
208
|
-
ws.send(JSON.stringify({
|
|
209
|
-
psp_version: PSP_VERSION, type: "announce", network,
|
|
210
|
-
from: relayPeerId, message_id: crypto.randomUUID(),
|
|
211
|
-
timestamp: Date.now(), ttl_ms: 10_000, body: { capabilities: { relay: true } }
|
|
212
|
-
}));
|
|
213
|
-
// Share our known relay list so the remote can cache us
|
|
214
|
-
if (selfKnownRelays?.length) {
|
|
215
|
-
ws.send(JSON.stringify({
|
|
216
|
-
psp_version: PSP_VERSION, type: "ext", network,
|
|
217
|
-
from: relayPeerId, message_id: crypto.randomUUID(),
|
|
218
|
-
timestamp: Date.now(), ttl_ms: 10_000,
|
|
219
|
-
body: { action: "relay_list", relays: selfKnownRelays }
|
|
220
|
-
}));
|
|
221
|
-
}
|
|
222
|
-
// Request their peers
|
|
223
|
-
ws.send(JSON.stringify({
|
|
224
|
-
psp_version: PSP_VERSION, type: "discover", network,
|
|
225
|
-
from: relayPeerId, message_id: crypto.randomUUID(),
|
|
226
|
-
timestamp: Date.now(), ttl_ms: 10_000, body: {}
|
|
227
|
-
}));
|
|
228
|
-
});
|
|
227
|
+
const base = relayHttpBase(relayUrl);
|
|
228
|
+
const params = new URLSearchParams({ network });
|
|
229
|
+
if (requesterPeerId) params.set("exclude", requesterPeerId);
|
|
230
|
+
const resp = await fetch(`${base}/api/v1/peers?${params.toString()}`);
|
|
231
|
+
if (!resp.ok) return [];
|
|
232
|
+
const data = await resp.json();
|
|
233
|
+
const peers = Array.isArray(data?.peers) ? data.peers : [];
|
|
234
|
+
return peers.map(p => ({ ...p, relay_url: relayUrl }));
|
|
229
235
|
} catch {
|
|
230
236
|
return [];
|
|
231
237
|
}
|
|
232
238
|
}
|
|
233
239
|
|
|
234
|
-
//
|
|
240
|
+
// Forward a PSP message to a remote relay over HTTP for delivery or queueing.
|
|
235
241
|
async function forwardToRelay(relayUrl, message, selfRelayId) {
|
|
236
242
|
try {
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
await new Promise((resolve) => {
|
|
244
|
-
const relayPeerId = selfRelayId || "relay-bridge";
|
|
245
|
-
const closeTimer = setTimeout(() => {
|
|
246
|
-
try { ws.close(); } catch {}
|
|
247
|
-
resolve();
|
|
248
|
-
}, 250);
|
|
249
|
-
|
|
250
|
-
const finish = () => {
|
|
251
|
-
clearTimeout(closeTimer);
|
|
252
|
-
resolve();
|
|
253
|
-
};
|
|
254
|
-
|
|
255
|
-
ws.addEventListener("error", finish, { once: true });
|
|
256
|
-
ws.addEventListener("close", finish, { once: true });
|
|
257
|
-
|
|
258
|
-
// Outbound Worker WebSocket: send immediately after accept(), no open event needed.
|
|
259
|
-
ws.send(JSON.stringify({
|
|
260
|
-
psp_version: PSP_VERSION, type: "announce", network: message.network,
|
|
261
|
-
from: relayPeerId, message_id: crypto.randomUUID(),
|
|
262
|
-
timestamp: Date.now(), ttl_ms: 10_000, body: { capabilities: { relay: true } }
|
|
263
|
-
}));
|
|
264
|
-
ws.send(JSON.stringify(message));
|
|
243
|
+
const base = relayHttpBase(relayUrl);
|
|
244
|
+
await fetch(`${base}/api/v1/relay`, {
|
|
245
|
+
method: "POST",
|
|
246
|
+
headers: { "Content-Type": "application/json" },
|
|
247
|
+
body: JSON.stringify({ message, via: selfRelayId || "relay-bridge" })
|
|
265
248
|
});
|
|
266
249
|
} catch {}
|
|
267
250
|
}
|
|
@@ -621,14 +604,13 @@ async function handleClientMessage(socket, rawData, env, ctx, prevPeerKey = null
|
|
|
621
604
|
|
|
622
605
|
let remotePeers = [];
|
|
623
606
|
if (env.RELAY_URL && env.DB) {
|
|
624
|
-
const selfRelayId = env.RELAY_PEER_ID || "relay-bridge";
|
|
625
607
|
const selfUrl = normalizeRelayUrl(env.RELAY_URL);
|
|
626
608
|
const allRelays = await listRelays(env.DB);
|
|
627
609
|
const remoteUrls = allRelays.map(r => r.url).filter(u => u !== selfUrl);
|
|
628
610
|
|
|
629
611
|
if (remoteUrls.length) {
|
|
630
612
|
const results = await Promise.all(
|
|
631
|
-
remoteUrls.map(u => queryRelayForPeers(u, network,
|
|
613
|
+
remoteUrls.map(u => queryRelayForPeers(u, network, peerId))
|
|
632
614
|
);
|
|
633
615
|
remotePeers = results.flat();
|
|
634
616
|
}
|