@honor-claw/yoyo 0.0.1-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/index.ts +25 -0
  2. package/openclaw.plugin.json +28 -0
  3. package/package.json +59 -0
  4. package/skills/yoyo-control/SKILL.md +346 -0
  5. package/skills/yoyo-control/references/capture-screenshot.md +66 -0
  6. package/skills/yoyo-control/references/local-search.md +27 -0
  7. package/skills/yoyo-control/references/open-app.md +54 -0
  8. package/skills/yoyo-control/references/phone-call.md +217 -0
  9. package/skills/yoyo-control/references/schedule.md +107 -0
  10. package/skills/yoyo-control/references/screen-recorder.md +67 -0
  11. package/skills/yoyo-control/references/search-contact.md +37 -0
  12. package/skills/yoyo-control/references/send-message.md +155 -0
  13. package/skills/yoyo-control/references/volume.md +536 -0
  14. package/skills/yoyo-control/scripts/README.md +103 -0
  15. package/skills/yoyo-control/scripts/invoke.js +119 -0
  16. package/skills/yoyo-control/scripts/volume-up.json +7 -0
  17. package/src/apis/claw-cloud.ts +74 -0
  18. package/src/apis/helpers.ts +10 -0
  19. package/src/apis/honor-auth.ts +148 -0
  20. package/src/apis/http-client.ts +239 -0
  21. package/src/apis/index.ts +8 -0
  22. package/src/apis/types.ts +47 -0
  23. package/src/cloud-channel/channel.ts +230 -0
  24. package/src/cloud-channel/client.ts +312 -0
  25. package/src/cloud-channel/index.ts +4 -0
  26. package/src/cloud-channel/types.ts +81 -0
  27. package/src/commands/index.ts +19 -0
  28. package/src/commands/login/impl.ts +21 -0
  29. package/src/commands/login/index.ts +1 -0
  30. package/src/commands/logout/index.ts +53 -0
  31. package/src/commands/status/index.ts +64 -0
  32. package/src/gateway-client/client.deprecated.ts +376 -0
  33. package/src/gateway-client/client.ts +76 -0
  34. package/src/gateway-client/device/auth.ts +57 -0
  35. package/src/gateway-client/device/builder.ts +105 -0
  36. package/src/gateway-client/device/helpers.ts +40 -0
  37. package/src/gateway-client/device/identity.ts +251 -0
  38. package/src/gateway-client/device/index.ts +40 -0
  39. package/src/gateway-client/device/types.ts +57 -0
  40. package/src/gateway-client/index.ts +2 -0
  41. package/src/gateway-client/types.deprecated.ts +217 -0
  42. package/src/gateway-client/types.ts +8 -0
  43. package/src/honor-auth/browser.ts +82 -0
  44. package/src/honor-auth/callback-server.ts +112 -0
  45. package/src/honor-auth/cloud.ts +70 -0
  46. package/src/honor-auth/config.ts +35 -0
  47. package/src/honor-auth/index.ts +2 -0
  48. package/src/honor-auth/token-manager.ts +80 -0
  49. package/src/honor-auth/types.ts +43 -0
  50. package/src/index.ts +10 -0
  51. package/src/modules/claw-configs/config-manager.ts +172 -0
  52. package/src/modules/claw-configs/index.ts +7 -0
  53. package/src/modules/claw-configs/types.ts +30 -0
  54. package/src/modules/device/device-info.ts +70 -0
  55. package/src/modules/device/index.ts +3 -0
  56. package/src/modules/device/providers/base.ts +27 -0
  57. package/src/modules/device/providers/pad.ts +114 -0
  58. package/src/modules/device/providers/windows.ts +67 -0
  59. package/src/modules/device/registry.ts +34 -0
  60. package/src/modules/login/impl.ts +70 -0
  61. package/src/modules/login/index.ts +6 -0
  62. package/src/runtime.ts +14 -0
  63. package/src/schemas.ts +20 -0
  64. package/src/services/connection/impl.ts +259 -0
  65. package/src/services/connection/index.ts +1 -0
  66. package/src/services/connection/types.ts +20 -0
  67. package/src/types.ts +64 -0
  68. package/src/utils/id.ts +8 -0
  69. package/src/utils/jwt.ts +36 -0
  70. package/src/utils/logger.ts +20 -0
  71. package/src/utils/proxy.ts +58 -0
