freertc 0.1.11 → 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 -88
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,99 +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
|
-
|
|
177
|
-
ws.addEventListener("message", async (ev) => {
|
|
178
|
-
try {
|
|
179
|
-
const msg = JSON.parse(ev.data);
|
|
180
|
-
if (msg.type === "peer_list" && msg.network === network && !gotPeerList) {
|
|
181
|
-
gotPeerList = true;
|
|
182
|
-
clearTimeout(timer);
|
|
183
|
-
ws.close();
|
|
184
|
-
resolve((msg.body?.peers || []).map(p => ({ ...p, relay_url: relayUrl })));
|
|
185
|
-
}
|
|
186
|
-
// Cache any relay list the remote sends us via ext
|
|
187
|
-
if (msg.type === "ext" && msg.body?.action === "relay_list" && db) {
|
|
188
|
-
const remoteRelays = msg.body.relays || [];
|
|
189
|
-
await Promise.all(
|
|
190
|
-
remoteRelays.map(r => r.url ? upsertRelay(db, r.url, r.name || null).catch(() => {}) : null)
|
|
191
|
-
);
|
|
192
|
-
}
|
|
193
|
-
} catch {}
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
ws.addEventListener("error", () => { clearTimeout(timer); resolve([]); });
|
|
197
|
-
ws.addEventListener("close", () => { if (!gotPeerList) { clearTimeout(timer); resolve([]); } });
|
|
198
|
-
|
|
199
|
-
const relayPeerId = selfRelayId || "relay-bridge";
|
|
200
|
-
// Announce as relay bridge
|
|
201
|
-
ws.send(JSON.stringify({
|
|
202
|
-
psp_version: PSP_VERSION, type: "announce", network,
|
|
203
|
-
from: relayPeerId, message_id: crypto.randomUUID(),
|
|
204
|
-
timestamp: Date.now(), ttl_ms: 10_000, body: { capabilities: { relay: true } }
|
|
205
|
-
}));
|
|
206
|
-
// Share our known relay list so the remote can cache us
|
|
207
|
-
if (selfKnownRelays?.length) {
|
|
208
|
-
ws.send(JSON.stringify({
|
|
209
|
-
psp_version: PSP_VERSION, type: "ext", network,
|
|
210
|
-
from: relayPeerId, message_id: crypto.randomUUID(),
|
|
211
|
-
timestamp: Date.now(), ttl_ms: 10_000,
|
|
212
|
-
body: { action: "relay_list", relays: selfKnownRelays }
|
|
213
|
-
}));
|
|
214
|
-
}
|
|
215
|
-
// Request their peers
|
|
216
|
-
ws.send(JSON.stringify({
|
|
217
|
-
psp_version: PSP_VERSION, type: "discover", network,
|
|
218
|
-
from: relayPeerId, message_id: crypto.randomUUID(),
|
|
219
|
-
timestamp: Date.now(), ttl_ms: 10_000, body: {}
|
|
220
|
-
}));
|
|
221
|
-
});
|
|
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 }));
|
|
222
235
|
} catch {
|
|
223
236
|
return [];
|
|
224
237
|
}
|
|
225
238
|
}
|
|
226
239
|
|
|
227
|
-
//
|
|
240
|
+
// Forward a PSP message to a remote relay over HTTP for delivery or queueing.
|
|
228
241
|
async function forwardToRelay(relayUrl, message, selfRelayId) {
|
|
229
242
|
try {
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
await new Promise((resolve) => {
|
|
237
|
-
const relayPeerId = selfRelayId || "relay-bridge";
|
|
238
|
-
const closeTimer = setTimeout(() => {
|
|
239
|
-
try { ws.close(); } catch {}
|
|
240
|
-
resolve();
|
|
241
|
-
}, 250);
|
|
242
|
-
|
|
243
|
-
const finish = () => {
|
|
244
|
-
clearTimeout(closeTimer);
|
|
245
|
-
resolve();
|
|
246
|
-
};
|
|
247
|
-
|
|
248
|
-
ws.addEventListener("error", finish, { once: true });
|
|
249
|
-
ws.addEventListener("close", finish, { once: true });
|
|
250
|
-
|
|
251
|
-
// Outbound Worker WebSocket: send immediately after accept(), no open event needed.
|
|
252
|
-
ws.send(JSON.stringify({
|
|
253
|
-
psp_version: PSP_VERSION, type: "announce", network: message.network,
|
|
254
|
-
from: relayPeerId, message_id: crypto.randomUUID(),
|
|
255
|
-
timestamp: Date.now(), ttl_ms: 10_000, body: { capabilities: { relay: true } }
|
|
256
|
-
}));
|
|
257
|
-
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" })
|
|
258
248
|
});
|
|
259
249
|
} catch {}
|
|
260
250
|
}
|
|
@@ -614,14 +604,13 @@ async function handleClientMessage(socket, rawData, env, ctx, prevPeerKey = null
|
|
|
614
604
|
|
|
615
605
|
let remotePeers = [];
|
|
616
606
|
if (env.RELAY_URL && env.DB) {
|
|
617
|
-
const selfRelayId = env.RELAY_PEER_ID || "relay-bridge";
|
|
618
607
|
const selfUrl = normalizeRelayUrl(env.RELAY_URL);
|
|
619
608
|
const allRelays = await listRelays(env.DB);
|
|
620
609
|
const remoteUrls = allRelays.map(r => r.url).filter(u => u !== selfUrl);
|
|
621
610
|
|
|
622
611
|
if (remoteUrls.length) {
|
|
623
612
|
const results = await Promise.all(
|
|
624
|
-
remoteUrls.map(u => queryRelayForPeers(u, network,
|
|
613
|
+
remoteUrls.map(u => queryRelayForPeers(u, network, peerId))
|
|
625
614
|
);
|
|
626
615
|
remotePeers = results.flat();
|
|
627
616
|
}
|