@walkhi/code-relax 0.1.0-beta.66 → 0.1.0-beta.68
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/dist/bin/codex-remote.mjs +4 -2
- package/dist/bin/manage-relay-admin.mjs +20 -20
- package/dist/bin/migrate-relay-db.mjs +109 -109
- package/dist/bin/publish-harmony-release.mjs +46 -46
- package/dist/bin/self-relay-admin.mjs +8 -8
- package/dist/bin/self-relay-demo.mjs +33 -33
- package/dist/bin/self-relay-server.mjs +66 -66
- package/dist/bin/set-relay-admin-password.mjs +11 -11
- package/dist/build-platform.json +1 -1
- package/dist/get-lan-networks.ps1 +40 -40
- package/dist/shared/app-server-events.cjs +511 -511
- package/dist/shared/p2p-data-channel.cjs +72 -72
- package/dist/shared/p2p-diagnostics.cjs +28 -28
- package/dist/shared/p2p-ice-policy.cjs +22 -22
- package/dist/shared/plan-pricing.cjs +32 -32
- package/dist/shared/question-attention.cjs +69 -69
- package/dist/shared/release-version.cjs +31 -31
- package/dist/src/archived-threads.mjs +22 -22
- package/dist/src/background-process.mjs +18 -18
- package/dist/src/code-browser.mjs +100 -100
- package/dist/src/desktop-launcher.mjs +62 -62
- package/dist/src/dots-task-files.mjs +52 -52
- package/dist/src/execution-health.mjs +26 -26
- package/dist/src/message-images.mjs +9 -9
- package/dist/src/plan-pricing.mjs +47 -47
- package/dist/src/platform/codex-runtime.mjs +3 -3
- package/dist/src/platform/credential-store.mjs +19 -19
- package/dist/src/platform/desktop-host.mjs +3 -3
- package/dist/src/platform/desktop-permission-mode.mjs +17 -17
- package/dist/src/platform/desktop-project-registration.mjs +3 -3
- package/dist/src/platform/file-opener.mjs +9 -9
- package/dist/src/platform/linux/README.md +3 -3
- package/dist/src/platform/linux/codex-runtime.mjs +56 -56
- package/dist/src/platform/linux/desktop-installation.mjs +36 -36
- package/dist/src/platform/linux/desktop-project-registration.mjs +11 -11
- package/dist/src/platform/linux/network-info.mjs +11 -11
- package/dist/src/platform/linux/project-locations.mjs +17 -17
- package/dist/src/platform/linux/resident-startup.mjs +52 -52
- package/dist/src/platform/linux/retained-runtime.mjs +76 -76
- package/dist/src/platform/linux/service-host.mjs +18 -18
- package/dist/src/platform/macos/README.md +3 -3
- package/dist/src/platform/macos/desktop-project-registration.mjs +11 -11
- package/dist/src/platform/macos/network-info.mjs +8 -8
- package/dist/src/platform/macos/project-locations.mjs +8 -8
- package/dist/src/platform/macos/resident-startup.mjs +84 -84
- package/dist/src/platform/macos/retained-runtime.mjs +127 -127
- package/dist/src/platform/macos/service-host.mjs +18 -18
- package/dist/src/platform/macos/system.mjs +29 -29
- package/dist/src/platform/network-info.mjs +3 -3
- package/dist/src/platform/path-identity.mjs +5 -5
- package/dist/src/platform/posix/network-candidates.mjs +23 -23
- package/dist/src/platform/posix/runtime-paths.mjs +25 -25
- package/dist/src/platform/power-inhibitor.mjs +22 -22
- package/dist/src/platform/project-locations.mjs +3 -3
- package/dist/src/platform/runtime-paths.mjs +8 -8
- package/dist/src/platform/select.mjs +5 -5
- package/dist/src/platform/service-host.mjs +3 -3
- package/dist/src/platform/user-environment.mjs +3 -3
- package/dist/src/platform/windows/IsolatedProcess.cs +79 -79
- package/dist/src/platform/windows/background-process.mjs +30 -30
- package/dist/src/platform/windows/desktop-helper.mjs +179 -179
- package/dist/src/platform/windows/desktop-runtime.ps1 +10 -10
- package/dist/src/platform/windows/desktop-shortcuts.ps1 +7 -7
- package/dist/src/platform/windows/keep-awake.ps1 +20 -20
- package/dist/src/platform/windows/network-info.mjs +34 -34
- package/dist/src/platform/windows/resident-start.js +20 -20
- package/dist/src/platform/windows/resident-start.ps1 +7 -7
- package/dist/src/platform/windows/resident-startup.ps1 +83 -83
- package/dist/src/platform/windows/resolve-state-root.ps1 +14 -14
- package/dist/src/platform/windows/runtime-paths.mjs +20 -20
- package/dist/src/platform/windows/rustdesk-video-probe.mjs +167 -167
- package/dist/src/platform/windows/start-hidden-console.ps1 +58 -58
- package/dist/src/platform/windows/stop-desktop.ps1 +22 -22
- package/dist/src/platform/windows/user-environment.ps1 +41 -41
- package/dist/src/project-create.mjs +121 -121
- package/dist/src/project-management.mjs +1 -1
- package/dist/src/project-remove.mjs +55 -55
- package/dist/src/project-update.mjs +59 -59
- package/dist/src/relay-config-import.mjs +45 -45
- package/dist/src/self-relay/account-connections.mjs +52 -52
- package/dist/src/self-relay/admin-auth.mjs +59 -59
- package/dist/src/self-relay/admin-cloud.mjs +101 -101
- package/dist/src/self-relay/admin-login-page.mjs +7 -7
- package/dist/src/self-relay/admin-server.mjs +105 -105
- package/dist/src/self-relay/admin-state.mjs +127 -127
- package/dist/src/self-relay/admin-workstations-page.mjs +95 -95
- package/dist/src/self-relay/admin-workstations.mjs +143 -143
- package/dist/src/self-relay/android-releases.mjs +61 -61
- package/dist/src/self-relay/client.mjs +61 -61
- package/dist/src/self-relay/connection-reporting.mjs +38 -38
- package/dist/src/self-relay/debug-allowlist.mjs +33 -33
- package/dist/src/self-relay/demo.mjs +6 -6
- package/dist/src/self-relay/device-info.mjs +27 -27
- package/dist/src/self-relay/device-store.mjs +92 -92
- package/dist/src/self-relay/lan-authorizations.mjs +71 -71
- package/dist/src/self-relay/lifecycle.mjs +278 -278
- package/dist/src/self-relay/long-pairing.mjs +40 -40
- package/dist/src/self-relay/mysql-state.mjs +118 -118
- package/dist/src/self-relay/phone-connections.mjs +55 -55
- package/dist/src/self-relay/server.mjs +1066 -1066
- package/dist/src/self-relay/usage-daily.mjs +55 -55
- package/dist/src/self-relay/windows-route-helper.mjs +75 -75
- package/dist/src/self-relay/wire.mjs +36 -36
- package/dist/src/server.mjs +1947 -1947
- package/dist/src/service-control.mjs +82 -82
- package/dist/src/service-health.mjs +33 -33
- package/dist/src/service-power.mjs +58 -58
- package/dist/src/service-restart.mjs +81 -81
- package/dist/src/service-update-worker.mjs +42 -42
- package/dist/src/service-update.mjs +80 -80
- package/dist/src/shared-recovery.mjs +152 -152
- package/dist/src/skill-install.mjs +51 -51
- package/dist/src/thread-title-generation.mjs +150 -150
- package/dist/src/turn-model-history.mjs +47 -47
- package/dist/tools/rtc-route-helper.ps1 +99 -99
- package/dist/tools/start-rtc-route-helper.ps1 +15 -15
- package/dist/web/vendor/markdown-it.LICENSE.txt +22 -22
- package/dist/web/vendor/markdown-it.umd.min.js +13 -13
- package/package.json +4 -4
- package/tools/postinstall.mjs +1 -1
|
@@ -1,55 +1,55 @@
|
|
|
1
|
-
const dayMs = 86400000;
|
|
2
|
-
const chinaOffsetMs = 8 * 3600000;
|
|
3
|
-
|
|
4
|
-
export function usageDaySlices(start, end) {
|
|
5
|
-
const slices = [];
|
|
6
|
-
for (let cursor = start; cursor < end;) {
|
|
7
|
-
const date = new Date(cursor + chinaOffsetMs).toISOString().slice(0, 10);
|
|
8
|
-
const nextDay = Date.parse(`${date}T00:00:00Z`) + dayMs - chinaOffsetMs;
|
|
9
|
-
const until = Math.min(end, nextDay);
|
|
10
|
-
slices.push({ date, start: cursor, end: until });
|
|
11
|
-
cursor = until;
|
|
12
|
-
}
|
|
13
|
-
return slices;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const readIntervals = value => Array.isArray(value) ? value : JSON.parse(value || '[]');
|
|
17
|
-
|
|
18
|
-
export function mergeUsageIntervals(existing, start, end) {
|
|
19
|
-
const sorted = [...existing, [start, end]].sort((a, b) => a[0] - b[0]);
|
|
20
|
-
const merged = [];
|
|
21
|
-
for (const range of sorted) {
|
|
22
|
-
const last = merged.at(-1);
|
|
23
|
-
if (last && range[0] <= last[1]) last[1] = Math.max(last[1], range[1]);
|
|
24
|
-
else merged.push([...range]);
|
|
25
|
-
}
|
|
26
|
-
return { intervals: merged, seconds: merged.reduce((sum, [from, until]) => sum + until - from, 0) / 1000 };
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export async function recordDailyUsage(db, userId, computerId, free, start, end, now) {
|
|
30
|
-
for (const slice of usageDaySlices(start, end)) {
|
|
31
|
-
await db.execute(`INSERT IGNORE INTO relay_usage_user_days
|
|
32
|
-
(user_id,usage_date,intervals,updated_at) VALUES (?,?,'[]',?)`, [userId, slice.date, now]);
|
|
33
|
-
const [userRows] = await db.execute(`SELECT intervals FROM relay_usage_user_days
|
|
34
|
-
WHERE user_id=? AND usage_date=? FOR UPDATE`, [userId, slice.date]);
|
|
35
|
-
const user = mergeUsageIntervals(readIntervals(userRows[0].intervals), slice.start, slice.end);
|
|
36
|
-
await db.execute(`UPDATE relay_usage_user_days SET intervals=?,task_seconds=?,updated_at=?
|
|
37
|
-
WHERE user_id=? AND usage_date=?`,
|
|
38
|
-
[JSON.stringify(user.intervals), user.seconds, now, userId, slice.date]);
|
|
39
|
-
|
|
40
|
-
await db.execute(`INSERT IGNORE INTO relay_usage_computer_days
|
|
41
|
-
(computer_id,usage_date,intervals,free_intervals,updated_at) VALUES (?,?,'[]','[]',?)`,
|
|
42
|
-
[computerId, slice.date, now]);
|
|
43
|
-
const [computerRows] = await db.execute(`SELECT intervals,free_intervals FROM relay_usage_computer_days
|
|
44
|
-
WHERE computer_id=? AND usage_date=? FOR UPDATE`, [computerId, slice.date]);
|
|
45
|
-
const computer = mergeUsageIntervals(readIntervals(computerRows[0].intervals), slice.start, slice.end);
|
|
46
|
-
const freeIntervals = readIntervals(computerRows[0].free_intervals);
|
|
47
|
-
const freeComputer = free ? mergeUsageIntervals(freeIntervals, slice.start, slice.end) : null;
|
|
48
|
-
await db.execute(`UPDATE relay_usage_computer_days SET intervals=?,task_seconds=?,
|
|
49
|
-
free_intervals=?,free_task_seconds=COALESCE(?,free_task_seconds),updated_at=?
|
|
50
|
-
WHERE computer_id=? AND usage_date=?`,
|
|
51
|
-
[JSON.stringify(computer.intervals), computer.seconds,
|
|
52
|
-
JSON.stringify(freeComputer?.intervals || freeIntervals), freeComputer?.seconds ?? null,
|
|
53
|
-
now, computerId, slice.date]);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
1
|
+
const dayMs = 86400000;
|
|
2
|
+
const chinaOffsetMs = 8 * 3600000;
|
|
3
|
+
|
|
4
|
+
export function usageDaySlices(start, end) {
|
|
5
|
+
const slices = [];
|
|
6
|
+
for (let cursor = start; cursor < end;) {
|
|
7
|
+
const date = new Date(cursor + chinaOffsetMs).toISOString().slice(0, 10);
|
|
8
|
+
const nextDay = Date.parse(`${date}T00:00:00Z`) + dayMs - chinaOffsetMs;
|
|
9
|
+
const until = Math.min(end, nextDay);
|
|
10
|
+
slices.push({ date, start: cursor, end: until });
|
|
11
|
+
cursor = until;
|
|
12
|
+
}
|
|
13
|
+
return slices;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const readIntervals = value => Array.isArray(value) ? value : JSON.parse(value || '[]');
|
|
17
|
+
|
|
18
|
+
export function mergeUsageIntervals(existing, start, end) {
|
|
19
|
+
const sorted = [...existing, [start, end]].sort((a, b) => a[0] - b[0]);
|
|
20
|
+
const merged = [];
|
|
21
|
+
for (const range of sorted) {
|
|
22
|
+
const last = merged.at(-1);
|
|
23
|
+
if (last && range[0] <= last[1]) last[1] = Math.max(last[1], range[1]);
|
|
24
|
+
else merged.push([...range]);
|
|
25
|
+
}
|
|
26
|
+
return { intervals: merged, seconds: merged.reduce((sum, [from, until]) => sum + until - from, 0) / 1000 };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function recordDailyUsage(db, userId, computerId, free, start, end, now) {
|
|
30
|
+
for (const slice of usageDaySlices(start, end)) {
|
|
31
|
+
await db.execute(`INSERT IGNORE INTO relay_usage_user_days
|
|
32
|
+
(user_id,usage_date,intervals,updated_at) VALUES (?,?,'[]',?)`, [userId, slice.date, now]);
|
|
33
|
+
const [userRows] = await db.execute(`SELECT intervals FROM relay_usage_user_days
|
|
34
|
+
WHERE user_id=? AND usage_date=? FOR UPDATE`, [userId, slice.date]);
|
|
35
|
+
const user = mergeUsageIntervals(readIntervals(userRows[0].intervals), slice.start, slice.end);
|
|
36
|
+
await db.execute(`UPDATE relay_usage_user_days SET intervals=?,task_seconds=?,updated_at=?
|
|
37
|
+
WHERE user_id=? AND usage_date=?`,
|
|
38
|
+
[JSON.stringify(user.intervals), user.seconds, now, userId, slice.date]);
|
|
39
|
+
|
|
40
|
+
await db.execute(`INSERT IGNORE INTO relay_usage_computer_days
|
|
41
|
+
(computer_id,usage_date,intervals,free_intervals,updated_at) VALUES (?,?,'[]','[]',?)`,
|
|
42
|
+
[computerId, slice.date, now]);
|
|
43
|
+
const [computerRows] = await db.execute(`SELECT intervals,free_intervals FROM relay_usage_computer_days
|
|
44
|
+
WHERE computer_id=? AND usage_date=? FOR UPDATE`, [computerId, slice.date]);
|
|
45
|
+
const computer = mergeUsageIntervals(readIntervals(computerRows[0].intervals), slice.start, slice.end);
|
|
46
|
+
const freeIntervals = readIntervals(computerRows[0].free_intervals);
|
|
47
|
+
const freeComputer = free ? mergeUsageIntervals(freeIntervals, slice.start, slice.end) : null;
|
|
48
|
+
await db.execute(`UPDATE relay_usage_computer_days SET intervals=?,task_seconds=?,
|
|
49
|
+
free_intervals=?,free_task_seconds=COALESCE(?,free_task_seconds),updated_at=?
|
|
50
|
+
WHERE computer_id=? AND usage_date=?`,
|
|
51
|
+
[JSON.stringify(computer.intervals), computer.seconds,
|
|
52
|
+
JSON.stringify(freeComputer?.intervals || freeIntervals), freeComputer?.seconds ?? null,
|
|
53
|
+
now, computerId, slice.date]);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -1,75 +1,75 @@
|
|
|
1
|
-
import net from 'node:net';
|
|
2
|
-
|
|
3
|
-
const pipePath = String.raw`\\.\pipe\CodeRelaxRtcRouteHelper`;
|
|
4
|
-
|
|
5
|
-
export function createWindowsRouteHelper({ enabled = false, token = '', iceServers = [], report = () => {} } = {}) {
|
|
6
|
-
const addresses = new Set();
|
|
7
|
-
const routeAddresses = new Set(iceServers.map(stunAddress).filter(publicIpv4));
|
|
8
|
-
let chain = Promise.resolve();
|
|
9
|
-
const enqueue = task => chain = chain.then(task, task);
|
|
10
|
-
if (!enabled || process.platform !== 'win32') return { prepare() { return Promise.resolve(); }, include() { return Promise.resolve(); } };
|
|
11
|
-
if (!/^[A-Za-z0-9_-]{32,128}$/.test(token)) throw new Error('Windows RTC 路由辅助凭据无效');
|
|
12
|
-
const add = values => enqueue(async () => {
|
|
13
|
-
for (const value of values) addresses.add(value);
|
|
14
|
-
if (!values.length) return;
|
|
15
|
-
const result = await request({ token, action: 'add', addresses: [...addresses], ttlSeconds: 120 });
|
|
16
|
-
if (!result.ok) throw new Error(result.error || 'Windows RTC 路由辅助不可用');
|
|
17
|
-
report('windows-route-helper', { count: addresses.size });
|
|
18
|
-
});
|
|
19
|
-
return {
|
|
20
|
-
prepare() { return add([...routeAddresses]); },
|
|
21
|
-
include(message) {
|
|
22
|
-
const discovered = candidateAddresses(message);
|
|
23
|
-
return add(discovered.filter(value => !addresses.has(value)));
|
|
24
|
-
}
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function stunAddress(value) {
|
|
29
|
-
const match = /^stun:([^:]+):\d{1,5}$/i.exec(value || '');
|
|
30
|
-
return match?.[1] || '';
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function candidateAddresses(message) {
|
|
34
|
-
const text = message?.kind === 'candidate' ? message.candidate : ['offer', 'answer'].includes(message?.kind) ? message.sdp : '';
|
|
35
|
-
const result = new Set();
|
|
36
|
-
for (const line of String(text || '').split(/\r?\n/)) {
|
|
37
|
-
const parts = line.trim().replace(/^a=/, '').split(/\s+/);
|
|
38
|
-
if (parts[0]?.startsWith('candidate:') && parts[6] === 'typ' && publicIpv4(parts[4])) result.add(parts[4]);
|
|
39
|
-
}
|
|
40
|
-
return [...result];
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function publicIpv4(value) {
|
|
44
|
-
const parts = String(value).split('.').map(Number);
|
|
45
|
-
if (parts.length !== 4 || parts.some(part => !Number.isInteger(part) || part < 0 || part > 255)) return false;
|
|
46
|
-
const [a, b] = parts;
|
|
47
|
-
return a !== 0 && a !== 10 && a !== 127 && a < 224 && !(a === 169 && b === 254) && !(a === 172 && b >= 16 && b <= 31) &&
|
|
48
|
-
!(a === 192 && b === 168) && !(a === 100 && b >= 64 && b <= 127) && !(a === 198 && (b === 18 || b === 19));
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function request(value) {
|
|
52
|
-
return new Promise((resolve, reject) => {
|
|
53
|
-
const socket = net.createConnection(pipePath);
|
|
54
|
-
let text = '';
|
|
55
|
-
let settled = false;
|
|
56
|
-
const rejectOnce = error => { if (!settled) { settled = true; reject(error); } };
|
|
57
|
-
const resolveOnce = result => { if (!settled) { settled = true; resolve(result); } };
|
|
58
|
-
socket.setTimeout(3000);
|
|
59
|
-
socket.setEncoding('utf8');
|
|
60
|
-
socket.once('connect', () => socket.write(`${JSON.stringify(value)}\n`));
|
|
61
|
-
socket.on('data', chunk => {
|
|
62
|
-
text += chunk;
|
|
63
|
-
const line = text.indexOf('\n');
|
|
64
|
-
if (line < 0) return;
|
|
65
|
-
try { resolveOnce(JSON.parse(text.slice(0, line))); socket.end(); }
|
|
66
|
-
catch { rejectOnce(new Error('Windows RTC 路由辅助响应无效')); socket.destroy(); }
|
|
67
|
-
});
|
|
68
|
-
socket.once('timeout', () => socket.destroy(new Error('Windows RTC 路由辅助超时')));
|
|
69
|
-
socket.once('error', rejectOnce);
|
|
70
|
-
socket.once('close', hadError => {
|
|
71
|
-
if (hadError || settled) return;
|
|
72
|
-
rejectOnce(new Error('Windows RTC 路由辅助响应无效'));
|
|
73
|
-
});
|
|
74
|
-
});
|
|
75
|
-
}
|
|
1
|
+
import net from 'node:net';
|
|
2
|
+
|
|
3
|
+
const pipePath = String.raw`\\.\pipe\CodeRelaxRtcRouteHelper`;
|
|
4
|
+
|
|
5
|
+
export function createWindowsRouteHelper({ enabled = false, token = '', iceServers = [], report = () => {} } = {}) {
|
|
6
|
+
const addresses = new Set();
|
|
7
|
+
const routeAddresses = new Set(iceServers.map(stunAddress).filter(publicIpv4));
|
|
8
|
+
let chain = Promise.resolve();
|
|
9
|
+
const enqueue = task => chain = chain.then(task, task);
|
|
10
|
+
if (!enabled || process.platform !== 'win32') return { prepare() { return Promise.resolve(); }, include() { return Promise.resolve(); } };
|
|
11
|
+
if (!/^[A-Za-z0-9_-]{32,128}$/.test(token)) throw new Error('Windows RTC 路由辅助凭据无效');
|
|
12
|
+
const add = values => enqueue(async () => {
|
|
13
|
+
for (const value of values) addresses.add(value);
|
|
14
|
+
if (!values.length) return;
|
|
15
|
+
const result = await request({ token, action: 'add', addresses: [...addresses], ttlSeconds: 120 });
|
|
16
|
+
if (!result.ok) throw new Error(result.error || 'Windows RTC 路由辅助不可用');
|
|
17
|
+
report('windows-route-helper', { count: addresses.size });
|
|
18
|
+
});
|
|
19
|
+
return {
|
|
20
|
+
prepare() { return add([...routeAddresses]); },
|
|
21
|
+
include(message) {
|
|
22
|
+
const discovered = candidateAddresses(message);
|
|
23
|
+
return add(discovered.filter(value => !addresses.has(value)));
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function stunAddress(value) {
|
|
29
|
+
const match = /^stun:([^:]+):\d{1,5}$/i.exec(value || '');
|
|
30
|
+
return match?.[1] || '';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function candidateAddresses(message) {
|
|
34
|
+
const text = message?.kind === 'candidate' ? message.candidate : ['offer', 'answer'].includes(message?.kind) ? message.sdp : '';
|
|
35
|
+
const result = new Set();
|
|
36
|
+
for (const line of String(text || '').split(/\r?\n/)) {
|
|
37
|
+
const parts = line.trim().replace(/^a=/, '').split(/\s+/);
|
|
38
|
+
if (parts[0]?.startsWith('candidate:') && parts[6] === 'typ' && publicIpv4(parts[4])) result.add(parts[4]);
|
|
39
|
+
}
|
|
40
|
+
return [...result];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function publicIpv4(value) {
|
|
44
|
+
const parts = String(value).split('.').map(Number);
|
|
45
|
+
if (parts.length !== 4 || parts.some(part => !Number.isInteger(part) || part < 0 || part > 255)) return false;
|
|
46
|
+
const [a, b] = parts;
|
|
47
|
+
return a !== 0 && a !== 10 && a !== 127 && a < 224 && !(a === 169 && b === 254) && !(a === 172 && b >= 16 && b <= 31) &&
|
|
48
|
+
!(a === 192 && b === 168) && !(a === 100 && b >= 64 && b <= 127) && !(a === 198 && (b === 18 || b === 19));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function request(value) {
|
|
52
|
+
return new Promise((resolve, reject) => {
|
|
53
|
+
const socket = net.createConnection(pipePath);
|
|
54
|
+
let text = '';
|
|
55
|
+
let settled = false;
|
|
56
|
+
const rejectOnce = error => { if (!settled) { settled = true; reject(error); } };
|
|
57
|
+
const resolveOnce = result => { if (!settled) { settled = true; resolve(result); } };
|
|
58
|
+
socket.setTimeout(3000);
|
|
59
|
+
socket.setEncoding('utf8');
|
|
60
|
+
socket.once('connect', () => socket.write(`${JSON.stringify(value)}\n`));
|
|
61
|
+
socket.on('data', chunk => {
|
|
62
|
+
text += chunk;
|
|
63
|
+
const line = text.indexOf('\n');
|
|
64
|
+
if (line < 0) return;
|
|
65
|
+
try { resolveOnce(JSON.parse(text.slice(0, line))); socket.end(); }
|
|
66
|
+
catch { rejectOnce(new Error('Windows RTC 路由辅助响应无效')); socket.destroy(); }
|
|
67
|
+
});
|
|
68
|
+
socket.once('timeout', () => socket.destroy(new Error('Windows RTC 路由辅助超时')));
|
|
69
|
+
socket.once('error', rejectOnce);
|
|
70
|
+
socket.once('close', hadError => {
|
|
71
|
+
if (hadError || settled) return;
|
|
72
|
+
rejectOnce(new Error('Windows RTC 路由辅助响应无效'));
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
}
|
|
@@ -1,36 +1,36 @@
|
|
|
1
|
-
import { randomBytes, createHash, timingSafeEqual } from 'node:crypto';
|
|
2
|
-
import { WebSocket } from 'ws';
|
|
3
|
-
|
|
4
|
-
export const secret = () => randomBytes(32).toString('base64url');
|
|
5
|
-
export const digest = value => createHash('sha256').update(value).digest('hex');
|
|
6
|
-
export const validSecret = value => typeof value === 'string' && /^[\w-]{43}$/.test(value);
|
|
7
|
-
export function matches(value, hash) {
|
|
8
|
-
return validSecret(value) && typeof hash === 'string' && timingSafeEqual(Buffer.from(digest(value)), Buffer.from(hash));
|
|
9
|
-
}
|
|
10
|
-
export function send(socket, message) {
|
|
11
|
-
if (socket.readyState !== WebSocket.OPEN) return false;
|
|
12
|
-
if (socket.bufferedAmount > 64 * 1024 * 1024) { socket.close(1013, 'Receiver too slow'); return false; }
|
|
13
|
-
socket.send(JSON.stringify(message)); return true;
|
|
14
|
-
}
|
|
15
|
-
export function decode(bytes, binary) {
|
|
16
|
-
if (binary) throw new Error('Text frames required');
|
|
17
|
-
const value = JSON.parse(bytes.toString());
|
|
18
|
-
if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('Object required');
|
|
19
|
-
return value;
|
|
20
|
-
}
|
|
21
|
-
export function loopbackUrl(value, pathname) {
|
|
22
|
-
const url = new URL(value);
|
|
23
|
-
if (url.protocol !== 'ws:' || url.hostname !== '127.0.0.1' || url.pathname !== pathname || url.search || url.hash || url.username || url.password) {
|
|
24
|
-
throw new Error('本地原型仅接受指定路径的 ws://127.0.0.1 地址');
|
|
25
|
-
}
|
|
26
|
-
return url.href;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function relayAddress(value) {
|
|
30
|
-
const url = new URL(value);
|
|
31
|
-
if (url.protocol === 'ws:') return loopbackUrl(value, '/relay');
|
|
32
|
-
if (url.protocol !== 'wss:' || url.pathname !== '/relay' || url.search || url.hash || url.username || url.password) {
|
|
33
|
-
throw new Error('远程中继必须使用无查询凭据的 wss://主机/relay 地址');
|
|
34
|
-
}
|
|
35
|
-
return url.href;
|
|
36
|
-
}
|
|
1
|
+
import { randomBytes, createHash, timingSafeEqual } from 'node:crypto';
|
|
2
|
+
import { WebSocket } from 'ws';
|
|
3
|
+
|
|
4
|
+
export const secret = () => randomBytes(32).toString('base64url');
|
|
5
|
+
export const digest = value => createHash('sha256').update(value).digest('hex');
|
|
6
|
+
export const validSecret = value => typeof value === 'string' && /^[\w-]{43}$/.test(value);
|
|
7
|
+
export function matches(value, hash) {
|
|
8
|
+
return validSecret(value) && typeof hash === 'string' && timingSafeEqual(Buffer.from(digest(value)), Buffer.from(hash));
|
|
9
|
+
}
|
|
10
|
+
export function send(socket, message) {
|
|
11
|
+
if (socket.readyState !== WebSocket.OPEN) return false;
|
|
12
|
+
if (socket.bufferedAmount > 64 * 1024 * 1024) { socket.close(1013, 'Receiver too slow'); return false; }
|
|
13
|
+
socket.send(JSON.stringify(message)); return true;
|
|
14
|
+
}
|
|
15
|
+
export function decode(bytes, binary) {
|
|
16
|
+
if (binary) throw new Error('Text frames required');
|
|
17
|
+
const value = JSON.parse(bytes.toString());
|
|
18
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('Object required');
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
export function loopbackUrl(value, pathname) {
|
|
22
|
+
const url = new URL(value);
|
|
23
|
+
if (url.protocol !== 'ws:' || url.hostname !== '127.0.0.1' || url.pathname !== pathname || url.search || url.hash || url.username || url.password) {
|
|
24
|
+
throw new Error('本地原型仅接受指定路径的 ws://127.0.0.1 地址');
|
|
25
|
+
}
|
|
26
|
+
return url.href;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function relayAddress(value) {
|
|
30
|
+
const url = new URL(value);
|
|
31
|
+
if (url.protocol === 'ws:') return loopbackUrl(value, '/relay');
|
|
32
|
+
if (url.protocol !== 'wss:' || url.pathname !== '/relay' || url.search || url.hash || url.username || url.password) {
|
|
33
|
+
throw new Error('远程中继必须使用无查询凭据的 wss://主机/relay 地址');
|
|
34
|
+
}
|
|
35
|
+
return url.href;
|
|
36
|
+
}
|