



























@@ -16,6 +16,11 @@ import {
1616publicKeyRawBase64UrlFromPem,
1717signDevicePayload,
1818} from "../infra/device-identity.js";
19+import {
20+approveDevicePairing,
21+getPairedDevice,
22+requestDevicePairing,
23+} from "../infra/device-pairing.js";
1924import { drainSystemEvents, peekSystemEvents } from "../infra/system-events.js";
2025import { rawDataToString } from "../infra/ws.js";
2126import { resetLogger, setLoggerOverride } from "../logging.js";
@@ -838,42 +843,121 @@ export function testOnlyResolveAuthTokenForSignature(opts?: {
838843return resolveAuthTokenForSignature(opts);
839844}
840845846+type ConnectReqClient = {
847+id: string;
848+displayName?: string;
849+version: string;
850+platform: string;
851+mode: string;
852+deviceFamily?: string;
853+modelIdentifier?: string;
854+instanceId?: string;
855+};
856+857+type ConnectReqDevice = {
858+id: string;
859+publicKey: string;
860+signature: string;
861+signedAt: number;
862+nonce?: string;
863+};
864+865+type ConnectReqOptions = {
866+token?: string;
867+bootstrapToken?: string;
868+deviceToken?: string;
869+password?: string;
870+skipDefaultAuth?: boolean;
871+minProtocol?: number;
872+maxProtocol?: number;
873+client?: ConnectReqClient;
874+role?: string;
875+scopes?: string[];
876+caps?: string[];
877+commands?: string[];
878+permissions?: Record<string, boolean>;
879+device?: ConnectReqDevice | null;
880+deviceIdentityPath?: string;
881+skipConnectChallengeNonce?: boolean;
882+prePairDevice?: boolean;
883+timeoutMs?: number;
884+};
885+886+function shouldPrePairTestDevice(params: {
887+client: ConnectReqClient;
888+opts?: ConnectReqOptions;
889+}): boolean {
890+if (params.opts?.device !== undefined || params.opts?.deviceToken) {
891+return false;
892+}
893+if (params.opts?.prePairDevice !== undefined) {
894+return params.opts.prePairDevice;
895+}
896+if (params.opts?.skipDefaultAuth === true) {
897+return false;
898+}
899+return (
900+params.client.mode === GATEWAY_CLIENT_MODES.WEBCHAT ||
901+params.client.id === GATEWAY_CLIENT_NAMES.WEBCHAT_UI
902+);
903+}
904+905+function pairedDeviceAllowsScopes(params: {
906+paired: Awaited<ReturnType<typeof getPairedDevice>>;
907+publicKey: string;
908+role: string;
909+scopes: string[];
910+}): boolean {
911+if (!params.paired || params.paired.publicKey !== params.publicKey) {
912+return false;
913+}
914+const pairedRoles = params.paired.roles ?? (params.paired.role ? [params.paired.role] : []);
915+if (!pairedRoles.includes(params.role)) {
916+return false;
917+}
918+const approvedScopes = params.paired.approvedScopes ?? params.paired.scopes ?? [];
919+return params.scopes.every((scope) => approvedScopes.includes(scope));
920+}
921+922+async function prePairTestDevice(params: {
923+device: ConnectReqDevice;
924+client: ConnectReqClient;
925+role: string;
926+scopes: string[];
927+}): Promise<void> {
928+const paired = await getPairedDevice(params.device.id);
929+if (
930+pairedDeviceAllowsScopes({
931+ paired,
932+publicKey: params.device.publicKey,
933+role: params.role,
934+scopes: params.scopes,
935+})
936+) {
937+return;
938+}
939+const pairing = await requestDevicePairing({
940+deviceId: params.device.id,
941+publicKey: params.device.publicKey,
942+role: params.role,
943+scopes: params.scopes,
944+clientId: params.client.id,
945+clientMode: params.client.mode,
946+platform: params.client.platform,
947+deviceFamily: params.client.deviceFamily,
948+silent: false,
949+});
950+const approved = await approveDevicePairing(pairing.request.requestId, {
951+callerScopes: params.scopes,
952+});
953+if (approved?.status !== "approved") {
954+throw new Error(`failed to pre-pair test device ${params.device.id}`);
955+}
956+}
957+841958export async function connectReq(
842959ws: WebSocket,
843-opts?: {
844-token?: string;
845-bootstrapToken?: string;
846-deviceToken?: string;
847-password?: string;
848-skipDefaultAuth?: boolean;
849-minProtocol?: number;
850-maxProtocol?: number;
851-client?: {
852-id: string;
853-displayName?: string;
854-version: string;
855-platform: string;
856-mode: string;
857-deviceFamily?: string;
858-modelIdentifier?: string;
859-instanceId?: string;
860-};
861-role?: string;
862-scopes?: string[];
863-caps?: string[];
864-commands?: string[];
865-permissions?: Record<string, boolean>;
866-device?: {
867-id: string;
868-publicKey: string;
869-signature: string;
870-signedAt: number;
871-nonce?: string;
872-} | null;
873-deviceIdentityPath?: string;
874-skipConnectChallengeNonce?: boolean;
875-timeoutMs?: number;
876-},
960+opts?: ConnectReqOptions,
877961): Promise<ConnectResponse> {
878962const { randomUUID } = await import("node:crypto");
879963const id = randomUUID();
@@ -956,6 +1040,20 @@ export async function connectReq(
9561040nonce: connectChallengeNonce,
9571041};
9581042})();
1043+if (
1044+device &&
1045+shouldPrePairTestDevice({
1046+ client,
1047+ opts,
1048+})
1049+) {
1050+await prePairTestDevice({
1051+ device,
1052+ client,
1053+ role,
1054+scopes: requestedScopes,
1055+});
1056+}
9591057const isResponseForId = (o: unknown): boolean => {
9601058if (!o || typeof o !== "object" || Array.isArray(o)) {
9611059return false;
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。