scripts/preview.js
Rama main · réplica de lectura del repositorio original.
import { randomBytes } from "node:crypto";
import { join } from "node:path";
import { fileURLToPath } from "node:url";
import { attachBrowserReopenOnEnter, openBrowser } from "../src/browser.js";
import { ensureLocalDashboardBrowserLaunchRoot } from "../src/services/local-dashboard-control.js";
import { startWizardServer } from "../src/web/server.js";
const SESSION_TOKEN_PATTERN = /^[A-Za-z0-9_-]{43}$/u;
const previewRegistrationId = "preview_registration_1";
const previewAccount = Object.freeze({
registrationId: previewRegistrationId, label: "Preview account",
email: "preview@example.test", identity: "verified", session: "connected",
planPermission: "granted", planEnabled: true, ownership: "owned",
generation: "68f572d8-2b56-4ef2-a6c2-7791078a1161",
ownerHostId: "urn:uuid:38031c97-8e16-402b-85f8-6c7eb17e72bb",
ownerRuntimeId: "local", needsPlanWelcome: false,
});
// Select sanitized UI state: RELMIO_PREVIEW_FIXTURE=signed-out|connected|reauthorize|
// plan-not-granted|usage-limit|handoff-pending|staged node scripts/preview.js.
export const PREVIEW_FIXTURES = Object.freeze([
"signed-out", "connected", "reauthorize", "plan-not-granted", "usage-limit", "handoff-pending", "staged",
]);
export function createPreviewServices(fixture = "connected") {
if (!PREVIEW_FIXTURES.includes(fixture)) throw new TypeError("Unknown sanitized preview fixture.");
const account = { ...previewAccount,
session: ["signed-out", "reauthorize"].includes(fixture) ? fixture : "connected",
planPermission: fixture === "plan-not-granted" ? "not-granted" : "granted",
planEnabled: !["signed-out", "reauthorize", "plan-not-granted"].includes(fixture),
ownership: fixture === "handoff-pending" ? "handoff-pending" : "owned",
};
// The guide choice lives in memory for this preview run; preview never writes the preference file.
const preferences = {};
return {
async readUiPreferences() { return { ...preferences }; },
async writeUiPreferences({ preferences: { guide } }) { preferences.guide = guide; },
async listAuthRegistrations() { return [account]; },
async getSelectedRegistration() { return previewRegistrationId; },
async getAuthStatus({ registrationId }) {
return { ...account, exists: registrationId === previewRegistrationId && account.session === "connected" };
},
async readRegistration({ registrationId }) {
if (registrationId !== previewRegistrationId) throw new Error("Unknown preview account.");
return { registrationId, clientId: "preview-client", generation: account.generation };
},
async listSiwcModels() { return []; },
async getProjectMeta() {
return { version: "preview", stars: null, checkedAt: new Date().toISOString() };
},
async getSshCapabilities() { return { agent: { status: "unavailable", transport: null } }; },
async reviewVpsSiwcTarget() { return { n8nContainerId: "a".repeat(64), networkId: "b".repeat(64) }; },
async getVpsSiwcInstallationStatus() {
return fixture === "staged" ? { state: "staged", staging: {
installId: "preview_installation_1", registrationId: previewRegistrationId, stage: "prepared",
} } : { state: "absent" };
},
async scanHostFingerprint() {
return "SHA256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
},
async connectVerified(request) {
return {
close() {},
identity: Object.freeze({
host: request.host, port: Number(request.port), fingerprint: request.expectedFingerprint,
username: request.username, authentication: request.useAgent ? "agent" : "password",
privilege: request.privilege, loginUid: request.privilege === "root" ? 0 : 1000, effectiveUid: 0,
}),
scope: request.privilege === "root" ? "vps" : "local-model-only",
};
},
async discoverN8n() {
return {
dockerVersion: "28.3.2",
composeVersion: "2.38.2",
containers: [
{
id: "a".repeat(64),
image: "docker.n8n.io/n8nio/n8n",
name: "n8n-n8n-1",
state: "running",
},
],
};
},
async discoverNetworks() {
return { networks: ["proxy"], recommended: "proxy" };
},
async installSidecar() {
throw new Error("Sanitized preview cannot install a ChatGPT sidecar.");
},
};
}
export async function runPreview({
env = process.env,
log = console.log,
open = openBrowser,
attachReopen = attachBrowserReopenOnEnter,
startServer = startWizardServer,
ensureBrowserLaunchRoot = ensureLocalDashboardBrowserLaunchRoot,
createSessionToken = () => randomBytes(32).toString("base64url"),
signalTarget = process,
} = {}) {
if (
typeof log !== "function" || typeof open !== "function" ||
typeof attachReopen !== "function" || typeof startServer !== "function" ||
typeof ensureBrowserLaunchRoot !== "function" ||
typeof createSessionToken !== "function" ||
!signalTarget || typeof signalTarget.once !== "function" ||
typeof signalTarget.removeListener !== "function"
) {
throw new TypeError("The Relmio preview launcher adapter is invalid.");
}
const sessionToken = createSessionToken();
if (!SESSION_TOKEN_PATTERN.test(sessionToken)) {
throw new Error("Relmio could not create a strong preview session.");
}
const fixture = env.RELMIO_PREVIEW_FIXTURE ?? "connected";
const services = createPreviewServices(fixture);
const browserHandoffRoot = await ensureBrowserLaunchRoot({ env });
let wizard;
let detachReopen = () => {};
let closePromise;
function onSignal() {
void close().catch(() => {});
}
function close() {
if (closePromise) return closePromise;
closePromise = (async () => {
signalTarget.removeListener("SIGINT", onSignal);
signalTarget.removeListener("SIGTERM", onSignal);
detachReopen();
await wizard?.close();
})();
return closePromise;
}
try {
wizard = await startServer({
sessionToken,
storageRoot: join(browserHandoffRoot, "siwc-preview"),
services,
previewMode: true,
previewFixture: fixture,
browserHandoffRoot,
});
const prepareLaunch = async () => await wizard.prepareBrowserLaunch("/");
await open(await prepareLaunch());
detachReopen = attachReopen({ prepareLaunch, open, write: log });
if (typeof detachReopen !== "function") {
throw new TypeError("The Relmio preview reopen adapter is invalid.");
}
signalTarget.once("SIGINT", onSignal);
signalTarget.once("SIGTERM", onSignal);
log(
"Sanitized preview opened through a private browser handoff. Live ChatGPT sign-in is disabled. Press Control+C to stop.",
);
return Object.freeze({ close });
} catch (error) {
await close();
throw error;
}
}
if (process.argv[1] === fileURLToPath(import.meta.url)) {
try {
await runPreview();
} catch (error) {
console.error(error.message);
process.exitCode = 1;
}
}