moving
This commit is contained in:
262
scripts/read-tauri-bootstrap-token.mjs
Normal file
262
scripts/read-tauri-bootstrap-token.mjs
Normal file
@@ -0,0 +1,262 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import process from 'node:process';
|
||||
import WebSocket from 'ws';
|
||||
|
||||
const DEFAULT_TIMEOUT_MS = 60_000;
|
||||
const MIN_TOKEN_LENGTH = 32;
|
||||
const MAX_TOKEN_LENGTH = 200;
|
||||
|
||||
function fail(message) {
|
||||
console.error(`tauri-bootstrap-token: ${message}`);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
|
||||
function parseArgs(argv) {
|
||||
const options = {
|
||||
port: null,
|
||||
timeoutMs: DEFAULT_TIMEOUT_MS,
|
||||
selfTest: false,
|
||||
allowLegacyUi: false,
|
||||
};
|
||||
for (let index = 0; index < argv.length; index += 1) {
|
||||
const argument = argv[index];
|
||||
if (argument === '--self-test') {
|
||||
options.selfTest = true;
|
||||
continue;
|
||||
}
|
||||
if (argument === '--allow-legacy-ui') {
|
||||
options.allowLegacyUi = true;
|
||||
continue;
|
||||
}
|
||||
if (argument === '--port' || argument === '--timeout-ms') {
|
||||
const value = argv[index + 1];
|
||||
index += 1;
|
||||
if (value === undefined) throw new Error(`${argument} requires a value`);
|
||||
if (argument === '--port') options.port = Number(value);
|
||||
else options.timeoutMs = Number(value);
|
||||
continue;
|
||||
}
|
||||
if (argument.startsWith('--port=')) options.port = Number(argument.slice(7));
|
||||
else if (argument.startsWith('--timeout-ms=')) options.timeoutMs = Number(argument.slice(13));
|
||||
else throw new Error(`unknown argument: ${argument}`);
|
||||
}
|
||||
if (!Number.isInteger(options.port) || options.port < 1024 || options.port > 65535) {
|
||||
throw new Error('port must be an integer between 1024 and 65535');
|
||||
}
|
||||
if (!Number.isInteger(options.timeoutMs) || options.timeoutMs < 1000 || options.timeoutMs > 300_000) {
|
||||
throw new Error('timeout-ms must be an integer between 1000 and 300000');
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
function isValidEndpoint(value) {
|
||||
return value !== null
|
||||
&& typeof value === 'object'
|
||||
&& Number.isInteger(value.port)
|
||||
&& value.port > 0
|
||||
&& value.port <= 65535
|
||||
&& typeof value.instanceId === 'string'
|
||||
&& value.instanceId.trim().length > 0
|
||||
&& typeof value.bootstrapToken === 'string'
|
||||
&& value.bootstrapToken.length >= MIN_TOKEN_LENGTH
|
||||
&& value.bootstrapToken.length <= MAX_TOKEN_LENGTH;
|
||||
}
|
||||
|
||||
function isValidDesktopRuntime(value, allowLegacyUi = false) {
|
||||
return isValidEndpoint(value)
|
||||
&& value.uiReady === true
|
||||
&& value.uiStartupState === (allowLegacyUi ? 'legacy-ready' : 'ready')
|
||||
&& typeof value.uiPath === 'string'
|
||||
&& value.uiPath.startsWith('/')
|
||||
&& Number.isInteger(value.uiTextLength)
|
||||
&& value.uiTextLength > 0;
|
||||
}
|
||||
|
||||
async function fetchTargets(port) {
|
||||
const response = await fetch(`http://127.0.0.1:${port}/json/list`);
|
||||
if (!response.ok) throw new Error(`WebView debug endpoint returned HTTP ${response.status}`);
|
||||
const targets = await response.json();
|
||||
if (!Array.isArray(targets)) throw new Error('WebView debug endpoint returned an invalid target list');
|
||||
return targets.filter((target) => target?.type === 'page' && typeof target.webSocketDebuggerUrl === 'string');
|
||||
}
|
||||
|
||||
async function evaluateEnsureService(webSocketUrl, timeoutMs, allowLegacyUi) {
|
||||
const socket = new WebSocket(webSocketUrl);
|
||||
let nextId = 1;
|
||||
const pending = new Map();
|
||||
const timer = setTimeout(() => {
|
||||
for (const pendingRequest of pending.values()) pendingRequest.reject(new Error('CDP request timed out'));
|
||||
pending.clear();
|
||||
socket.close();
|
||||
}, timeoutMs);
|
||||
|
||||
try {
|
||||
await new Promise((resolve, reject) => {
|
||||
const openTimer = setTimeout(() => reject(new Error('CDP WebSocket open timed out')), timeoutMs);
|
||||
socket.addEventListener('open', () => {
|
||||
clearTimeout(openTimer);
|
||||
resolve();
|
||||
}, { once: true });
|
||||
socket.addEventListener('error', () => {
|
||||
clearTimeout(openTimer);
|
||||
reject(new Error('CDP WebSocket connection failed'));
|
||||
}, { once: true });
|
||||
});
|
||||
|
||||
socket.addEventListener('message', (event) => {
|
||||
let message;
|
||||
try {
|
||||
message = JSON.parse(String(event.data));
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
const request = pending.get(message.id);
|
||||
if (!request) return;
|
||||
pending.delete(message.id);
|
||||
if (message.error) request.reject(new Error('Tauri IPC evaluation failed'));
|
||||
else request.resolve(message.result);
|
||||
});
|
||||
|
||||
const result = await new Promise((resolve, reject) => {
|
||||
const id = nextId;
|
||||
nextId += 1;
|
||||
pending.set(id, { resolve, reject });
|
||||
socket.send(JSON.stringify({
|
||||
id,
|
||||
method: 'Runtime.evaluate',
|
||||
params: {
|
||||
awaitPromise: true,
|
||||
returnByValue: true,
|
||||
expression: `
|
||||
(async () => {
|
||||
const invoke = globalThis.__TAURI_INTERNALS__?.invoke;
|
||||
if (typeof invoke !== 'function') throw new Error('Tauri IPC is unavailable');
|
||||
const endpoint = await invoke('ensure_service');
|
||||
const deadline = Date.now() + 10_000;
|
||||
for (;;) {
|
||||
const root = document.getElementById('root');
|
||||
const startup = root?.querySelector('[data-waggle-startup]');
|
||||
const startupState = startup?.getAttribute('data-waggle-startup') ?? null;
|
||||
const readyState = root?.getAttribute('data-waggle-ui-ready') ?? null;
|
||||
const uiTextLength = root?.innerText?.trim().length ?? 0;
|
||||
const strictUiReady = Boolean(
|
||||
root
|
||||
&& root.childElementCount > 0
|
||||
&& !startup
|
||||
&& readyState === 'ready'
|
||||
&& uiTextLength > 0
|
||||
);
|
||||
const legacyUiReady = Boolean(
|
||||
root
|
||||
&& root.childElementCount > 0
|
||||
&& !startup
|
||||
&& uiTextLength > 0
|
||||
);
|
||||
const uiReady = ${allowLegacyUi ? 'legacyUiReady' : 'strictUiReady'};
|
||||
if (uiReady || startupState === 'failed' || Date.now() >= deadline) {
|
||||
return {
|
||||
...endpoint,
|
||||
uiPath: globalThis.location?.pathname ?? '',
|
||||
uiReady,
|
||||
uiStartupState: ${allowLegacyUi ? "uiReady ? 'legacy-ready' : (readyState ?? startupState)" : 'readyState ?? startupState'},
|
||||
uiTextLength,
|
||||
};
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
}
|
||||
})()
|
||||
`,
|
||||
},
|
||||
}));
|
||||
});
|
||||
const remote = result?.result?.value;
|
||||
if (!isValidEndpoint(remote)) throw new Error('Tauri returned an invalid service endpoint');
|
||||
if (!isValidDesktopRuntime(remote, allowLegacyUi)) {
|
||||
throw new Error('Waggle UI did not render its application shell');
|
||||
}
|
||||
return {
|
||||
bootstrapToken: remote.bootstrapToken,
|
||||
uiPath: remote.uiPath,
|
||||
uiReady: remote.uiReady,
|
||||
uiStartupState: remote.uiStartupState,
|
||||
uiTextLength: remote.uiTextLength,
|
||||
};
|
||||
} finally {
|
||||
clearTimeout(timer);
|
||||
socket.close();
|
||||
}
|
||||
}
|
||||
|
||||
async function resolveToken(port, timeoutMs, allowLegacyUi) {
|
||||
const deadline = Date.now() + timeoutMs;
|
||||
let lastError = 'WebView target not ready';
|
||||
while (Date.now() < deadline) {
|
||||
try {
|
||||
const targets = await fetchTargets(port);
|
||||
for (const target of targets) {
|
||||
try {
|
||||
return await evaluateEnsureService(
|
||||
target.webSocketDebuggerUrl,
|
||||
Math.min(15_000, deadline - Date.now()),
|
||||
allowLegacyUi,
|
||||
);
|
||||
} catch (error) {
|
||||
lastError = error instanceof Error ? error.message : 'CDP evaluation failed';
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
lastError = error instanceof Error ? error.message : 'WebView debug endpoint unavailable';
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, 250));
|
||||
}
|
||||
throw new Error(lastError);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
if (process.argv.includes('--self-test')) {
|
||||
if (typeof WebSocket !== 'function') throw new Error('WebSocket client is unavailable');
|
||||
const valid = 'a'.repeat(MIN_TOKEN_LENGTH);
|
||||
if (!isValidEndpoint({ port: 3333, instanceId: 'test-instance', bootstrapToken: valid })) {
|
||||
throw new Error('valid endpoint fixture rejected');
|
||||
}
|
||||
if (isValidEndpoint({ port: 3333, instanceId: 'test-instance', bootstrapToken: 'short' })) {
|
||||
throw new Error('short token fixture accepted');
|
||||
}
|
||||
const validRuntime = {
|
||||
port: 3333,
|
||||
instanceId: 'test-instance',
|
||||
bootstrapToken: valid,
|
||||
uiPath: '/home',
|
||||
uiReady: true,
|
||||
uiStartupState: 'ready',
|
||||
uiTextLength: 10,
|
||||
};
|
||||
if (!isValidDesktopRuntime(validRuntime)) throw new Error('valid desktop runtime rejected');
|
||||
if (isValidDesktopRuntime({ ...validRuntime, uiReady: false })) {
|
||||
throw new Error('blank desktop runtime accepted');
|
||||
}
|
||||
if (isValidDesktopRuntime({ ...validRuntime, uiTextLength: 0 })) {
|
||||
throw new Error('empty desktop runtime accepted');
|
||||
}
|
||||
if (isValidDesktopRuntime({ ...validRuntime, uiStartupState: 'loading' })) {
|
||||
throw new Error('loading desktop runtime accepted');
|
||||
}
|
||||
if (isValidDesktopRuntime({ ...validRuntime, uiStartupState: 'failed' })) {
|
||||
throw new Error('failed desktop runtime accepted');
|
||||
}
|
||||
if (!isValidDesktopRuntime({ ...validRuntime, uiStartupState: 'legacy-ready' }, true)) {
|
||||
throw new Error('valid legacy desktop runtime rejected');
|
||||
}
|
||||
if (isValidDesktopRuntime({ ...validRuntime, uiReady: false, uiStartupState: 'legacy-ready' }, true)) {
|
||||
throw new Error('blank legacy desktop runtime accepted');
|
||||
}
|
||||
console.log(JSON.stringify({ pass: true, cases: 9 }));
|
||||
return;
|
||||
}
|
||||
const options = parseArgs(process.argv.slice(2));
|
||||
const runtime = await resolveToken(options.port, options.timeoutMs, options.allowLegacyUi);
|
||||
console.log(JSON.stringify(runtime));
|
||||
}
|
||||
|
||||
main().catch((error) => fail(error instanceof Error ? error.message : 'unknown failure'));
|
||||
Reference in New Issue
Block a user