Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
157 lines
5.9 KiB
JavaScript
157 lines
5.9 KiB
JavaScript
const assert = require("node:assert/strict");
|
|
const { readFileSync } = require("node:fs");
|
|
const test = require("node:test");
|
|
const vm = require("node:vm");
|
|
const deepLink = require("./deepLink.cjs");
|
|
|
|
const source = readFileSync(require.resolve("./main.cjs"), "utf8");
|
|
|
|
function section(start, end) {
|
|
const from = source.indexOf(start);
|
|
const to = source.indexOf(end, from);
|
|
assert.ok(from >= 0 && to > from);
|
|
return source.slice(from, to);
|
|
}
|
|
|
|
// Execute the main process's actual queue, preference handler, and delivery
|
|
// functions. Only desktop services and renderer readiness are controlled here.
|
|
function createHarness() {
|
|
const handlers = new Map();
|
|
const deliveries = [];
|
|
const waiting = [];
|
|
const timers = [];
|
|
const sandbox = {
|
|
...deepLink,
|
|
process: { argv: ["netcatty"] },
|
|
app: { isReady: () => false },
|
|
ipcMain: { handle: (name, handler) => handlers.set(name, handler) },
|
|
isDev: false,
|
|
readSshDeepLinkEnabledPreference: () => true,
|
|
readJmsDeepLinkEnabledPreference: () => false,
|
|
resolveOpenTerminalPathsFromArgs: () => [],
|
|
resolveExplorerContextMenuEnabled: () => ({ enabled: false }),
|
|
applySshProtocolClientPreference: () => true,
|
|
writeSshDeepLinkEnabledPreference: () => true,
|
|
createAndShowMainWindow: async () => ({}),
|
|
focusMainWindow: () => {},
|
|
getWindowManager: () => ({
|
|
sendWhenRendererReady: (_win, channel, payload, options) => new Promise((resolve) => {
|
|
waiting.push({ channel, payload, options, resolve });
|
|
}),
|
|
}),
|
|
setTimeout: (callback) => timers.push(callback),
|
|
console: { warn: () => {} },
|
|
};
|
|
const context = vm.createContext(sandbox);
|
|
vm.runInContext([
|
|
section("let sshDeepLinkEnabled =", 'ipcMain?.handle?.("netcatty:deepLink:jms:setEnabled"'),
|
|
section("async function deliverSshDeepLink(", "async function deliverOpenTerminalPath("),
|
|
`globalThis.launch = {
|
|
ssh: { queue: queueSshDeepLink, flush: flushPendingSshDeepLinks, pending: pendingSshDeepLinkUrls },
|
|
telnet: { queue: queueTelnetDeepLink, flush: flushPendingTelnetDeepLinks, pending: pendingTelnetDeepLinkUrls },
|
|
};`,
|
|
].join("\n"), context);
|
|
|
|
return {
|
|
launch: context.launch,
|
|
deliveries,
|
|
timers,
|
|
async setEnabled(enabled) {
|
|
await handlers.get("netcatty:deepLink:ssh:setEnabled")(null, { enabled });
|
|
},
|
|
async nextDelivery() {
|
|
for (let attempt = 0; attempt < 20 && waiting.length === 0; attempt++) await Promise.resolve();
|
|
assert.ok(waiting.length > 0, "delivery must reach renderer readiness");
|
|
const item = waiting.shift();
|
|
return {
|
|
finish(result) {
|
|
if (result) return item.resolve(result);
|
|
if (!item.options.shouldSend()) {
|
|
return item.resolve({ success: false, reason: item.options.cancelReason });
|
|
}
|
|
deliveries.push({ channel: item.channel, url: item.payload.url });
|
|
item.resolve({ success: true });
|
|
},
|
|
};
|
|
},
|
|
};
|
|
}
|
|
|
|
for (const protocol of ["ssh", "telnet"]) {
|
|
test(`${protocol}: disable/re-enable permanently cancels an in-flight scheme request`, async () => {
|
|
const h = createHarness();
|
|
const queue = h.launch[protocol];
|
|
queue.queue(`${protocol}://old.example`);
|
|
const flushing = queue.flush();
|
|
const old = await h.nextDelivery();
|
|
await h.setEnabled(false);
|
|
await h.setEnabled(true);
|
|
old.finish();
|
|
await flushing;
|
|
assert.deepEqual(h.deliveries, []);
|
|
assert.equal(queue.pending.length, 0);
|
|
assert.equal(h.timers.length, 0);
|
|
});
|
|
|
|
test(`${protocol}: cancelled schemes cannot return through a failed-delivery retry`, async () => {
|
|
const h = createHarness();
|
|
const queue = h.launch[protocol];
|
|
queue.queue(`${protocol}://old.example`);
|
|
const flushing = queue.flush();
|
|
const old = await h.nextDelivery();
|
|
await h.setEnabled(false);
|
|
await h.setEnabled(true);
|
|
old.finish({ success: false, reason: "window-destroyed" });
|
|
await flushing;
|
|
assert.equal(queue.pending.length, 0, "a cancelled request must not be requeued");
|
|
assert.equal(h.timers.length, 0);
|
|
});
|
|
|
|
test(`${protocol}: disabling removes queued schemes but preserves queued CLI and new requests`, async () => {
|
|
const h = createHarness();
|
|
const queue = h.launch[protocol];
|
|
queue.queue(`${protocol}://old.example`);
|
|
queue.queue(`${protocol}://queued-old.example`);
|
|
queue.queue(`${protocol}://cli.example`, { viaCommandLine: true });
|
|
const flushing = queue.flush();
|
|
const old = await h.nextDelivery();
|
|
await h.setEnabled(false);
|
|
await h.setEnabled(true);
|
|
queue.queue(`${protocol}://new.example`);
|
|
old.finish();
|
|
(await h.nextDelivery()).finish();
|
|
(await h.nextDelivery()).finish();
|
|
await flushing;
|
|
assert.deepEqual(h.deliveries.map((item) => item.url), [
|
|
`${protocol}://cli.example`, `${protocol}://new.example`,
|
|
]);
|
|
});
|
|
|
|
test(`${protocol}: an in-flight CLI request survives disabling and re-enabling schemes`, async () => {
|
|
const h = createHarness();
|
|
const queue = h.launch[protocol];
|
|
queue.queue(`${protocol}://cli.example`, { viaCommandLine: true });
|
|
const flushing = queue.flush();
|
|
const cli = await h.nextDelivery();
|
|
await h.setEnabled(false);
|
|
await h.setEnabled(true);
|
|
cli.finish();
|
|
await flushing;
|
|
assert.deepEqual(h.deliveries.map((item) => item.url), [`${protocol}://cli.example`]);
|
|
});
|
|
|
|
test(`${protocol}: CLI retries remain queued when schemes are disabled`, async () => {
|
|
const h = createHarness();
|
|
const queue = h.launch[protocol];
|
|
queue.queue(`${protocol}://cli.example`, { viaCommandLine: true });
|
|
const flushing = queue.flush();
|
|
const cli = await h.nextDelivery();
|
|
await h.setEnabled(false);
|
|
cli.finish({ success: false, reason: "window-destroyed" });
|
|
await flushing;
|
|
assert.equal(queue.pending.length, 1);
|
|
assert.equal(h.timers.length, 1);
|
|
assert.equal(queue.pending[0].rawUrl, `${protocol}://cli.example`);
|
|
});
|
|
}
|