freertc 0.1.0 → 0.1.2
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 -0
- package/package.json +1 -1
- package/scripts/wrangler-install-wizard.mjs +65 -5
- package/src/index.js +57 -36
package/README.md
CHANGED
|
@@ -84,6 +84,7 @@ The wizard can:
|
|
|
84
84
|
- Copy the worker runtime files into your current project when they are missing.
|
|
85
85
|
- Verify Wrangler CLI.
|
|
86
86
|
- Create `wrangler.jsonc` from `wrangler.template.jsonc` if needed.
|
|
87
|
+
- No domain? No problem. Press Enter at the domain prompt to deploy on a free `workers.dev` subdomain.
|
|
87
88
|
- Set Worker name automatically to `freertc-<your-domain>` when a domain is provided.
|
|
88
89
|
- Initialize local D1 schema for `wrangler dev`.
|
|
89
90
|
- Initialize remote D1 schema for deploy.
|
package/package.json
CHANGED
|
@@ -180,6 +180,18 @@ function firstUuidFromText(text) {
|
|
|
180
180
|
return match ? match[0] : null;
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
+
function firstWorkersDevUrlFromText(text) {
|
|
184
|
+
if (!text) return null;
|
|
185
|
+
const match = text.match(/https:\/\/[a-z0-9-]+\.[a-z0-9-]+\.workers\.dev(?:\/ws)?/i);
|
|
186
|
+
return match ? match[0] : null;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function workersDevSubdomainFromWhoami(text) {
|
|
190
|
+
if (!text) return null;
|
|
191
|
+
const match = text.match(/workers\.dev\s+subdomain\s*:\s*([a-z0-9-]+)/i);
|
|
192
|
+
return match ? match[1] : null;
|
|
193
|
+
}
|
|
194
|
+
|
|
183
195
|
function resolveRemoteDbId(dbName) {
|
|
184
196
|
// Try create first. If DB already exists, wrangler typically returns non-zero
|
|
185
197
|
// and we fall back to list lookup.
|
|
@@ -250,6 +262,17 @@ function normalizeHost(value) {
|
|
|
250
262
|
return host || null;
|
|
251
263
|
}
|
|
252
264
|
|
|
265
|
+
function wsUrlFromHttpOrWsUrl(value) {
|
|
266
|
+
if (!value || typeof value !== 'string') return null;
|
|
267
|
+
let url = value.trim();
|
|
268
|
+
if (!url) return null;
|
|
269
|
+
url = url.replace(/^https:\/\//i, 'wss://').replace(/^http:\/\//i, 'ws://');
|
|
270
|
+
if (!/\/ws$/i.test(url)) {
|
|
271
|
+
url = `${url.replace(/\/$/, '')}/ws`;
|
|
272
|
+
}
|
|
273
|
+
return url;
|
|
274
|
+
}
|
|
275
|
+
|
|
253
276
|
function firstRouteHostFromWranglerConfig(filePath) {
|
|
254
277
|
if (!fs.existsSync(filePath)) return null;
|
|
255
278
|
const jsonc = fs.readFileSync(filePath, 'utf8');
|
|
@@ -450,6 +473,7 @@ async function main() {
|
|
|
450
473
|
// Derive D1 database name from domain or existing config.
|
|
451
474
|
console.log('\nStep 3: Configure D1 database name');
|
|
452
475
|
let preferredHealthHost = null;
|
|
476
|
+
let useWorkersDevFallback = false;
|
|
453
477
|
const isFirstRun = wranglerInit.created;
|
|
454
478
|
try {
|
|
455
479
|
const existingDbName = parseFirstDatabaseName(WRANGLER_CONFIG);
|
|
@@ -458,12 +482,13 @@ async function main() {
|
|
|
458
482
|
let derivedDbName;
|
|
459
483
|
|
|
460
484
|
if (isFirstRun) {
|
|
461
|
-
console.log('
|
|
485
|
+
console.log('No domain? No problem. Press Enter to use a free workers.dev subdomain.');
|
|
462
486
|
const domainInput = (await rl.question('Domain (example: example.com) [Enter to skip]: ')).trim();
|
|
463
487
|
if (!domainInput) {
|
|
464
488
|
const suffix = randomSuffix();
|
|
465
489
|
const workerName = `${PROJECT_NAME}-${suffix}`;
|
|
466
490
|
derivedDbName = `freertc-signal-${suffix}`;
|
|
491
|
+
useWorkersDevFallback = true;
|
|
467
492
|
console.log(`✓ No domain — using free workers.dev subdomain.`);
|
|
468
493
|
console.log(` Worker name : ${workerName}`);
|
|
469
494
|
console.log(` Database : ${derivedDbName}`);
|
|
@@ -505,6 +530,7 @@ async function main() {
|
|
|
505
530
|
const suffix = randomSuffix();
|
|
506
531
|
const workerName = `${PROJECT_NAME}-${suffix}`;
|
|
507
532
|
derivedDbName = `freertc-signal-${suffix}`;
|
|
533
|
+
useWorkersDevFallback = true;
|
|
508
534
|
console.log(`✓ No domain — using free workers.dev subdomain.`);
|
|
509
535
|
console.log(` Worker name : ${workerName}`);
|
|
510
536
|
console.log(` Database : ${derivedDbName}`);
|
|
@@ -525,8 +551,9 @@ async function main() {
|
|
|
525
551
|
}
|
|
526
552
|
}
|
|
527
553
|
|
|
528
|
-
// Patch DB name and
|
|
529
|
-
const
|
|
554
|
+
// Patch DB name and set RELAY_URL only when host is known.
|
|
555
|
+
const routeHost = firstRouteHostFromWranglerConfig(WRANGLER_CONFIG);
|
|
556
|
+
const host = preferredHealthHost || routeHost;
|
|
530
557
|
const relayWsUrl = host ? `wss://${host}/ws` : null;
|
|
531
558
|
|
|
532
559
|
let wranglerText = fs.readFileSync(WRANGLER_CONFIG, 'utf8');
|
|
@@ -534,6 +561,9 @@ async function main() {
|
|
|
534
561
|
if (relayWsUrl) {
|
|
535
562
|
wranglerText = patchVar(wranglerText, 'RELAY_URL', relayWsUrl);
|
|
536
563
|
console.log(`✓ Set RELAY_URL: ${relayWsUrl}`);
|
|
564
|
+
} else if (useWorkersDevFallback) {
|
|
565
|
+
wranglerText = removeVar(wranglerText, 'RELAY_URL');
|
|
566
|
+
console.log('✓ Deferred RELAY_URL until deploy (workers.dev URL is assigned at deploy time).');
|
|
537
567
|
}
|
|
538
568
|
fs.writeFileSync(WRANGLER_CONFIG, wranglerText, 'utf8');
|
|
539
569
|
console.log(`✓ Updated wrangler.jsonc with database name: ${derivedDbName}`);
|
|
@@ -637,13 +667,43 @@ async function main() {
|
|
|
637
667
|
|
|
638
668
|
const doDeploy = await rl.question('Deploy now (freertc deploy)? [Y/n]: ');
|
|
639
669
|
if (yes(doDeploy, true)) {
|
|
640
|
-
|
|
670
|
+
const deployResult = runWranglerCapture(['deploy', '--env', 'production'], { allowFailure: true });
|
|
671
|
+
if (deployResult.stdout) process.stdout.write(deployResult.stdout);
|
|
672
|
+
if (deployResult.stderr) process.stderr.write(deployResult.stderr);
|
|
673
|
+
if (!deployResult.ok) {
|
|
674
|
+
throw new Error('Deploy failed. See output above for details.');
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
let workersDevHealthHost = null;
|
|
678
|
+
if (useWorkersDevFallback) {
|
|
679
|
+
let workersDevUrl = firstWorkersDevUrlFromText(`${deployResult.stdout}\n${deployResult.stderr}`);
|
|
680
|
+
if (!workersDevUrl) {
|
|
681
|
+
const whoami = runWranglerCapture(['whoami'], { allowFailure: true });
|
|
682
|
+
const subdomain = workersDevSubdomainFromWhoami(`${whoami.stdout}\n${whoami.stderr}`);
|
|
683
|
+
const workerName = parseFirstWorkerName(WRANGLER_CONFIG);
|
|
684
|
+
if (subdomain && workerName) {
|
|
685
|
+
workersDevUrl = `https://${workerName}.${subdomain}.workers.dev`;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
const workersDevRelayWsUrl = wsUrlFromHttpOrWsUrl(workersDevUrl);
|
|
690
|
+
if (workersDevRelayWsUrl) {
|
|
691
|
+
let postDeployText = fs.readFileSync(WRANGLER_CONFIG, 'utf8');
|
|
692
|
+
postDeployText = patchVar(postDeployText, 'RELAY_URL', workersDevRelayWsUrl);
|
|
693
|
+
fs.writeFileSync(WRANGLER_CONFIG, postDeployText, 'utf8');
|
|
694
|
+
workersDevHealthHost = normalizeHost(workersDevRelayWsUrl);
|
|
695
|
+
console.log(`✓ Set RELAY_URL from deployed workers.dev URL: ${workersDevRelayWsUrl}`);
|
|
696
|
+
} else {
|
|
697
|
+
console.log('Could not auto-detect workers.dev URL from deploy output.');
|
|
698
|
+
console.log('Set RELAY_URL manually in wrangler.jsonc to: wss://<worker>.<subdomain>.workers.dev/ws');
|
|
699
|
+
}
|
|
700
|
+
}
|
|
641
701
|
|
|
642
702
|
console.log('\nStep 8: Verify deployment endpoint (recommended)');
|
|
643
703
|
console.log('Auto-checking /health on detected domain(s)...');
|
|
644
704
|
|
|
645
705
|
const routeHost = firstRouteHostFromWranglerConfig(WRANGLER_CONFIG);
|
|
646
|
-
const hosts = [preferredHealthHost, routeHost].filter(Boolean);
|
|
706
|
+
const hosts = [preferredHealthHost, routeHost, workersDevHealthHost].filter(Boolean);
|
|
647
707
|
const uniqueHosts = [...new Set(hosts)];
|
|
648
708
|
|
|
649
709
|
if (uniqueHosts.length === 0) {
|
package/src/index.js
CHANGED
|
@@ -260,6 +260,41 @@ async function listRelays(db) {
|
|
|
260
260
|
return (result.results || []).map(r => ({ url: r.url, name: r.name }));
|
|
261
261
|
}
|
|
262
262
|
|
|
263
|
+
function mergeDiscoveredPeers(...peerGroups) {
|
|
264
|
+
const merged = new Map();
|
|
265
|
+
|
|
266
|
+
for (const peers of peerGroups) {
|
|
267
|
+
if (!Array.isArray(peers)) continue;
|
|
268
|
+
for (const peer of peers) {
|
|
269
|
+
const peerId = peer?.peer_id;
|
|
270
|
+
if (typeof peerId !== "string" || !peerId) continue;
|
|
271
|
+
|
|
272
|
+
const existing = merged.get(peerId);
|
|
273
|
+
const nextTimestamp = Number(peer?.timestamp || 0);
|
|
274
|
+
const existingTimestamp = Number(existing?.timestamp || 0);
|
|
275
|
+
if (!existing || nextTimestamp >= existingTimestamp) {
|
|
276
|
+
merged.set(peerId, peer);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return Array.from(merged.values()).sort((left, right) => left.peer_id.localeCompare(right.peer_id));
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function sendPeerList(socket, network, peers, to = null, from = "bootstrap-relay") {
|
|
285
|
+
socket.send(JSON.stringify({
|
|
286
|
+
psp_version: PSP_VERSION,
|
|
287
|
+
type: "peer_list",
|
|
288
|
+
network,
|
|
289
|
+
from,
|
|
290
|
+
to,
|
|
291
|
+
message_id: crypto.randomUUID(),
|
|
292
|
+
timestamp: Date.now(),
|
|
293
|
+
ttl_ms: DEFAULT_TTL_MS,
|
|
294
|
+
body: { peers }
|
|
295
|
+
}));
|
|
296
|
+
}
|
|
297
|
+
|
|
263
298
|
// Broadcast peer list to all connected peers in a network
|
|
264
299
|
async function broadcastPeerList(db, network) {
|
|
265
300
|
const sockets = networkSubscribers.get(network);
|
|
@@ -279,23 +314,9 @@ async function broadcastPeerList(db, network) {
|
|
|
279
314
|
session_id: row.session_id,
|
|
280
315
|
timestamp: row.updated_at_ms
|
|
281
316
|
}));
|
|
282
|
-
|
|
283
|
-
const message = {
|
|
284
|
-
psp_version: PSP_VERSION,
|
|
285
|
-
type: "peer_list",
|
|
286
|
-
network,
|
|
287
|
-
from: "bootstrap-relay",
|
|
288
|
-
to: null,
|
|
289
|
-
message_id: crypto.randomUUID(),
|
|
290
|
-
timestamp: Date.now(),
|
|
291
|
-
ttl_ms: DEFAULT_TTL_MS,
|
|
292
|
-
body: { peers }
|
|
293
|
-
};
|
|
294
|
-
|
|
295
|
-
const payload = JSON.stringify(message);
|
|
296
317
|
for (const socket of sockets) {
|
|
297
318
|
try {
|
|
298
|
-
socket
|
|
319
|
+
sendPeerList(socket, network, peers);
|
|
299
320
|
} catch (e) {
|
|
300
321
|
sockets.delete(socket);
|
|
301
322
|
}
|
|
@@ -566,36 +587,36 @@ async function handleClientMessage(socket, rawData, env, ctx, prevPeerKey = null
|
|
|
566
587
|
}
|
|
567
588
|
|
|
568
589
|
} else if (type === "discover") {
|
|
569
|
-
|
|
590
|
+
let localPeers = [];
|
|
570
591
|
if (db) {
|
|
571
|
-
|
|
592
|
+
localPeers = await findPeers(db, network, peerId);
|
|
572
593
|
}
|
|
573
|
-
|
|
594
|
+
|
|
595
|
+
let remotePeers = [];
|
|
574
596
|
if (env.RELAY_URL && env.DB) {
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
const remoteUrls = allRelays.map(r => r.url).filter(u => u !== selfUrl);
|
|
580
|
-
if (!remoteUrls.length) return;
|
|
597
|
+
const selfRelayId = env.RELAY_PEER_ID || "relay-bridge";
|
|
598
|
+
const selfUrl = normalizeRelayUrl(env.RELAY_URL);
|
|
599
|
+
const allRelays = await listRelays(env.DB);
|
|
600
|
+
const remoteUrls = allRelays.map(r => r.url).filter(u => u !== selfUrl);
|
|
581
601
|
|
|
602
|
+
if (remoteUrls.length) {
|
|
582
603
|
const results = await Promise.all(
|
|
583
604
|
remoteUrls.map(u => queryRelayForPeers(u, network, selfRelayId, env.DB, allRelays))
|
|
584
605
|
);
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
const message = {
|
|
589
|
-
psp_version: PSP_VERSION, type: "peer_list", network,
|
|
590
|
-
from: selfRelayId, to: peerId,
|
|
591
|
-
message_id: crypto.randomUUID(), timestamp: Date.now(),
|
|
592
|
-
ttl_ms: DEFAULT_TTL_MS,
|
|
593
|
-
body: { peers: remotePeers }
|
|
594
|
-
};
|
|
595
|
-
try { socket.send(JSON.stringify(message)); } catch {}
|
|
596
|
-
})());
|
|
606
|
+
remotePeers = results.flat();
|
|
607
|
+
}
|
|
597
608
|
}
|
|
598
609
|
|
|
610
|
+
try {
|
|
611
|
+
sendPeerList(
|
|
612
|
+
socket,
|
|
613
|
+
network,
|
|
614
|
+
mergeDiscoveredPeers(localPeers, remotePeers).filter(peer => peer.peer_id !== peerId),
|
|
615
|
+
peerId,
|
|
616
|
+
env.RELAY_PEER_ID || "bootstrap-relay"
|
|
617
|
+
);
|
|
618
|
+
} catch {}
|
|
619
|
+
|
|
599
620
|
} else if (type === "ext" && message.body?.action === "relay_list") {
|
|
600
621
|
// Remote relay is sharing its known relay list — cache any new entries
|
|
601
622
|
if (db) {
|