freertc 0.1.22 → 0.1.28
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/bin/freertc.mjs +142 -6
- package/package.json +1 -1
- package/scripts/non-cloudflare-server.mjs +1 -1
- package/scripts/postinstall-message.mjs +6 -0
- package/src/index.js +1 -1
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.28","protocol_version":"1.0","peers":0}
|
|
220
220
|
```
|
|
221
221
|
|
|
222
222
|
## Auto WebRTC two-tab test
|
package/bin/freertc.mjs
CHANGED
|
@@ -76,6 +76,11 @@ function normalizeHost(value) {
|
|
|
76
76
|
return host || null;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
function isPlaceholderHost(host) {
|
|
80
|
+
if (!host || typeof host !== 'string') return false;
|
|
81
|
+
return /your-domain\.example|your[-_]?domain/i.test(host);
|
|
82
|
+
}
|
|
83
|
+
|
|
79
84
|
function sanitizeDomain(domain) {
|
|
80
85
|
if (!domain || typeof domain !== 'string') return null;
|
|
81
86
|
return domain
|
|
@@ -95,11 +100,46 @@ function patchJsoncVar(text, varName, value) {
|
|
|
95
100
|
return text;
|
|
96
101
|
}
|
|
97
102
|
|
|
103
|
+
function ensureRoutesForHost(configText, host) {
|
|
104
|
+
const normalizedHost = normalizeHost(host);
|
|
105
|
+
if (!normalizedHost || isPlaceholderHost(normalizedHost)) {
|
|
106
|
+
return configText;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const pattern = `${normalizedHost}/*`;
|
|
110
|
+
let text = configText;
|
|
111
|
+
|
|
112
|
+
if (/^\s*"routes"\s*:\s*\[/m.test(text)) {
|
|
113
|
+
text = text.replace(/^(\s*"pattern"\s*:\s*")([^"]*)(")/m, `$1${pattern}$3`);
|
|
114
|
+
if (/^\s*"zone_name"\s*:\s*"[^"]*"/m.test(text)) {
|
|
115
|
+
text = text.replace(/^(\s*"zone_name"\s*:\s*")([^"]*)(")/m, `$1${normalizedHost}$3`);
|
|
116
|
+
}
|
|
117
|
+
return text;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const routesBlock = [
|
|
121
|
+
' "routes": [',
|
|
122
|
+
' {',
|
|
123
|
+
` "pattern": "${pattern}",`,
|
|
124
|
+
` "zone_name": "${normalizedHost}"`,
|
|
125
|
+
' }',
|
|
126
|
+
' ]'
|
|
127
|
+
].join('\n');
|
|
128
|
+
|
|
129
|
+
if (/\n\s*"env"\s*:\s*\{/.test(text)) {
|
|
130
|
+
return text.replace(/\n\s*"env"\s*:\s*\{/, `\n${routesBlock},\n\n "env": {`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return text.replace(/\n}\s*$/, `\n${routesBlock}\n}`);
|
|
134
|
+
}
|
|
135
|
+
|
|
98
136
|
function extractRouteHost(configText) {
|
|
99
|
-
const routeMatch = configText.match(
|
|
137
|
+
const routeMatch = configText.match(/^\s*"pattern"\s*:\s*"([^"]+)"/m);
|
|
100
138
|
if (!routeMatch) return null;
|
|
101
139
|
const pattern = routeMatch[1];
|
|
102
|
-
|
|
140
|
+
const host = normalizeHost(pattern.split('/')[0]);
|
|
141
|
+
if (!host || isPlaceholderHost(host)) return null;
|
|
142
|
+
return host;
|
|
103
143
|
}
|
|
104
144
|
|
|
105
145
|
function extractCurrentWorkerName(configText) {
|
|
@@ -109,15 +149,24 @@ function extractCurrentWorkerName(configText) {
|
|
|
109
149
|
|
|
110
150
|
function extractDeployHost(configPath) {
|
|
111
151
|
const text = fs.readFileSync(configPath, 'utf8');
|
|
112
|
-
return
|
|
152
|
+
return extractGlobalRelayUrlHost(text) || extractRouteHost(text) || extractDatabaseDomainHost(text) || extractRelayUrlHost(text);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function extractGlobalRelayUrlHost(configText) {
|
|
156
|
+
const match = configText.match(/"GLOBAL_RELAY_URL"\s*:\s*"([^"]*)"/);
|
|
157
|
+
if (!match) return null;
|
|
158
|
+
const host = normalizeHost(match[1]);
|
|
159
|
+
if (!host || isPlaceholderHost(host)) return null;
|
|
160
|
+
return host;
|
|
113
161
|
}
|
|
114
162
|
|
|
115
163
|
function extractRelayUrlHost(configText) {
|
|
116
164
|
const relayUrlMatch = configText.match(/"RELAY_URL"\s*:\s*"([^"]*)"/);
|
|
117
165
|
if (!relayUrlMatch) return null;
|
|
118
166
|
const raw = relayUrlMatch[1];
|
|
119
|
-
|
|
120
|
-
|
|
167
|
+
const host = normalizeHost(raw);
|
|
168
|
+
if (!host || isPlaceholderHost(host)) return null;
|
|
169
|
+
return host;
|
|
121
170
|
}
|
|
122
171
|
|
|
123
172
|
function extractDatabaseDomainHost(configText) {
|
|
@@ -134,8 +183,10 @@ function extractDatabaseDomainHost(configText) {
|
|
|
134
183
|
function autoPatchWranglerConfig(configPath) {
|
|
135
184
|
let text = fs.readFileSync(configPath, 'utf8');
|
|
136
185
|
|
|
186
|
+
const publicHost = extractGlobalRelayUrlHost(text) || extractDatabaseDomainHost(text) || extractRouteHost(text);
|
|
137
187
|
const routeHost = extractRouteHost(text);
|
|
138
|
-
const
|
|
188
|
+
const envDomainCandidate = normalizeHost(process.env.CF_DOMAIN || process.env.CLOUD_FLARE_DOMAIN || '');
|
|
189
|
+
const envDomainHost = envDomainCandidate && !isPlaceholderHost(envDomainCandidate) ? envDomainCandidate : null;
|
|
139
190
|
const relayHost = routeHost || envDomainHost || extractRelayUrlHost(text) || extractDatabaseDomainHost(text);
|
|
140
191
|
|
|
141
192
|
if (!relayHost) {
|
|
@@ -150,6 +201,7 @@ function autoPatchWranglerConfig(configPath) {
|
|
|
150
201
|
text = patchJsoncVar(text, 'RELAY_URL', relayWsUrl);
|
|
151
202
|
text = patchJsoncVar(text, 'RELAY_NAME', relayName);
|
|
152
203
|
text = text.replace(/("database_name"\s*:\s*)"freertc-signal(?:-your[-_]?domain)?"/gi, `$1"${dbName}"`);
|
|
204
|
+
text = ensureRoutesForHost(text, publicHost || relayHost);
|
|
153
205
|
|
|
154
206
|
if (text !== before) {
|
|
155
207
|
fs.writeFileSync(configPath, text, 'utf8');
|
|
@@ -268,6 +320,31 @@ async function apiGetJson(url, token) {
|
|
|
268
320
|
return payload.result;
|
|
269
321
|
}
|
|
270
322
|
|
|
323
|
+
async function apiMutateJson(url, token, method, body) {
|
|
324
|
+
const response = await fetch(url, {
|
|
325
|
+
method,
|
|
326
|
+
headers: {
|
|
327
|
+
Authorization: `Bearer ${token}`,
|
|
328
|
+
Accept: 'application/json',
|
|
329
|
+
'Content-Type': 'application/json'
|
|
330
|
+
},
|
|
331
|
+
body: JSON.stringify(body)
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
let payload;
|
|
335
|
+
try {
|
|
336
|
+
payload = await response.json();
|
|
337
|
+
} catch {
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (!response.ok || !payload?.success) {
|
|
342
|
+
return null;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return payload.result || {};
|
|
346
|
+
}
|
|
347
|
+
|
|
271
348
|
async function resolveZoneIdForHost(host, token) {
|
|
272
349
|
const candidates = guessZoneCandidates(host);
|
|
273
350
|
for (const candidate of candidates) {
|
|
@@ -313,6 +390,52 @@ async function findRouteOwnerForHost(host) {
|
|
|
313
390
|
return wildcard ? (wildcard.script || wildcard.script_name) : null;
|
|
314
391
|
}
|
|
315
392
|
|
|
393
|
+
async function forceRouteBindingToWorker(host, workerName) {
|
|
394
|
+
const normalizedHost = normalizeHost(host);
|
|
395
|
+
const normalizedWorker = typeof workerName === 'string' ? workerName.trim() : '';
|
|
396
|
+
const token = getApiToken();
|
|
397
|
+
if (!normalizedHost || !normalizedWorker || !token) {
|
|
398
|
+
return false;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const zoneId = await resolveZoneIdForHost(normalizedHost, token);
|
|
402
|
+
if (!zoneId) {
|
|
403
|
+
return false;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const routes = await apiGetJson(`https://api.cloudflare.com/client/v4/zones/${zoneId}/workers/routes`, token);
|
|
407
|
+
if (!Array.isArray(routes)) {
|
|
408
|
+
return false;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const exactPattern = `${normalizedHost}/*`;
|
|
412
|
+
const matchingRoute = routes.find((route) => String(route?.pattern || '') === exactPattern)
|
|
413
|
+
|| routes.find((route) => routePatternMatchesHost(route?.pattern, normalizedHost));
|
|
414
|
+
|
|
415
|
+
if (matchingRoute?.id) {
|
|
416
|
+
const currentScript = matchingRoute.script || matchingRoute.script_name || '';
|
|
417
|
+
if (currentScript === normalizedWorker) {
|
|
418
|
+
return false;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const updated = await apiMutateJson(
|
|
422
|
+
`https://api.cloudflare.com/client/v4/zones/${zoneId}/workers/routes/${matchingRoute.id}`,
|
|
423
|
+
token,
|
|
424
|
+
'PUT',
|
|
425
|
+
{ pattern: matchingRoute.pattern || exactPattern, script: normalizedWorker }
|
|
426
|
+
);
|
|
427
|
+
return Boolean(updated);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
const created = await apiMutateJson(
|
|
431
|
+
`https://api.cloudflare.com/client/v4/zones/${zoneId}/workers/routes`,
|
|
432
|
+
token,
|
|
433
|
+
'POST',
|
|
434
|
+
{ pattern: exactPattern, script: normalizedWorker }
|
|
435
|
+
);
|
|
436
|
+
return Boolean(created);
|
|
437
|
+
}
|
|
438
|
+
|
|
316
439
|
async function maybeAlignWorkerNameToRouteOwner(configPath) {
|
|
317
440
|
const text = fs.readFileSync(configPath, 'utf8');
|
|
318
441
|
const host = extractRouteHost(text) || extractRelayUrlHost(text) || extractDatabaseDomainHost(text);
|
|
@@ -552,6 +675,19 @@ async function main() {
|
|
|
552
675
|
}
|
|
553
676
|
}
|
|
554
677
|
|
|
678
|
+
const finalText = fs.readFileSync(configPath, 'utf8');
|
|
679
|
+
const finalWorkerName = extractCurrentWorkerName(finalText);
|
|
680
|
+
if (finalWorkerName) {
|
|
681
|
+
const rebound = await forceRouteBindingToWorker(deployHost, finalWorkerName);
|
|
682
|
+
if (rebound) {
|
|
683
|
+
console.log(`Live health still stale on ${deployHost}. Rebound Cloudflare route to worker "${finalWorkerName}" and rechecking health.`);
|
|
684
|
+
liveVersion = await fetchLiveHealthVersion(deployHost);
|
|
685
|
+
if (liveVersion && compareVersions(liveVersion, CLI_VERSION) >= 0) {
|
|
686
|
+
process.exit(0);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
555
691
|
console.error(`Deploy completed but live health on https://${deployHost}/health is still stale (version=${liveVersion || 'unknown'}, expected>=${CLI_VERSION}).`);
|
|
556
692
|
console.error('Refusing silent success. Please rerun deploy after Cloudflare route cache settles.');
|
|
557
693
|
process.exit(1);
|
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.28';
|
|
11
11
|
const DEFAULT_TTL_MS = 30_000;
|
|
12
12
|
const MAX_TTL_MS = 120_000;
|
|
13
13
|
const MAX_MESSAGE_SIZE = 64 * 1024;
|
|
@@ -69,6 +69,12 @@ async function maybeSelfHealInstall() {
|
|
|
69
69
|
return;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
const localAheadOfRegistry = compareVersions(CLI_VERSION, latestVersion) > 0;
|
|
73
|
+
if (localAheadOfRegistry) {
|
|
74
|
+
console.log(`freertc postinstall detected local ${CLI_VERSION} ahead of npm latest ${latestVersion}; skipping self-heal to avoid downgrade.`);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
72
78
|
let dependencyRewritten = false;
|
|
73
79
|
for (const section of ['dependencies', 'devDependencies', 'optionalDependencies']) {
|
|
74
80
|
const table = hostPackage?.[section];
|
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.28";
|
|
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"]);
|