@@ -0,0 +1,53 @@
1
+ import { type OpenClawPluginApi } from "openclaw/plugin-sdk";
2
+ import { getConnectionManager } from "../../services/connection-manager.js";
3
+ import { type Command } from "commander";
4
+
5
+ export function registerLogoutCommand(api: OpenClawPluginApi, command: Command) {
6
+ const nextCommand = command
7
+ .command("logout")
8
+ .description("Logout and disconnect from YOYOClaw")
9
+ .option("--force", "Force logout without confirmation")
10
+ .action(async (options) => {
11
+ api.logger.info("honor logout CLI command called");
12
+
13
+ const { force } = options;
14
+ const connectionManager = getConnectionManager();
15
+ const isConnected = connectionManager.isConnected();
16
+ const connectedDevices = connectionManager.getConnectedDevices();
17
+
18
+ if (!isConnected) {
19
+ console.log("⚠️ 当前未连接到 YOYOClaw");
20
+ return;
21
+ }
22
+
23
+ // 如果有活跃的设备连接且不是强制模式,提示确认
24
+ if (connectedDevices.length > 0 && !force) {
25
+ console.log(`⚠️ 当前有 ${connectedDevices.length} 个活跃的设备连接:`);
26
+ for (const device of connectedDevices) {
27
+ console.log(` - ${device}`);
28
+ }
29
+ console.log("\n⚠️ 断开连接将停止所有设备间的通信");
30
+ console.log('💡 使用 --force 选项可以跳过此确认');
31
+
32
+ // 注意:在命令行工具中,我们无法直接等待用户输入
33
+ // 这里我们给出提示,然后继续执行
34
+ console.log("\n🔌 正在断开连接...");
35
+ } else {
36
+ console.log("🔌 正在断开连接...");
37
+ }
38
+
39
+ try {
40
+ await connectionManager.cleanup();
41
+ console.log("✅ WebSocket 连接已关闭");
42
+ console.log("✅ 所有设备连接已清理");
43
+ console.log("✅ 资源已释放");
44
+ console.log("👋 已登出");
45
+ } catch (error) {
46
+ const errorMessage = error instanceof Error ? error.message : String(error);
47
+ console.error("❌ 登出失败:", errorMessage);
48
+ throw error;
49
+ }
50
+ });
51
+
52
+ return nextCommand;
53
+ }
@@ -0,0 +1,64 @@
1
+ import { type OpenClawPluginApi } from 'openclaw/plugin-sdk';
2
+ import { type Command } from 'commander';
3
+ import { getConnectionStatus } from '../../services/connection/impl.js';
4
+
5
+ export function registerStatusCommand(api: OpenClawPluginApi, command: Command) {
6
+ const nextCommand = command
7
+ .command('status')
8
+ .description('Show YOYOClaw connection status')
9
+ .action(async () => {
10
+ api.logger.info('YOYOClaw status CLI command called');
11
+
12
+ // 获取连接状态
13
+ const status = getConnectionStatus();
14
+
15
+ // 连接状态
16
+ const stateEmoji = getStateEmoji(status);
17
+ const stateText = getStateText(status);
18
+
19
+ api.logger.info(`连接状态: ${stateEmoji} ${stateText}`);
20
+
21
+ if (status === 'connected') {
22
+ // 已连接状态
23
+ api.logger.info('\n✅ YOYOClaw 连接正常');
24
+ } else if (status === 'connecting') {
25
+ // 连接中状态
26
+ api.logger.info('\n⏳ 正在建立连接...');
27
+ } else {
28
+ // 未连接状态
29
+ api.logger.info("\n💡 请先执行 'yoyoclaw login' 建立连接");
30
+ }
31
+ });
32
+
33
+ return nextCommand;
34
+ }
35
+
36
+ /**
37
+ * 获取状态对应的 emoji
38
+ */
39
+ function getStateEmoji(status: 'idle' | 'connecting' | 'connected'): string {
40
+ switch (status) {
41
+ case 'connected':
42
+ return '✅';
43
+ case 'connecting':
44
+ return '⏳';
45
+ case 'idle':
46
+ default:
47
+ return '⚪';
48
+ }
49
+ }
50
+
51
+ /**
52
+ * 获取状态对应的文本
53
+ */
54
+ function getStateText(status: 'idle' | 'connecting' | 'connected'): string {
55
+ switch (status) {
56
+ case 'connected':
57
+ return '已连接';
58
+ case 'connecting':
59
+ return '连接中';
60
+ case 'idle':
61
+ default:
62
+ return '未连接';
63
+ }
64
+ }
@@ -0,0 +1,376 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { WebSocket } from "ws";
3
+ import type {
4
+ EventFrame,
5
+ HelloOk,
6
+ ConnectParams,
7
+ RequestFrame,
8
+ ResponseFrame,
9
+ GatewayClientId,
10
+ GatewayClientMode,
11
+ } from "./types.deprecated.js";
12
+ import {
13
+ loadOrCreateDeviceIdentity,
14
+ signDevicePayload,
15
+ publicKeyRawBase64UrlFromPem,
16
+ } from "./device/identity.js";
17
+ import { buildDeviceAuthPayloadV3 } from "./device/auth.js";
18
+ import type { DeviceIdentity } from "./device/types.js";
19
+ import { rawDataToString } from "openclaw/plugin-sdk";
20
+
21
+ /**
22
+ * 插件 Gateway Client 配置选项(简化版)
23
+ */
24
+ export interface GatewayClientOptions {
25
+ url: string; // ws://127.0.0.1:18789
26
+ clientId?: GatewayClientId;
27
+ clientDisplayName?: string;
28
+ clientVersion: string;
29
+ platform?: string;
30
+ mode?: GatewayClientMode;
31
+ instanceId?: string;
32
+ role?: string;
33
+ scopes?: string[];
34
+ token?: string;
35
+ password?: string;
36
+ locale?: string;
37
+ deviceFamily?: string;
38
+ deviceIdentity?: DeviceIdentity;
39
+ modelIdentifier?: string;
40
+ connectTimeout?: number; // 连接挑战超时时间(毫秒),默认 2000
41
+ logDebug?: (...args: unknown[]) => void;
42
+ logError?: (...args: unknown[]) => void;
43
+ onEvent?: (evt: EventFrame) => void;
44
+ onHelloOk?: (hello: HelloOk) => void;
45
+ onConnectError?: (err: Error) => void;
46
+ onClose?: (code: number, reason: string) => void;
47
+ }
48
+
49
+ // ==================== GatewayClient 类 ====================
50
+
51
+ type Pending = {
52
+ resolve: (value: unknown) => void;
53
+ reject: (err: unknown) => void;
54
+ };
55
+
56
+ /**
57
+ * 精简版 Gateway Client,用于插件场景
58
+ * - 保留 WebSocket 连接
59
+ * - 简化认证流程
60
+ * - 支持直接消息传递
61
+ */
62
+ export class GatewayClient {
63
+ private ws: WebSocket | null = null;
64
+ private opts: GatewayClientOptions;
65
+ private pending = new Map<string, Pending>();
66
+ private closed = false;
67
+ private connectNonce: string | null = null;
68
+ private connectSent = false;
69
+ private connectTimer: NodeJS.Timeout | null = null;
70
+ private paired = false;
71
+ private deviceIdentity: DeviceIdentity;
72
+
73
+ constructor(opts: GatewayClientOptions) {
74
+ this.deviceIdentity = opts.deviceIdentity ?? loadOrCreateDeviceIdentity();
75
+
76
+ this.opts = {
77
+ ...opts,
78
+ deviceIdentity: this.deviceIdentity,
79
+ clientId: opts.clientId,
80
+ clientVersion: opts.clientVersion,
81
+ platform: opts.platform,
82
+ mode: opts.mode,
83
+ role: opts.role,
84
+ scopes: opts.scopes ?? [],
85
+ locale: opts.locale ?? "zh-Hans-CN",
86
+ deviceFamily: opts.deviceFamily,
87
+ logDebug: opts.logDebug ?? (() => {}),
88
+ logError: opts.logError ?? console.error,
89
+ };
90
+ }
91
+
92
+ /**
93
+ * 启动连接
94
+ */
95
+ start() {
96
+ if (this.closed) {
97
+ return;
98
+ }
99
+ const url = this.opts.url;
100
+ this.ws = new WebSocket(url, { maxPayload: 25 * 1024 * 1024 });
101
+
102
+ this.ws.on("open", () => {
103
+ this.queueConnect();
104
+ });
105
+
106
+ this.ws.on("message", (data) => this.handleMessage(rawDataToString(data)));
107
+
108
+ this.ws.on("close", (code, reason) => {
109
+ const reasonText = rawDataToString(reason);
110
+ this.ws = null;
111
+ this.flushPendingErrors(
112
+ new Error(`gateway closed (${code}): ${reasonText}`)
113
+ );
114
+ this.opts.onClose?.(code, reasonText);
115
+ });
116
+
117
+ this.ws.on("error", (err) => {
118
+ this.opts.logDebug(`gateway client error: ${String(err)}`);
119
+ if (!this.connectSent) {
120
+ this.opts.onConnectError?.(
121
+ err instanceof Error ? err : new Error(String(err))
122
+ );
123
+ }
124
+ });
125
+ }
126
+
127
+ /**
128
+ * 停止连接
129
+ */
130
+ stop() {
131
+ this.closed = true;
132
+ if (this.connectTimer) {
133
+ clearTimeout(this.connectTimer);
134
+ this.connectTimer = null;
135
+ }
136
+ this.ws?.close();
137
+ this.ws = null;
138
+ this.flushPendingErrors(new Error("gateway client stopped"));
139
+ }
140
+
141
+ /**
142
+ * 完成配对(插件维度的 node-gateway 配对)
143
+ */
144
+ async pair(): Promise<HelloOk> {
145
+ return new Promise((resolve, reject) => {
146
+ const originalOnHelloOk = this.opts.onHelloOk;
147
+ const originalOnConnectError = this.opts.onConnectError;
148
+
149
+ this.opts.onHelloOk = (hello) => {
150
+ this.paired = true;
151
+ originalOnHelloOk?.(hello);
152
+ this.opts.onHelloOk = originalOnHelloOk;
153
+ this.opts.onConnectError = originalOnConnectError;
154
+ resolve(hello);
155
+ };
156
+
157
+ this.opts.onConnectError = (err) => {
158
+ originalOnConnectError?.(err);
159
+ this.opts.onHelloOk = originalOnHelloOk;
160
+ this.opts.onConnectError = originalOnConnectError;
161
+ reject(err);
162
+ };
163
+
164
+ // 如果已经连接成功,直接返回
165
+ if (this.paired) {
166
+ this.opts.onHelloOk = originalOnHelloOk;
167
+ this.opts.onConnectError = originalOnConnectError;
168
+ resolve({
169
+ type: "hello-ok",
170
+ protocol: 3,
171
+ gateway: { id: "unknown", version: "unknown" },
172
+ });
173
+ }
174
+ });
175
+ }
176
+
177
+ /**
178
+ * 直接发送消息(配对后使用)
179
+ */
180
+ send(data: string | Buffer): void {
181
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
182
+ throw new Error("gateway not connected");
183
+ }
184
+ this.ws.send(data);
185
+ }
186
+
187
+ /**
188
+ * 发送请求并等待响应
189
+ */
190
+ async request<T = unknown>(method: string, params?: unknown): Promise<T> {
191
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
192
+ throw new Error("gateway not connected");
193
+ }
194
+ const id = randomUUID();
195
+ const frame: RequestFrame = { type: "req", id, method, params };
196
+
197
+ const p = new Promise<T>((resolve, reject) => {
198
+ this.pending.set(id, {
199
+ resolve: (value) => resolve(value as T),
200
+ reject,
201
+ });
202
+ });
203
+
204
+ this.ws.send(JSON.stringify(frame));
205
+ return p;
206
+ }
207
+
208
+ /**
209
+ * 发送连接请求
210
+ */
211
+ private sendConnect() {
212
+ if (this.connectSent) {
213
+ return;
214
+ }
215
+ const nonce = this.connectNonce?.trim() ?? "";
216
+ if (!nonce) {
217
+ this.opts.onConnectError?.(
218
+ new Error("gateway connect challenge missing nonce")
219
+ );
220
+ this.ws?.close(1008, "connect challenge missing nonce");
221
+ return;
222
+ }
223
+ this.connectSent = true;
224
+ if (this.connectTimer) {
225
+ clearTimeout(this.connectTimer);
226
+ this.connectTimer = null;
227
+ }
228
+
229
+ const signedAtMs = Date.now();
230
+
231
+ // Build device authentication payload and signature
232
+ const device = (() => {
233
+ const payload = buildDeviceAuthPayloadV3({
234
+ deviceId: this.deviceIdentity.deviceId,
235
+ clientId: this.opts.clientId,
236
+ clientMode: this.opts.mode,
237
+ role: this.opts.role,
238
+ scopes: this.opts.scopes,
239
+ signedAtMs,
240
+ token: this.opts.token,
241
+ nonce,
242
+ platform: this.opts.platform,
243
+ deviceFamily: this.opts.deviceFamily,
244
+ });
245
+ const signature = signDevicePayload(
246
+ this.deviceIdentity.privateKeyPem,
247
+ payload
248
+ );
249
+ return {
250
+ id: this.deviceIdentity.deviceId,
251
+ publicKey: publicKeyRawBase64UrlFromPem(
252
+ this.deviceIdentity.publicKeyPem
253
+ ),
254
+ signature,
255
+ signedAt: signedAtMs,
256
+ nonce,
257
+ };
258
+ })();
259
+
260
+ const params: ConnectParams = {
261
+ minProtocol: 1,
262
+ maxProtocol: 3,
263
+ client: {
264
+ id: this.opts.clientId!,
265
+ displayName: this.opts.clientDisplayName,
266
+ version: this.opts.clientVersion,
267
+ platform: this.opts.platform,
268
+ mode: this.opts.mode,
269
+ deviceFamily: this.opts.deviceFamily,
270
+ instanceId: this.opts.instanceId,
271
+ },
272
+ role: this.opts.role,
273
+ scopes: this.opts.scopes,
274
+ auth:
275
+ this.opts.token || this.opts.password
276
+ ? {
277
+ token: this.opts.token,
278
+ password: this.opts.password,
279
+ }
280
+ : undefined,
281
+ locale: this.opts.locale,
282
+ device,
283
+ };
284
+
285
+ void this.request<HelloOk>("connect", params)
286
+ .then((helloOk) => {
287
+ this.opts.onHelloOk?.(helloOk);
288
+ })
289
+ .catch((err) => {
290
+ this.opts.onConnectError?.(
291
+ err instanceof Error ? err : new Error(String(err))
292
+ );
293
+ this.opts.logError(`gateway connect failed: ${String(err)}`);
294
+ this.ws?.close(1008, "connect failed");
295
+ });
296
+ }
297
+
298
+ /**
299
+ * 处理接收到的消息
300
+ */
301
+ private handleMessage(raw: string) {
302
+ try {
303
+ const parsed = JSON.parse(raw);
304
+
305
+ // 处理事件帧(兼容 "evt" 和 "event" 两种格式)
306
+ if (parsed.type === "evt" || parsed.type === "event") {
307
+ const evt = parsed as EventFrame;
308
+ if (evt.event === "connect.challenge") {
309
+ const payload = evt.payload as { nonce?: unknown } | undefined;
310
+ const nonce =
311
+ payload && typeof payload.nonce === "string" ? payload.nonce : null;
312
+ if (!nonce || nonce.trim().length === 0) {
313
+ this.opts.onConnectError?.(
314
+ new Error("gateway connect challenge missing nonce")
315
+ );
316
+ this.ws?.close(1008, "connect challenge missing nonce");
317
+ return;
318
+ }
319
+ this.connectNonce = nonce.trim();
320
+ this.sendConnect();
321
+ return;
322
+ }
323
+ this.opts.onEvent?.(evt);
324
+ return;
325
+ }
326
+
327
+ // 处理响应帧
328
+ if (parsed.type === "res") {
329
+ const res = parsed as ResponseFrame;
330
+ const pending = this.pending.get(res.id);
331
+ if (!pending) {
332
+ return;
333
+ }
334
+ this.pending.delete(res.id);
335
+ if (res.ok) {
336
+ pending.resolve(res.payload);
337
+ } else {
338
+ pending.reject(new Error(res.error?.message ?? "unknown error"));
339
+ }
340
+ }
341
+ } catch (err) {
342
+ this.opts.logDebug(`gateway client parse error: ${String(err)}`);
343
+ }
344
+ }
345
+
346
+ /**
347
+ * 队列连接请求
348
+ */
349
+ private queueConnect() {
350
+ this.connectNonce = null;
351
+ this.connectSent = false;
352
+ if (this.connectTimer) {
353
+ clearTimeout(this.connectTimer);
354
+ }
355
+ const timeout = this.opts.connectTimeout ?? 2000;
356
+ this.connectTimer = setTimeout(() => {
357
+ if (this.connectSent || this.ws?.readyState !== WebSocket.OPEN) {
358
+ return;
359
+ }
360
+ this.opts.onConnectError?.(
361
+ new Error("gateway connect challenge timeout")
362
+ );
363
+ this.ws?.close(1008, "connect challenge timeout");
364
+ }, timeout);
365
+ }
366
+
367
+ /**
368
+ * 清理所有待处理的请求
369
+ */
370
+ private flushPendingErrors(err: Error) {
371
+ for (const [, p] of this.pending) {
372
+ p.reject(err);
373
+ }
374
+ this.pending.clear();
375
+ }
376
+ }
@@ -0,0 +1,76 @@
1
+ import { rawDataToString } from "openclaw/plugin-sdk";
2
+ import { WebSocket } from "ws";
3
+ import { GatewayClientOptions } from "./types.js";
4
+ import { useClawLogger } from "../utils/logger.js";
5
+
6
+ const GATEWAY_SERVER_URL = "ws://127.0.0.1:18789";
7
+
8
+ /**
9
+ * 纯透传 Gateway Client
10
+ * - 建立 WebSocket 连接
11
+ * - 所有消息直接透传,不做任何协议处理
12
+ */
13
+ export class GatewayClient {
14
+ private ws: WebSocket | null = null;
15
+ private opts: GatewayClientOptions;
16
+ private closed = false;
17
+
18
+ constructor(opts: GatewayClientOptions) {
19
+ this.opts = opts;
20
+ }
21
+
22
+ /**
23
+ * 启动连接
24
+ */
25
+ start() {
26
+ if (this.closed) {
27
+ return;
28
+ }
29
+ const url = GATEWAY_SERVER_URL;
30
+ this.ws = new WebSocket(url, { maxPayload: 25 * 1024 * 1024 });
31
+
32
+ this.ws.on("open", () => {
33
+ this.opts.onOpen?.();
34
+ });
35
+
36
+ this.ws.on("message", (data) => {
37
+ const dataText = rawDataToString(data);
38
+ useClawLogger().debug?.(`[yoyoclaw-gateway] received message:, ${dataText.slice(0, 500)}`);
39
+ this.opts.onMessage?.(dataText);
40
+ });
41
+
42
+ this.ws.on("close", (code, reason) => {
43
+ const reasonText = rawDataToString(reason);
44
+ this.ws = null;
45
+ useClawLogger().info(`[yoyoclaw-gateway] closed (${code}): ${reasonText}`);
46
+ this.opts.onClose?.();
47
+ });
48
+
49
+ this.ws.on("error", (err) => {
50
+ useClawLogger().error(`[yoyoclaw-gateway] error: ${String(err)}`);
51
+ this.opts.onClose?.();
52
+ });
53
+ }
54
+
55
+ /**
56
+ * 停止连接
57
+ */
58
+ stop() {
59
+ this.closed = true;
60
+ this.ws?.close();
61
+ this.ws = null;
62
+ }
63
+
64
+ /**
65
+ * 发送消息(透传)
66
+ */
67
+ send(data: Buffer | string): void {
68
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
69
+ useClawLogger().error(
70
+ "[yoyoclaw-gateway] send failed: gateway not connected"
71
+ );
72
+ return;
73
+ }
74
+ this.ws.send(data);
75
+ }
76
+ }
@@ -0,0 +1,57 @@
1
+ import { normalizeDeviceMetadataForAuth } from "./helpers.js";
2
+ import type {
3
+ DeviceAuthPayloadParams,
4
+ DeviceAuthPayloadV3Params,
5
+ } from "./types.js";
6
+
7
+ export { normalizeDeviceMetadataForAuth };
8
+
9
+ /**
10
+ * Build device authentication payload (v2)
11
+ * @param params - Authentication parameters
12
+ * @returns Pipe-delimited payload string
13
+ */
14
+ export function buildDeviceAuthPayload(
15
+ params: DeviceAuthPayloadParams
16
+ ): string {
17
+ const scopes = params.scopes.join(",");
18
+ const token = params.token ?? "";
19
+ return [
20
+ "v2",
21
+ params.deviceId,
22
+ params.clientId,
23
+ params.clientMode,
24
+ params.role,
25
+ scopes,
26
+ String(params.signedAtMs),
27
+ token,
28
+ params.nonce,
29
+ ].join("|");
30
+ }
31
+
32
+ /**
33
+ * Build device authentication payload (v3) with platform and device family
34
+ * @param params - Authentication parameters including platform and device family
35
+ * @returns Pipe-delimited payload string
36
+ */
37
+ export function buildDeviceAuthPayloadV3(
38
+ params: DeviceAuthPayloadV3Params
39
+ ): string {
40
+ const scopes = params.scopes.join(",");
41
+ const token = params.token ?? "";
42
+ const platform = normalizeDeviceMetadataForAuth(params.platform);
43
+ const deviceFamily = normalizeDeviceMetadataForAuth(params.deviceFamily);
44
+ return [
45
+ "v3",
46
+ params.deviceId,
47
+ params.clientId,
48
+ params.clientMode,
49
+ params.role,
50
+ scopes,
51
+ String(params.signedAtMs),
52
+ token,
53
+ params.nonce,
54
+ platform,
55
+ deviceFamily,
56
+ ].join("|");
57
+ }