openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
106 lines (105 loc) • 5.46 kB
JavaScript
import { r as runCommandWithTimeout } from "./exec-BIE-3oLG.js";
import { s as isLoopbackHost } from "./net-DbNPs6Xm.js";
import { t as ErrorCodes } from "./gateway-error-details-w0nAGBBp.js";
import { Ot as validateDevicePairSetupCodeParams, kt as validateDevicePairSetupStatusParams } from "./src-BiL5aQto.js";
import { d as errorShape } from "./error-codes-Bo8q2D1o.js";
import { F as VOICE_NODE_PAIRING_SETUP_BOOTSTRAP_PROFILE, N as NODE_PAIRING_SETUP_BOOTSTRAP_PROFILE, P as PAIRING_SETUP_BOOTSTRAP_PROFILE, u as readDevicePairSetupCompletion } from "./device-bootstrap-DRFMRCcr.js";
import { n as renderQrPngDataUrl } from "./qr-image-CnPhdkea.js";
import { t as formatForLog } from "./ws-log-Dkg9wKNU.js";
import { t as assertValidParams } from "./validation-pzrlzFvo.js";
import { a as resolvePairingSetupFromConfig, n as encodePairingSetupCode, r as resolveConfiguredPairingPublicUrl } from "./setup-code-Dv85EyDz.js";
import { n as registerDevicePairingJoinCode } from "./device-pairing-join-code-D6lAWKAx.js";
//#region src/gateway/server-methods/device-pair-setup.ts
const MAX_QR_DATA_URL_LENGTH = 16384;
function resolveDevicePairingJoinBaseUrl(payload) {
for (const candidate of payload.urls ?? [payload.url]) {
const parsed = new URL(candidate);
if (parsed.protocol === "wss:") {
parsed.protocol = "https:";
return parsed;
}
if (parsed.protocol === "ws:" && isLoopbackHost(parsed.hostname)) {
parsed.protocol = "http:";
return parsed;
}
}
throw new Error("Join URLs require a TLS gateway endpoint, except for loopback. Use the setup code directly for plaintext LAN pairing.");
}
/** Gateway handler for producing a device-pairing setup code + connect QR. */
const devicePairSetupHandlers = {
"device.pair.setupCode": async ({ params, respond, context }) => {
if (!assertValidParams(params, validateDevicePairSetupCodeParams, "device.pair.setupCode", respond)) return;
try {
if (params.joinUrl === true && params.bootstrapProfile !== void 0 && params.bootstrapProfile !== "node") {
respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, "Join URLs require bootstrapProfile=node."));
return;
}
const config = context.getRuntimeConfig();
const requestPublicUrl = typeof params.publicUrl === "string" ? params.publicUrl : void 0;
const configuredPublicUrl = params.preferRemoteUrl === true ? void 0 : resolveConfiguredPairingPublicUrl(config);
const publicUrl = requestPublicUrl ?? configuredPublicUrl;
const resolved = await resolvePairingSetupFromConfig(config, {
env: process.env,
publicUrl,
preferRemoteUrl: params.preferRemoteUrl === true,
localTlsFingerprint: context.gatewayTlsFingerprint,
...params.joinUrl === true || params.bootstrapProfile ? { bootstrapProfile: params.joinUrl === true || params.bootstrapProfile === "node" ? NODE_PAIRING_SETUP_BOOTSTRAP_PROFILE : params.bootstrapProfile === "voice-node" ? VOICE_NODE_PAIRING_SETUP_BOOTSTRAP_PROFILE : PAIRING_SETUP_BOOTSTRAP_PROFILE } : {},
runCommandWithTimeout: async (argv, runOpts) => await runCommandWithTimeout(argv, { timeoutMs: runOpts.timeoutMs })
});
if (!resolved.ok) {
respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, resolved.error));
return;
}
const setupCode = encodePairingSetupCode(resolved.payload);
let joinUrl;
if (params.joinUrl === true) {
const parsedJoinUrl = resolveDevicePairingJoinBaseUrl(resolved.payload);
const shortcode = registerDevicePairingJoinCode({
payload: resolved.payload,
expiresAtMs: resolved.expiresAtMs
});
parsedJoinUrl.pathname = `${parsedJoinUrl.pathname.replace(/\/+$/u, "")}/j/${shortcode}`;
parsedJoinUrl.search = "";
parsedJoinUrl.hash = "";
joinUrl = parsedJoinUrl.toString();
}
const renderedQr = params.includeQr !== false ? await renderQrPngDataUrl(setupCode).catch(() => void 0) : void 0;
const qrDataUrl = renderedQr && renderedQr.length <= MAX_QR_DATA_URL_LENGTH ? renderedQr : void 0;
respond(true, {
setupId: resolved.setupId,
expiresAtMs: resolved.expiresAtMs,
setupCode,
...joinUrl ? { joinUrl } : {},
...qrDataUrl ? { qrDataUrl } : {},
gatewayUrl: resolved.payload.url,
...resolved.payload.urls ? { gatewayUrls: resolved.payload.urls } : {},
auth: resolved.authLabel,
urlSource: requestPublicUrl ? "request.publicUrl" : resolved.urlSource,
access: resolved.access,
...resolved.accessDowngraded ? { accessDowngraded: true } : {}
}, void 0);
} catch (err) {
respond(false, void 0, errorShape(ErrorCodes.UNAVAILABLE, formatForLog(err)));
}
},
"device.pair.setupStatus": async ({ params, respond }) => {
if (!assertValidParams(params, validateDevicePairSetupStatusParams, "device.pair.setupStatus", respond)) return;
try {
const completion = await readDevicePairSetupCompletion({ setupId: params.setupId });
respond(true, completion ? (() => {
const payload = {
setupId: completion.setupId,
deviceId: completion.deviceId,
...completion.deviceName ? { deviceName: completion.deviceName } : {},
access: completion.access,
ts: completion.completedAtMs
};
return completion.deliveryState === "confirmed" ? { completion: payload } : { deliveryUncertain: payload };
})() : {}, void 0);
} catch (err) {
respond(false, void 0, errorShape(ErrorCodes.UNAVAILABLE, formatForLog(err)));
}
}
};
//#endregion
export { devicePairSetupHandlers };