phoneclaw-connector 1.0.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.
@@ -0,0 +1,71 @@
1
+ import QRCode from 'qrcode';
2
+ export class PairingManager {
3
+ activePairingCodes = new Map();
4
+ CODE_EXPIRY_MS = 10 * 60 * 1000; // 10 minutes
5
+ generatePairingCode(deviceInfo, config) {
6
+ // Generate 6-digit pairing code
7
+ const pairingCode = Math.floor(100000 + Math.random() * 900000).toString();
8
+ const pairingInfo = {
9
+ deviceId: deviceInfo.deviceId,
10
+ pairingCode,
11
+ relayUrl: config.relayServer,
12
+ expiresAt: Date.now() + this.CODE_EXPIRY_MS
13
+ };
14
+ // Store with cleanup
15
+ this.activePairingCodes.set(pairingCode, pairingInfo);
16
+ setTimeout(() => {
17
+ this.activePairingCodes.delete(pairingCode);
18
+ }, this.CODE_EXPIRY_MS);
19
+ return pairingInfo;
20
+ }
21
+ async generateQRCode(pairingInfo) {
22
+ const qrData = JSON.stringify({
23
+ deviceId: pairingInfo.deviceId,
24
+ pairingCode: pairingInfo.pairingCode,
25
+ relayUrl: pairingInfo.relayUrl,
26
+ expiresAt: pairingInfo.expiresAt
27
+ });
28
+ return QRCode.toString(qrData, {
29
+ type: 'terminal',
30
+ width: 20,
31
+ margin: 1
32
+ });
33
+ }
34
+ async generateQRCodeDataURL(pairingInfo) {
35
+ const qrData = JSON.stringify({
36
+ deviceId: pairingInfo.deviceId,
37
+ pairingCode: pairingInfo.pairingCode,
38
+ relayUrl: pairingInfo.relayUrl,
39
+ expiresAt: pairingInfo.expiresAt
40
+ });
41
+ return QRCode.toDataURL(qrData, {
42
+ width: 300,
43
+ margin: 2
44
+ });
45
+ }
46
+ validatePairingCode(code) {
47
+ const pairingInfo = this.activePairingCodes.get(code);
48
+ if (!pairingInfo) {
49
+ return null;
50
+ }
51
+ if (Date.now() > pairingInfo.expiresAt) {
52
+ this.activePairingCodes.delete(code);
53
+ return null;
54
+ }
55
+ return pairingInfo;
56
+ }
57
+ clearPairingCode(code) {
58
+ this.activePairingCodes.delete(code);
59
+ }
60
+ getActivePairingCodes() {
61
+ // Clean up expired codes
62
+ const now = Date.now();
63
+ for (const [code, info] of this.activePairingCodes.entries()) {
64
+ if (now > info.expiresAt) {
65
+ this.activePairingCodes.delete(code);
66
+ }
67
+ }
68
+ return Array.from(this.activePairingCodes.keys());
69
+ }
70
+ }
71
+ //# sourceMappingURL=pairing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pairing.js","sourceRoot":"","sources":["../src/pairing.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ import { DeviceInfo, SkillConfig } from './types.js';
2
+ export declare class RelayClient {
3
+ private ws;
4
+ private config;
5
+ private deviceInfo;
6
+ private heartbeatTimer;
7
+ private reconnectTimer;
8
+ private reconnectAttempts;
9
+ private messageHandlers;
10
+ private isConnected;
11
+ constructor(config: SkillConfig, deviceInfo: DeviceInfo);
12
+ connect(): Promise<void>;
13
+ private handleMessage;
14
+ private handleForwardMessage;
15
+ private handlePairingRequest;
16
+ private startHeartbeat;
17
+ private stopHeartbeat;
18
+ private scheduleReconnect;
19
+ send(message: any): void;
20
+ on(type: string, handler: (data: any) => void): void;
21
+ off(type: string): void;
22
+ disconnect(): void;
23
+ get connected(): boolean;
24
+ }
25
+ //# sourceMappingURL=relay.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relay.d.ts","sourceRoot":"","sources":["../src/relay.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,WAAW,EAA4B,MAAM,YAAY,CAAC;AAE/E,qBAAa,WAAW;IACtB,OAAO,CAAC,EAAE,CAA0B;IACpC,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,cAAc,CAA+B;IACrD,OAAO,CAAC,cAAc,CAA+B;IACrD,OAAO,CAAC,iBAAiB,CAAK;IAC9B,OAAO,CAAC,eAAe,CAA0C;IACjE,OAAO,CAAC,WAAW,CAAS;gBAEhB,MAAM,EAAE,WAAW,EAAE,UAAU,EAAE,UAAU;IAKjD,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IA2E9B,OAAO,CAAC,aAAa;YA4BP,oBAAoB;IASlC,OAAO,CAAC,oBAAoB;IAO5B,OAAO,CAAC,cAAc;IAQtB,OAAO,CAAC,aAAa;IAOrB,OAAO,CAAC,iBAAiB;IAkCzB,IAAI,CAAC,OAAO,EAAE,GAAG,GAAG,IAAI;IAaxB,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,GAAG,IAAI;IAIpD,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAIvB,UAAU,IAAI,IAAI;IAkBlB,IAAI,SAAS,IAAI,OAAO,CAEvB;CACF"}
package/dist/relay.js ADDED
@@ -0,0 +1,206 @@
1
+ import WebSocket from 'ws';
2
+ export class RelayClient {
3
+ ws = null;
4
+ config;
5
+ deviceInfo;
6
+ heartbeatTimer = null;
7
+ reconnectTimer = null;
8
+ reconnectAttempts = 0;
9
+ messageHandlers = new Map();
10
+ isConnected = false;
11
+ constructor(config, deviceInfo) {
12
+ this.config = config;
13
+ this.deviceInfo = deviceInfo;
14
+ }
15
+ async connect() {
16
+ return new Promise((resolve, reject) => {
17
+ try {
18
+ // 主动关闭旧 ws(1000 normal closure),让 server 端 addConnection 看到是替换而不是新连接
19
+ if (this.ws) {
20
+ try {
21
+ this.ws.removeAllListeners();
22
+ this.ws.close(1000, 'Replaced by new connection');
23
+ }
24
+ catch {
25
+ // Ignore
26
+ }
27
+ this.ws = null;
28
+ }
29
+ this.ws = new WebSocket(this.config.relayServer, {
30
+ headers: {
31
+ 'X-Device-Id': this.deviceInfo.deviceId,
32
+ 'X-Device-Key': this.deviceInfo.deviceKey
33
+ }
34
+ });
35
+ this.ws.on('open', () => {
36
+ console.log('[RelayClient] Connected to relay server');
37
+ this.isConnected = true;
38
+ this.reconnectAttempts = 0;
39
+ this.startHeartbeat();
40
+ // Send connection handshake
41
+ this.send({
42
+ type: 'skill_connect',
43
+ deviceId: this.deviceInfo.deviceId,
44
+ deviceKey: this.deviceInfo.deviceKey,
45
+ platform: this.deviceInfo.platform,
46
+ version: this.deviceInfo.version
47
+ });
48
+ resolve();
49
+ });
50
+ this.ws.on('message', (data) => {
51
+ try {
52
+ const message = JSON.parse(data.toString());
53
+ this.handleMessage(message);
54
+ }
55
+ catch (error) {
56
+ console.error('[RelayClient] Failed to parse message:', error);
57
+ }
58
+ });
59
+ this.ws.on('close', (code, reason) => {
60
+ console.log(`[RelayClient] Connection closed: ${code} ${reason}`);
61
+ this.isConnected = false;
62
+ this.stopHeartbeat();
63
+ // 1000 = normal closure,server 端 addConnection 替换旧 ws 时会发这个
64
+ // 收到这个不应触发重连(避免和新 ws 抢着连形成死循环)
65
+ if (code === 1000) {
66
+ return;
67
+ }
68
+ this.scheduleReconnect();
69
+ });
70
+ this.ws.on('error', (error) => {
71
+ console.error('[RelayClient] Connection error:', error);
72
+ this.isConnected = false;
73
+ this.stopHeartbeat();
74
+ // 关键:error 触发后不一定会触发 close(ws 库实现),必须自己 scheduleReconnect
75
+ // scheduleReconnect 内部会 clearTimeout 旧 timer,避免重叠
76
+ this.scheduleReconnect();
77
+ });
78
+ }
79
+ catch (error) {
80
+ reject(error);
81
+ }
82
+ });
83
+ }
84
+ handleMessage(message) {
85
+ switch (message.type) {
86
+ case 'connected':
87
+ console.log('[RelayClient] Authenticated with relay server');
88
+ break;
89
+ case 'forward_message':
90
+ this.handleForwardMessage(message);
91
+ break;
92
+ case 'ping':
93
+ this.send({ type: 'pong' });
94
+ break;
95
+ case 'pairing_request':
96
+ this.handlePairingRequest(message);
97
+ break;
98
+ default:
99
+ // Call registered handlers
100
+ const handler = this.messageHandlers.get(message.type);
101
+ if (handler) {
102
+ handler(message);
103
+ }
104
+ break;
105
+ }
106
+ }
107
+ async handleForwardMessage(message) {
108
+ // This will be handled by the main skill class
109
+ // that has access to the Gateway client
110
+ const handler = this.messageHandlers.get('forward_message');
111
+ if (handler) {
112
+ handler(message);
113
+ }
114
+ }
115
+ handlePairingRequest(message) {
116
+ const handler = this.messageHandlers.get('pairing_request');
117
+ if (handler) {
118
+ handler(message);
119
+ }
120
+ }
121
+ startHeartbeat() {
122
+ this.heartbeatTimer = setInterval(() => {
123
+ if (this.isConnected && this.ws?.readyState === WebSocket.OPEN) {
124
+ this.send({ type: 'ping' });
125
+ }
126
+ }, this.config.heartbeatInterval);
127
+ }
128
+ stopHeartbeat() {
129
+ if (this.heartbeatTimer) {
130
+ clearInterval(this.heartbeatTimer);
131
+ this.heartbeatTimer = null;
132
+ }
133
+ }
134
+ scheduleReconnect() {
135
+ // 清掉上一次的 timer,避免多个 reconnect 任务叠加
136
+ if (this.reconnectTimer) {
137
+ clearTimeout(this.reconnectTimer);
138
+ this.reconnectTimer = null;
139
+ }
140
+ // reconnectAttempts: 0 表示无限重试(推荐生产环境使用)
141
+ // 大于 0 表示最多重试次数(老行为,调试用)
142
+ const maxAttempts = this.config.reconnectAttempts;
143
+ if (maxAttempts > 0 && this.reconnectAttempts >= maxAttempts) {
144
+ console.log(`[RelayClient] Max reconnect attempts (${maxAttempts}) reached, giving up`);
145
+ return;
146
+ }
147
+ // 指数退避,上限 60s(避免长时间断开后无限制等待)
148
+ const baseDelay = this.config.reconnectDelay;
149
+ const delay = baseDelay * Math.pow(2, Math.min(this.reconnectAttempts, 6));
150
+ console.log(`[RelayClient] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts + 1}${maxAttempts === 0 ? ', unlimited' : ''})`);
151
+ this.reconnectTimer = setTimeout(async () => {
152
+ this.reconnectTimer = null;
153
+ this.reconnectAttempts++;
154
+ try {
155
+ await this.connect();
156
+ }
157
+ catch (error) {
158
+ console.error('[RelayClient] Reconnect failed:', error);
159
+ // 关键:connect 失败时 on('error') handler 会 removeAllListeners + ws=null,
160
+ // 所以不会自动再触发 close 事件,必须显式再 schedule
161
+ this.scheduleReconnect();
162
+ }
163
+ }, delay);
164
+ }
165
+ send(message) {
166
+ if (this.ws?.readyState === WebSocket.OPEN) {
167
+ // 调试日志:把发给 relay 的关键消息打出来
168
+ const t = message?.type;
169
+ if (t === 'stream_chunk' || t === 'message_complete' || t === 'message_error' || t === 'forward_message') {
170
+ console.log(`[RelayClient] → ${t} sessionId=${message.sessionId ?? '-'} contentLen=${(message.content ?? '').length}`);
171
+ }
172
+ this.ws.send(JSON.stringify(message));
173
+ }
174
+ else {
175
+ console.warn(`[RelayClient] Cannot send ${message?.type}: connection not open (state=${this.ws?.readyState})`);
176
+ }
177
+ }
178
+ on(type, handler) {
179
+ this.messageHandlers.set(type, handler);
180
+ }
181
+ off(type) {
182
+ this.messageHandlers.delete(type);
183
+ }
184
+ disconnect() {
185
+ this.stopHeartbeat();
186
+ if (this.reconnectTimer) {
187
+ clearTimeout(this.reconnectTimer);
188
+ this.reconnectTimer = null;
189
+ }
190
+ if (this.ws) {
191
+ // 用 1000 normal closure 而不是默认 1005,让 server 端能区分主动断 vs 异常断
192
+ try {
193
+ this.ws.close(1000, 'Skill shutting down');
194
+ }
195
+ catch {
196
+ // Ignore
197
+ }
198
+ this.ws = null;
199
+ }
200
+ this.isConnected = false;
201
+ }
202
+ get connected() {
203
+ return this.isConnected;
204
+ }
205
+ }
206
+ //# sourceMappingURL=relay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relay.js","sourceRoot":"","sources":["../src/relay.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=status.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":""}
package/dist/status.js ADDED
@@ -0,0 +1,34 @@
1
+ import { readFileSync, existsSync } from 'fs';
2
+ import { join, dirname } from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ const __dirname = dirname(fileURLToPath(import.meta.url));
5
+ async function main() {
6
+ // Load device info
7
+ const devicePath = join(__dirname, '..', 'device.json');
8
+ if (!existsSync(devicePath)) {
9
+ console.log('Device not initialized. Run the skill first to generate device info.');
10
+ return;
11
+ }
12
+ const device = JSON.parse(readFileSync(devicePath, 'utf-8'));
13
+ // Check status file (written by main process)
14
+ const statusPath = join(__dirname, '..', 'status.json');
15
+ let status = {
16
+ deviceId: device.deviceId,
17
+ gatewayConnected: false,
18
+ relayConnected: false,
19
+ activeSessions: 0,
20
+ uptime: 0
21
+ };
22
+ if (existsSync(statusPath)) {
23
+ status = JSON.parse(readFileSync(statusPath, 'utf-8'));
24
+ }
25
+ console.log('\n=== PhoneClaw Skill Status ===');
26
+ console.log(`Device ID: ${status.deviceId}`);
27
+ console.log(`Gateway: ${status.gatewayConnected ? 'Connected' : 'Disconnected'}`);
28
+ console.log(`Relay: ${status.relayConnected ? 'Connected' : 'Disconnected'}`);
29
+ console.log(`Active Sessions: ${status.activeSessions}`);
30
+ console.log(`Uptime: ${Math.floor(status.uptime / 1000)}s`);
31
+ console.log('===============================\n');
32
+ }
33
+ main().catch(console.error);
34
+ //# sourceMappingURL=status.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.js","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AAC9C,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AAEpC,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAiB1D,KAAK,UAAU,IAAI;IACjB,mBAAmB;IACnB,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,aAAa,CAAC,CAAC;IACxD,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC5B,OAAO,CAAC,GAAG,CAAC,sEAAsE,CAAC,CAAC;QACpF,OAAO;IACT,CAAC;IACD,MAAM,MAAM,GAAe,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC;IAEzE,8CAA8C;IAC9C,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,aAAa,CAAC,CAAC;IACxD,IAAI,MAAM,GAAqB;QAC7B,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,gBAAgB,EAAE,KAAK;QACvB,cAAc,EAAE,KAAK;QACrB,cAAc,EAAE,CAAC;QACjB,MAAM,EAAE,CAAC;KACV,CAAC;IAEF,IAAI,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC3B,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC;IACzD,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,kCAAkC,CAAC,CAAC;IAChD,OAAO,CAAC,GAAG,CAAC,cAAc,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC7C,OAAO,CAAC,GAAG,CAAC,YAAY,MAAM,CAAC,gBAAgB,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,cAAc,EAAE,CAAC,CAAC;IAClF,OAAO,CAAC,GAAG,CAAC,UAAU,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,cAAc,EAAE,CAAC,CAAC;IAC9E,OAAO,CAAC,GAAG,CAAC,oBAAoB,MAAM,CAAC,cAAc,EAAE,CAAC,CAAC;IACzD,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC;IAC5D,OAAO,CAAC,GAAG,CAAC,mCAAmC,CAAC,CAAC;AACnD,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC"}
@@ -0,0 +1,33 @@
1
+ export interface SkillConfig {
2
+ relayServer: string;
3
+ autoConnect: boolean;
4
+ heartbeatInterval: number;
5
+ reconnectAttempts: number;
6
+ reconnectDelay: number;
7
+ gatewayUrl: string;
8
+ gatewayToken: string;
9
+ }
10
+ export interface DeviceInfo {
11
+ deviceId: string;
12
+ deviceKey: string;
13
+ platform: string;
14
+ version: string;
15
+ }
16
+ export interface PairingInfo {
17
+ deviceId: string;
18
+ pairingCode: string;
19
+ relayUrl: string;
20
+ expiresAt: number;
21
+ }
22
+ export interface StreamChunk {
23
+ sessionId: string;
24
+ content: string;
25
+ isDone: boolean;
26
+ }
27
+ export interface HealthStatus {
28
+ ok: boolean;
29
+ gatewayConnected: boolean;
30
+ relayConnected: boolean;
31
+ uptime: number;
32
+ }
33
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,WAAW;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,OAAO,CAAC;IACrB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,OAAO,CAAC;IACZ,gBAAgB,EAAE,OAAO,CAAC;IAC1B,cAAc,EAAE,OAAO,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;CAChB"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "phoneclaw-connector",
3
+ "version": "1.0.0",
4
+ "description": "Connect PhoneClaw app to your OpenClaw gateway via relay server",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "type": "module",
8
+ "files": [
9
+ "dist",
10
+ "SKILL.md"
11
+ ],
12
+ "scripts": {
13
+ "build": "tsc",
14
+ "build:dev": "tsc --watch",
15
+ "start": "node dist/index.js",
16
+ "dev": "ts-node src/index.ts",
17
+ "pair": "node dist/pair.js",
18
+ "status": "node dist/status.js"
19
+ },
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "https://github.com/wangzz/phone-claw-skill.git"
23
+ },
24
+ "author": "wangzz",
25
+ "license": "AGPL-3.0",
26
+ "keywords": [
27
+ "phoneclaw",
28
+ "openclaw",
29
+ "skill",
30
+ "connector",
31
+ "relay",
32
+ "remote-control",
33
+ "ios",
34
+ "gateway"
35
+ ],
36
+ "dependencies": {
37
+ "ws": "^8.16.0",
38
+ "qrcode": "^1.5.3",
39
+ "uuid": "^9.0.0",
40
+ "crypto": "^1.0.1"
41
+ },
42
+ "devDependencies": {
43
+ "@types/node": "^20.10.0",
44
+ "@types/ws": "^8.5.10",
45
+ "@types/qrcode": "^1.5.5",
46
+ "@types/uuid": "^9.0.7",
47
+ "typescript": "^5.3.0",
48
+ "ts-node": "^10.9.0"
49
+ },
50
+ "engines": {
51
+ "node": ">=18.0.0"
52
+ }
53
+ }