cc-control-shared 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.
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/protocols/index.d.ts +131 -0
- package/dist/protocols/index.d.ts.map +1 -0
- package/dist/protocols/index.js +16 -0
- package/dist/types/index.d.ts +164 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +4 -0
- package/dist/types/user.d.ts +42 -0
- package/dist/types/user.d.ts.map +1 -0
- package/dist/types/user.js +4 -0
- package/dist/utils/index.d.ts +62 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +187 -0
- package/dist/utils.test.d.ts +5 -0
- package/dist/utils.test.d.ts.map +1 -0
- package/dist/utils.test.js +290 -0
- package/package.json +47 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAGhC,cAAc,sBAAsB,CAAC;AAGrC,cAAc,kBAAkB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// CC-Control Shared Package
|
|
3
|
+
// ============================================================================
|
|
4
|
+
// Export all types
|
|
5
|
+
export * from "./types/index.js";
|
|
6
|
+
export * from "./types/user.js";
|
|
7
|
+
// Export all protocol definitions
|
|
8
|
+
export * from "./protocols/index.js";
|
|
9
|
+
// Export all utilities
|
|
10
|
+
export * from "./utils/index.js";
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import type { Session, LogEntry } from "../types/index.js";
|
|
2
|
+
export interface MessageEnvelope<T extends MessageType = MessageType> {
|
|
3
|
+
version: "1.0";
|
|
4
|
+
type: T;
|
|
5
|
+
id: string;
|
|
6
|
+
timestamp: string;
|
|
7
|
+
deviceId: string;
|
|
8
|
+
payload: MessagePayloadTypeMap[T];
|
|
9
|
+
}
|
|
10
|
+
export type MessageType = "auth:register" | "auth:pair" | "auth:success" | "auth:error" | "state:session_update" | "state:session_list" | "state:task_progress" | "state:task_completion" | "logs:stream" | "command:send_prompt" | "command:switch_session" | "command:manage_task" | "command:file_operation" | "response:command_result" | "response:error" | "connection:status";
|
|
11
|
+
export interface MessagePayloadTypeMap {
|
|
12
|
+
"auth:register": AuthRegisterPayload;
|
|
13
|
+
"auth:pair": AuthPairPayload;
|
|
14
|
+
"auth:success": AuthSuccessPayload;
|
|
15
|
+
"auth:error": AuthErrorPayload;
|
|
16
|
+
"state:session_update": StateSessionUpdatePayload;
|
|
17
|
+
"state:session_list": StateSessionListPayload;
|
|
18
|
+
"state:task_progress": StateTaskProgressPayload;
|
|
19
|
+
"state:task_completion": StateTaskCompletionPayload;
|
|
20
|
+
"logs:stream": LogsStreamPayload;
|
|
21
|
+
"command:send_prompt": CommandSendPromptPayload;
|
|
22
|
+
"command:switch_session": CommandSwitchSessionPayload;
|
|
23
|
+
"command:manage_task": CommandManageTaskPayload;
|
|
24
|
+
"command:file_operation": CommandFileOperationPayload;
|
|
25
|
+
"response:command_result": ResponseCommandResultPayload;
|
|
26
|
+
"response:error": ResponseErrorPayload;
|
|
27
|
+
"connection:status": ConnectionStatusPayload;
|
|
28
|
+
}
|
|
29
|
+
export interface AuthRegisterPayload {
|
|
30
|
+
deviceName: string;
|
|
31
|
+
deviceType: "desktop" | "mobile";
|
|
32
|
+
capabilities: {
|
|
33
|
+
version: string;
|
|
34
|
+
features: string[];
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
export interface AuthPairPayload {
|
|
38
|
+
pairingCode: string;
|
|
39
|
+
deviceToken?: string;
|
|
40
|
+
}
|
|
41
|
+
export interface AuthSuccessPayload {
|
|
42
|
+
deviceId: string;
|
|
43
|
+
token: string;
|
|
44
|
+
expiresAt: number;
|
|
45
|
+
}
|
|
46
|
+
export interface AuthErrorPayload {
|
|
47
|
+
error: string;
|
|
48
|
+
code: string;
|
|
49
|
+
}
|
|
50
|
+
export interface StateSessionUpdatePayload {
|
|
51
|
+
session: Session;
|
|
52
|
+
}
|
|
53
|
+
export interface StateSessionListPayload {
|
|
54
|
+
sessions: Session[];
|
|
55
|
+
lastUpdate: string;
|
|
56
|
+
}
|
|
57
|
+
export interface StateTaskProgressPayload {
|
|
58
|
+
sessionId: string;
|
|
59
|
+
taskId: string;
|
|
60
|
+
status: "in_progress" | "blocked" | "completed" | "failed";
|
|
61
|
+
progress: number;
|
|
62
|
+
currentStep?: string;
|
|
63
|
+
startedAt: string;
|
|
64
|
+
eta?: string;
|
|
65
|
+
}
|
|
66
|
+
export interface StateTaskCompletionPayload {
|
|
67
|
+
sessionId: string;
|
|
68
|
+
taskId: string;
|
|
69
|
+
status: "completed" | "failed" | "cancelled";
|
|
70
|
+
result?: {
|
|
71
|
+
summary: string;
|
|
72
|
+
filesModified: number;
|
|
73
|
+
commandsRun: number;
|
|
74
|
+
duration: number;
|
|
75
|
+
};
|
|
76
|
+
error?: {
|
|
77
|
+
code: string;
|
|
78
|
+
message: string;
|
|
79
|
+
stack?: string;
|
|
80
|
+
};
|
|
81
|
+
completedAt: string;
|
|
82
|
+
}
|
|
83
|
+
export interface LogsStreamPayload {
|
|
84
|
+
sessionId: string;
|
|
85
|
+
logs: LogEntry[];
|
|
86
|
+
isChunk?: boolean;
|
|
87
|
+
chunkIndex?: number;
|
|
88
|
+
totalChunks?: number;
|
|
89
|
+
}
|
|
90
|
+
export interface CommandSendPromptPayload {
|
|
91
|
+
sessionId: string;
|
|
92
|
+
commandId: string;
|
|
93
|
+
prompt: string;
|
|
94
|
+
context?: {
|
|
95
|
+
workingDirectory?: string;
|
|
96
|
+
files?: string[];
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
export interface CommandSwitchSessionPayload {
|
|
100
|
+
targetSessionId: string;
|
|
101
|
+
}
|
|
102
|
+
export interface CommandManageTaskPayload {
|
|
103
|
+
taskId: string;
|
|
104
|
+
action: "cancel" | "pause" | "resume";
|
|
105
|
+
}
|
|
106
|
+
export interface CommandFileOperationPayload {
|
|
107
|
+
operation: "read" | "write" | "delete" | "list";
|
|
108
|
+
path: string;
|
|
109
|
+
content?: string;
|
|
110
|
+
}
|
|
111
|
+
export interface ResponseCommandResultPayload {
|
|
112
|
+
commandId: string;
|
|
113
|
+
status: "success" | "error" | "pending";
|
|
114
|
+
result?: unknown;
|
|
115
|
+
timestamp: string;
|
|
116
|
+
}
|
|
117
|
+
export interface ResponseErrorPayload {
|
|
118
|
+
commandId?: string;
|
|
119
|
+
error: {
|
|
120
|
+
code: string;
|
|
121
|
+
message: string;
|
|
122
|
+
details?: Record<string, unknown>;
|
|
123
|
+
};
|
|
124
|
+
timestamp: string;
|
|
125
|
+
}
|
|
126
|
+
export interface ConnectionStatusPayload {
|
|
127
|
+
status: "connected" | "disconnected" | "reconnecting";
|
|
128
|
+
reason?: string;
|
|
129
|
+
}
|
|
130
|
+
export declare function createMessage<T extends MessageType>(type: T, payload: MessagePayloadTypeMap[T], deviceId: string): MessageEnvelope<T>;
|
|
131
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/protocols/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// WebSocket Message Protocol Definitions
|
|
3
|
+
// ============================================================================
|
|
4
|
+
// ----------------------------------------------------------------------------
|
|
5
|
+
// Message Builder Helpers
|
|
6
|
+
// ----------------------------------------------------------------------------
|
|
7
|
+
export function createMessage(type, payload, deviceId) {
|
|
8
|
+
return {
|
|
9
|
+
version: "1.0",
|
|
10
|
+
type,
|
|
11
|
+
id: crypto.randomUUID(),
|
|
12
|
+
timestamp: new Date().toISOString(),
|
|
13
|
+
deviceId,
|
|
14
|
+
payload: payload,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
export type SessionStatus = "initializing" | "idle" | "processing" | "waiting_input" | "error" | "terminated";
|
|
2
|
+
export interface Session {
|
|
3
|
+
id: string;
|
|
4
|
+
deviceId: string;
|
|
5
|
+
status: SessionStatus;
|
|
6
|
+
currentTask?: Task;
|
|
7
|
+
messages: Message[];
|
|
8
|
+
filesAccessed: string[];
|
|
9
|
+
lastActivity: Date;
|
|
10
|
+
createdAt: Date;
|
|
11
|
+
metadata: SessionMetadata;
|
|
12
|
+
}
|
|
13
|
+
export interface SessionMetadata {
|
|
14
|
+
workingDirectory: string;
|
|
15
|
+
claudeVersion: string;
|
|
16
|
+
project?: string;
|
|
17
|
+
customTitle?: string;
|
|
18
|
+
summary?: string;
|
|
19
|
+
firstPrompt?: string;
|
|
20
|
+
messageCount?: number;
|
|
21
|
+
}
|
|
22
|
+
export type TaskStatus = "pending" | "in_progress" | "blocked" | "completed" | "failed" | "cancelled";
|
|
23
|
+
export interface Task {
|
|
24
|
+
id: string;
|
|
25
|
+
sessionId: string;
|
|
26
|
+
type: TaskType;
|
|
27
|
+
status: TaskStatus;
|
|
28
|
+
progress: number;
|
|
29
|
+
subject: string;
|
|
30
|
+
description?: string;
|
|
31
|
+
startedAt: Date;
|
|
32
|
+
completedAt?: Date;
|
|
33
|
+
error?: TaskError;
|
|
34
|
+
metadata?: Record<string, unknown>;
|
|
35
|
+
}
|
|
36
|
+
export type TaskType = "prompt" | "tool_execution" | "file_operation";
|
|
37
|
+
export interface TaskError {
|
|
38
|
+
code: string;
|
|
39
|
+
message: string;
|
|
40
|
+
recoverable: boolean;
|
|
41
|
+
}
|
|
42
|
+
export interface Message {
|
|
43
|
+
id: string;
|
|
44
|
+
sessionId: string;
|
|
45
|
+
role: "user" | "assistant" | "system";
|
|
46
|
+
content: string;
|
|
47
|
+
timestamp: Date;
|
|
48
|
+
toolCalls?: ToolCall[];
|
|
49
|
+
metadata?: {
|
|
50
|
+
model?: string;
|
|
51
|
+
gitBranch?: string;
|
|
52
|
+
cwd?: string;
|
|
53
|
+
usage?: {
|
|
54
|
+
input_tokens: number;
|
|
55
|
+
output_tokens: number;
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
export interface ToolCall {
|
|
60
|
+
id: string;
|
|
61
|
+
name: string;
|
|
62
|
+
arguments: Record<string, unknown>;
|
|
63
|
+
result?: unknown;
|
|
64
|
+
status: "pending" | "approved" | "rejected" | "completed";
|
|
65
|
+
}
|
|
66
|
+
export type DeviceType = "desktop" | "mobile";
|
|
67
|
+
export type DeviceStatus = "online" | "offline";
|
|
68
|
+
export interface Device {
|
|
69
|
+
id: string;
|
|
70
|
+
name: string;
|
|
71
|
+
type: DeviceType;
|
|
72
|
+
status: DeviceStatus;
|
|
73
|
+
lastSeen: Date;
|
|
74
|
+
pairedDevices: string[];
|
|
75
|
+
capabilities: DeviceCapabilities;
|
|
76
|
+
}
|
|
77
|
+
export interface DeviceCapabilities {
|
|
78
|
+
version: string;
|
|
79
|
+
features: string[];
|
|
80
|
+
}
|
|
81
|
+
export type LogLevel = "debug" | "info" | "warn" | "error";
|
|
82
|
+
export type LogSource = "claude_code" | "system" | "agent";
|
|
83
|
+
export interface LogEntry {
|
|
84
|
+
id: string;
|
|
85
|
+
sessionId: string;
|
|
86
|
+
source: LogSource;
|
|
87
|
+
level: LogLevel;
|
|
88
|
+
timestamp: Date;
|
|
89
|
+
message: string;
|
|
90
|
+
metadata?: Record<string, unknown>;
|
|
91
|
+
}
|
|
92
|
+
export type ConnectionStatus = "connected" | "disconnected" | "connecting" | "reconnecting";
|
|
93
|
+
export interface ConnectionState {
|
|
94
|
+
status: ConnectionStatus;
|
|
95
|
+
lastConnected?: Date;
|
|
96
|
+
lastDisconnected?: Date;
|
|
97
|
+
reconnectAttempts?: number;
|
|
98
|
+
}
|
|
99
|
+
export type CommandType = "send_prompt" | "switch_session" | "manage_task" | "file_operation";
|
|
100
|
+
export interface Command {
|
|
101
|
+
id: string;
|
|
102
|
+
type: CommandType;
|
|
103
|
+
sessionId: string;
|
|
104
|
+
payload: CommandPayload;
|
|
105
|
+
timestamp: Date;
|
|
106
|
+
}
|
|
107
|
+
export type CommandPayload = SendPromptPayload | SwitchSessionPayload | ManageTaskPayload | FileOperationPayload;
|
|
108
|
+
export interface SendPromptPayload {
|
|
109
|
+
prompt: string;
|
|
110
|
+
context?: {
|
|
111
|
+
workingDirectory?: string;
|
|
112
|
+
files?: string[];
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
export interface SwitchSessionPayload {
|
|
116
|
+
targetSessionId: string;
|
|
117
|
+
}
|
|
118
|
+
export interface ManageTaskPayload {
|
|
119
|
+
taskId: string;
|
|
120
|
+
action: "cancel" | "pause" | "resume";
|
|
121
|
+
}
|
|
122
|
+
export type FileOperationType = "read" | "write" | "delete" | "list";
|
|
123
|
+
export interface FileOperationPayload {
|
|
124
|
+
operation: FileOperationType;
|
|
125
|
+
path: string;
|
|
126
|
+
content?: string;
|
|
127
|
+
}
|
|
128
|
+
export type ResponseStatus = "success" | "error" | "pending";
|
|
129
|
+
export interface CommandResponse {
|
|
130
|
+
commandId: string;
|
|
131
|
+
status: ResponseStatus;
|
|
132
|
+
result?: unknown;
|
|
133
|
+
error?: ResponseError;
|
|
134
|
+
timestamp: Date;
|
|
135
|
+
}
|
|
136
|
+
export interface ResponseError {
|
|
137
|
+
code: string;
|
|
138
|
+
message: string;
|
|
139
|
+
details?: Record<string, unknown>;
|
|
140
|
+
}
|
|
141
|
+
export interface AuthTokens {
|
|
142
|
+
accessToken: string;
|
|
143
|
+
refreshToken: string;
|
|
144
|
+
expiresAt: Date;
|
|
145
|
+
}
|
|
146
|
+
export interface PairingInfo {
|
|
147
|
+
pairingCode: string;
|
|
148
|
+
desktopId: string;
|
|
149
|
+
desktopName: string;
|
|
150
|
+
qrData: string;
|
|
151
|
+
expiresAt: Date;
|
|
152
|
+
}
|
|
153
|
+
export type Permission = "read:sessions" | "read:logs" | "send:prompts" | "manage:tasks" | "read:files" | "write:files";
|
|
154
|
+
export interface AccessTokenPayload {
|
|
155
|
+
sub: string;
|
|
156
|
+
type: "access";
|
|
157
|
+
device_type: DeviceType;
|
|
158
|
+
paired_device: string;
|
|
159
|
+
permissions: Permission[];
|
|
160
|
+
iat: number;
|
|
161
|
+
exp: number;
|
|
162
|
+
jti: string;
|
|
163
|
+
}
|
|
164
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export interface User {
|
|
2
|
+
id: string;
|
|
3
|
+
email: string;
|
|
4
|
+
emailVerified: boolean;
|
|
5
|
+
subscriptionTier: "free" | "pro" | "enterprise";
|
|
6
|
+
createdAt: Date;
|
|
7
|
+
updatedAt: Date;
|
|
8
|
+
lastLoginAt?: Date;
|
|
9
|
+
}
|
|
10
|
+
export interface CreateUserInput {
|
|
11
|
+
email: string;
|
|
12
|
+
password: string;
|
|
13
|
+
}
|
|
14
|
+
export interface LoginInput {
|
|
15
|
+
email: string;
|
|
16
|
+
password: string;
|
|
17
|
+
}
|
|
18
|
+
export interface UserSession {
|
|
19
|
+
user: User;
|
|
20
|
+
accessToken: string;
|
|
21
|
+
refreshToken: string;
|
|
22
|
+
expiresAt: Date;
|
|
23
|
+
}
|
|
24
|
+
export interface UserTokenPayload {
|
|
25
|
+
sub: string;
|
|
26
|
+
type: "access" | "refresh";
|
|
27
|
+
email: string;
|
|
28
|
+
tier: "free" | "pro" | "enterprise";
|
|
29
|
+
iat: number;
|
|
30
|
+
exp: number;
|
|
31
|
+
jti: string;
|
|
32
|
+
}
|
|
33
|
+
export interface DeviceTokenPayload {
|
|
34
|
+
sub: string;
|
|
35
|
+
type: "device";
|
|
36
|
+
userId: string;
|
|
37
|
+
deviceType: "desktop" | "mobile";
|
|
38
|
+
iat: number;
|
|
39
|
+
exp: number;
|
|
40
|
+
jti: string;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=user.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"user.d.ts","sourceRoot":"","sources":["../../src/types/user.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,IAAI;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,OAAO,CAAC;IACvB,gBAAgB,EAAE,MAAM,GAAG,KAAK,GAAG,YAAY,CAAC;IAChD,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;IAChB,WAAW,CAAC,EAAE,IAAI,CAAC;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,IAAI,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,GAAG,KAAK,GAAG,YAAY,CAAC;IACpC,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,QAAQ,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,SAAS,GAAG,QAAQ,CAAC;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Format a date for display
|
|
3
|
+
*/
|
|
4
|
+
export declare function formatDate(date: Date): string;
|
|
5
|
+
/**
|
|
6
|
+
* Format a duration in milliseconds to human-readable
|
|
7
|
+
*/
|
|
8
|
+
export declare function formatDuration(ms: number): string;
|
|
9
|
+
/**
|
|
10
|
+
* Format bytes to human-readable
|
|
11
|
+
*/
|
|
12
|
+
export declare function formatBytes(bytes: number): string;
|
|
13
|
+
/**
|
|
14
|
+
* Generate a pairing code (XXX-XXX-XXX format)
|
|
15
|
+
*/
|
|
16
|
+
export declare function generatePairingCode(): string;
|
|
17
|
+
/**
|
|
18
|
+
* Validate a pairing code format
|
|
19
|
+
*/
|
|
20
|
+
export declare function isValidPairingCode(code: string): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Deep clone an object
|
|
23
|
+
*/
|
|
24
|
+
export declare function deepClone<T>(obj: T): T;
|
|
25
|
+
/**
|
|
26
|
+
* Debounce a function
|
|
27
|
+
*/
|
|
28
|
+
export declare function debounce<T extends (...args: unknown[]) => unknown>(func: T, wait: number): (...args: Parameters<T>) => void;
|
|
29
|
+
/**
|
|
30
|
+
* Throttle a function
|
|
31
|
+
*/
|
|
32
|
+
export declare function throttle<T extends (...args: unknown[]) => unknown>(func: T, limit: number): (...args: Parameters<T>) => void;
|
|
33
|
+
/**
|
|
34
|
+
* Sleep for a specified duration
|
|
35
|
+
*/
|
|
36
|
+
export declare function sleep(ms: number): Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* Retry a function with exponential backoff
|
|
39
|
+
*/
|
|
40
|
+
export declare function retry<T>(fn: () => Promise<T>, options?: {
|
|
41
|
+
maxAttempts?: number;
|
|
42
|
+
initialDelay?: number;
|
|
43
|
+
maxDelay?: number;
|
|
44
|
+
backoffMultiplier?: number;
|
|
45
|
+
}): Promise<T>;
|
|
46
|
+
/**
|
|
47
|
+
* Parse a user's home directory path
|
|
48
|
+
*/
|
|
49
|
+
export declare function resolveHomePath(path: string): string;
|
|
50
|
+
/**
|
|
51
|
+
* Check if a path is within allowed directories
|
|
52
|
+
*/
|
|
53
|
+
export declare function isPathAllowed(path: string, allowed: string[], denied: string[]): boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Extract device ID from JWT token
|
|
56
|
+
*/
|
|
57
|
+
export declare function extractDeviceId(token: string): string | null;
|
|
58
|
+
/**
|
|
59
|
+
* Check if a JWT token is expired
|
|
60
|
+
*/
|
|
61
|
+
export declare function isTokenExpired(token: string): boolean;
|
|
62
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAIA;;GAEG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAe7C;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAQjD;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAWjD;AAED;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,CAa5C;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAExD;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAEtC;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,EAChE,IAAI,EAAE,CAAC,EACP,IAAI,EAAE,MAAM,GACX,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,KAAK,IAAI,CAYlC;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,EAChE,IAAI,EAAE,CAAC,EACP,KAAK,EAAE,MAAM,GACZ,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,KAAK,IAAI,CAUlC;AAED;;GAEG;AACH,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE/C;AAED;;GAEG;AACH,wBAAsB,KAAK,CAAC,CAAC,EAC3B,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,OAAO,GAAE;IACP,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CACvB,GACL,OAAO,CAAC,CAAC,CAAC,CA2BZ;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKpD;AAED;;GAEG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EAAE,EACjB,MAAM,EAAE,MAAM,EAAE,GACf,OAAO,CAsBT;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAO5D;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAQrD"}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Utility Functions
|
|
3
|
+
// ============================================================================
|
|
4
|
+
/**
|
|
5
|
+
* Format a date for display
|
|
6
|
+
*/
|
|
7
|
+
export function formatDate(date) {
|
|
8
|
+
const now = new Date();
|
|
9
|
+
const diff = now.getTime() - date.getTime();
|
|
10
|
+
const seconds = Math.floor(diff / 1000);
|
|
11
|
+
const minutes = Math.floor(seconds / 60);
|
|
12
|
+
const hours = Math.floor(minutes / 60);
|
|
13
|
+
const days = Math.floor(hours / 24);
|
|
14
|
+
if (seconds < 60)
|
|
15
|
+
return "just now";
|
|
16
|
+
if (minutes < 60)
|
|
17
|
+
return `${minutes}m ago`;
|
|
18
|
+
if (hours < 24)
|
|
19
|
+
return `${hours}h ago`;
|
|
20
|
+
if (days < 7)
|
|
21
|
+
return `${days}d ago`;
|
|
22
|
+
return date.toLocaleDateString();
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Format a duration in milliseconds to human-readable
|
|
26
|
+
*/
|
|
27
|
+
export function formatDuration(ms) {
|
|
28
|
+
const seconds = Math.floor(ms / 1000);
|
|
29
|
+
const minutes = Math.floor(seconds / 60);
|
|
30
|
+
const hours = Math.floor(minutes / 60);
|
|
31
|
+
if (seconds < 60)
|
|
32
|
+
return `${seconds}s`;
|
|
33
|
+
if (minutes < 60)
|
|
34
|
+
return `${minutes}m ${seconds % 60}s`;
|
|
35
|
+
return `${hours}h ${minutes % 60}m`;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Format bytes to human-readable
|
|
39
|
+
*/
|
|
40
|
+
export function formatBytes(bytes) {
|
|
41
|
+
const units = ["B", "KB", "MB", "GB"];
|
|
42
|
+
let size = bytes;
|
|
43
|
+
let unitIndex = 0;
|
|
44
|
+
while (size >= 1024 && unitIndex < units.length - 1) {
|
|
45
|
+
size /= 1024;
|
|
46
|
+
unitIndex++;
|
|
47
|
+
}
|
|
48
|
+
return `${size.toFixed(1)} ${units[unitIndex]}`;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Generate a pairing code (XXX-XXX-XXX format)
|
|
52
|
+
*/
|
|
53
|
+
export function generatePairingCode() {
|
|
54
|
+
const chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"; // No ambiguous chars
|
|
55
|
+
const segments = [];
|
|
56
|
+
for (let i = 0; i < 3; i++) {
|
|
57
|
+
let segment = "";
|
|
58
|
+
for (let j = 0; j < 3; j++) {
|
|
59
|
+
segment += chars[Math.floor(Math.random() * chars.length)];
|
|
60
|
+
}
|
|
61
|
+
segments.push(segment);
|
|
62
|
+
}
|
|
63
|
+
return segments.join("-");
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Validate a pairing code format
|
|
67
|
+
*/
|
|
68
|
+
export function isValidPairingCode(code) {
|
|
69
|
+
return /^[A-Z0-9]{3}-[A-Z0-9]{3}-[A-Z0-9]{3}$/.test(code);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Deep clone an object
|
|
73
|
+
*/
|
|
74
|
+
export function deepClone(obj) {
|
|
75
|
+
return JSON.parse(JSON.stringify(obj));
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Debounce a function
|
|
79
|
+
*/
|
|
80
|
+
export function debounce(func, wait) {
|
|
81
|
+
let timeout = null;
|
|
82
|
+
return function executedFunction(...args) {
|
|
83
|
+
const later = () => {
|
|
84
|
+
timeout = null;
|
|
85
|
+
func(...args);
|
|
86
|
+
};
|
|
87
|
+
if (timeout)
|
|
88
|
+
clearTimeout(timeout);
|
|
89
|
+
timeout = setTimeout(later, wait);
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Throttle a function
|
|
94
|
+
*/
|
|
95
|
+
export function throttle(func, limit) {
|
|
96
|
+
let inThrottle;
|
|
97
|
+
return function executedFunction(...args) {
|
|
98
|
+
if (!inThrottle) {
|
|
99
|
+
func(...args);
|
|
100
|
+
inThrottle = true;
|
|
101
|
+
setTimeout(() => (inThrottle = false), limit);
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Sleep for a specified duration
|
|
107
|
+
*/
|
|
108
|
+
export function sleep(ms) {
|
|
109
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Retry a function with exponential backoff
|
|
113
|
+
*/
|
|
114
|
+
export async function retry(fn, options = {}) {
|
|
115
|
+
const { maxAttempts = 3, initialDelay = 1000, maxDelay = 10000, backoffMultiplier = 2, } = options;
|
|
116
|
+
let lastError;
|
|
117
|
+
let delay = initialDelay;
|
|
118
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
119
|
+
try {
|
|
120
|
+
return await fn();
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
lastError = error;
|
|
124
|
+
if (attempt === maxAttempts) {
|
|
125
|
+
throw lastError;
|
|
126
|
+
}
|
|
127
|
+
await sleep(delay);
|
|
128
|
+
delay = Math.min(delay * backoffMultiplier, maxDelay);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
throw lastError;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Parse a user's home directory path
|
|
135
|
+
*/
|
|
136
|
+
export function resolveHomePath(path) {
|
|
137
|
+
if (path.startsWith("~")) {
|
|
138
|
+
return path.replace("~", process.env.HOME || process.env.USERPROFILE || "");
|
|
139
|
+
}
|
|
140
|
+
return path;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Check if a path is within allowed directories
|
|
144
|
+
*/
|
|
145
|
+
export function isPathAllowed(path, allowed, denied) {
|
|
146
|
+
const resolved = resolveHomePath(path);
|
|
147
|
+
// Check if path is in allowed list
|
|
148
|
+
const isAllowed = allowed.some((dir) => resolved.startsWith(resolveHomePath(dir)));
|
|
149
|
+
if (!isAllowed)
|
|
150
|
+
return false;
|
|
151
|
+
// Check if path matches denied patterns
|
|
152
|
+
const isDenied = denied.some((pattern) => {
|
|
153
|
+
// Simple glob pattern matching
|
|
154
|
+
const regex = new RegExp("^" +
|
|
155
|
+
pattern
|
|
156
|
+
.replace(/\*/g, ".*")
|
|
157
|
+
.replace(/\?/g, ".") +
|
|
158
|
+
"$");
|
|
159
|
+
return regex.test(resolved);
|
|
160
|
+
});
|
|
161
|
+
return !isDenied;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Extract device ID from JWT token
|
|
165
|
+
*/
|
|
166
|
+
export function extractDeviceId(token) {
|
|
167
|
+
try {
|
|
168
|
+
const payload = JSON.parse(atob(token.split(".")[1]));
|
|
169
|
+
return payload.sub || null;
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Check if a JWT token is expired
|
|
177
|
+
*/
|
|
178
|
+
export function isTokenExpired(token) {
|
|
179
|
+
try {
|
|
180
|
+
const payload = JSON.parse(atob(token.split(".")[1]));
|
|
181
|
+
const now = Math.floor(Date.now() / 1000);
|
|
182
|
+
return payload.exp < now;
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.test.d.ts","sourceRoot":"","sources":["../src/utils.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit Tests for Shared Package Utilities
|
|
3
|
+
*/
|
|
4
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
5
|
+
import { formatDate, formatDuration, formatBytes, generatePairingCode, isValidPairingCode, deepClone, debounce, throttle, sleep, retry, resolveHomePath, isPathAllowed, extractDeviceId, isTokenExpired, } from './index.js';
|
|
6
|
+
describe('formatDate', () => {
|
|
7
|
+
it('should format dates as "just now" for recent dates', () => {
|
|
8
|
+
const now = new Date();
|
|
9
|
+
const date = new Date(now.getTime() - 30 * 1000);
|
|
10
|
+
expect(formatDate(date)).toBe('just now');
|
|
11
|
+
});
|
|
12
|
+
it('should format dates as "Xm ago" for minutes', () => {
|
|
13
|
+
const now = new Date();
|
|
14
|
+
const date = new Date(now.getTime() - 30 * 60 * 1000);
|
|
15
|
+
expect(formatDate(date)).toBe('30m ago');
|
|
16
|
+
});
|
|
17
|
+
it('should format dates as "Xh ago" for hours', () => {
|
|
18
|
+
const now = new Date();
|
|
19
|
+
const date = new Date(now.getTime() - 5 * 60 * 60 * 1000);
|
|
20
|
+
expect(formatDate(date)).toBe('5h ago');
|
|
21
|
+
});
|
|
22
|
+
it('should format dates as "Xd ago" for days', () => {
|
|
23
|
+
const now = new Date();
|
|
24
|
+
const date = new Date(now.getTime() - 3 * 24 * 60 * 60 * 1000);
|
|
25
|
+
expect(formatDate(date)).toBe('3d ago');
|
|
26
|
+
});
|
|
27
|
+
it('should format older dates as locale date string', () => {
|
|
28
|
+
const date = new Date('2020-01-01');
|
|
29
|
+
const formatted = formatDate(date);
|
|
30
|
+
expect(formatted).toBe(date.toLocaleDateString());
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
describe('formatDuration', () => {
|
|
34
|
+
it('should format seconds', () => {
|
|
35
|
+
expect(formatDuration(5000)).toBe('5s');
|
|
36
|
+
expect(formatDuration(59000)).toBe('59s');
|
|
37
|
+
});
|
|
38
|
+
it('should format minutes and seconds', () => {
|
|
39
|
+
expect(formatDuration(60000)).toBe('1m 0s');
|
|
40
|
+
expect(formatDuration(125000)).toBe('2m 5s');
|
|
41
|
+
});
|
|
42
|
+
it('should format hours and minutes', () => {
|
|
43
|
+
expect(formatDuration(3600000)).toBe('1h 0m');
|
|
44
|
+
expect(formatDuration(3665000)).toBe('1h 1m');
|
|
45
|
+
expect(formatDuration(7265000)).toBe('2h 1m');
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
describe('formatBytes', () => {
|
|
49
|
+
it('should format bytes', () => {
|
|
50
|
+
expect(formatBytes(0)).toBe('0.0 B');
|
|
51
|
+
expect(formatBytes(512)).toBe('512.0 B');
|
|
52
|
+
expect(formatBytes(1023)).toBe('1023.0 B');
|
|
53
|
+
});
|
|
54
|
+
it('should format kilobytes', () => {
|
|
55
|
+
expect(formatBytes(1024)).toBe('1.0 KB');
|
|
56
|
+
expect(formatBytes(1536)).toBe('1.5 KB');
|
|
57
|
+
expect(formatBytes(10240)).toBe('10.0 KB');
|
|
58
|
+
});
|
|
59
|
+
it('should format megabytes', () => {
|
|
60
|
+
expect(formatBytes(1048576)).toBe('1.0 MB');
|
|
61
|
+
expect(formatBytes(5242880)).toBe('5.0 MB');
|
|
62
|
+
});
|
|
63
|
+
it('should format gigabytes', () => {
|
|
64
|
+
expect(formatBytes(1073741824)).toBe('1.0 GB');
|
|
65
|
+
expect(formatBytes(2147483648)).toBe('2.0 GB');
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
describe('generatePairingCode', () => {
|
|
69
|
+
it('should generate valid pairing code format', () => {
|
|
70
|
+
const code = generatePairingCode();
|
|
71
|
+
expect(code).toMatch(/^[A-Z0-9]{3}-[A-Z0-9]{3}-[A-Z0-9]{3}$/);
|
|
72
|
+
});
|
|
73
|
+
it('should generate unique codes', () => {
|
|
74
|
+
const codes = new Set();
|
|
75
|
+
for (let i = 0; i < 100; i++) {
|
|
76
|
+
codes.add(generatePairingCode());
|
|
77
|
+
}
|
|
78
|
+
expect(codes.size).toBe(100);
|
|
79
|
+
});
|
|
80
|
+
it('should not contain ambiguous characters', () => {
|
|
81
|
+
const code = generatePairingCode();
|
|
82
|
+
expect(code).not.toMatch(/[IO01]/);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
describe('isValidPairingCode', () => {
|
|
86
|
+
it('should validate correct pairing codes', () => {
|
|
87
|
+
expect(isValidPairingCode('ABC-123-DEF')).toBe(true);
|
|
88
|
+
expect(isValidPairingCode('XYZ-987-ABC')).toBe(true);
|
|
89
|
+
});
|
|
90
|
+
it('should reject incorrect formats', () => {
|
|
91
|
+
expect(isValidPairingCode('abc-123-def')).toBe(false); // lowercase
|
|
92
|
+
expect(isValidPairingCode('ABC123DEF')).toBe(false); // no dashes
|
|
93
|
+
expect(isValidPairingCode('AB-123-DEF')).toBe(false); // too short
|
|
94
|
+
expect(isValidPairingCode('ABC-123-DEF-456')).toBe(false); // too long
|
|
95
|
+
expect(isValidPairingCode('')).toBe(false); // empty
|
|
96
|
+
expect(isValidPairingCode('ABC-1I3-DEF')).toBe(true); // valid format
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
describe('deepClone', () => {
|
|
100
|
+
it('should clone objects', () => {
|
|
101
|
+
const obj = { a: 1, b: 2 };
|
|
102
|
+
const cloned = deepClone(obj);
|
|
103
|
+
expect(cloned).toEqual(obj);
|
|
104
|
+
expect(cloned).not.toBe(obj);
|
|
105
|
+
});
|
|
106
|
+
it('should clone nested objects', () => {
|
|
107
|
+
const obj = { a: { b: { c: 1 } } };
|
|
108
|
+
const cloned = deepClone(obj);
|
|
109
|
+
expect(cloned).toEqual(obj);
|
|
110
|
+
expect(cloned.a).not.toBe(obj.a);
|
|
111
|
+
});
|
|
112
|
+
it('should clone arrays', () => {
|
|
113
|
+
const arr = [1, 2, 3];
|
|
114
|
+
const cloned = deepClone(arr);
|
|
115
|
+
expect(cloned).toEqual(arr);
|
|
116
|
+
expect(cloned).not.toBe(arr);
|
|
117
|
+
});
|
|
118
|
+
it('should clone objects with arrays', () => {
|
|
119
|
+
const obj = { arr: [1, 2, 3] };
|
|
120
|
+
const cloned = deepClone(obj);
|
|
121
|
+
expect(cloned).toEqual(obj);
|
|
122
|
+
expect(cloned.arr).not.toBe(obj.arr);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
describe('debounce', () => {
|
|
126
|
+
it('should debounce function calls', () => {
|
|
127
|
+
vi.useFakeTimers();
|
|
128
|
+
const fn = vi.fn();
|
|
129
|
+
const debouncedFn = debounce(fn, 100);
|
|
130
|
+
debouncedFn();
|
|
131
|
+
debouncedFn();
|
|
132
|
+
debouncedFn();
|
|
133
|
+
expect(fn).not.toHaveBeenCalled();
|
|
134
|
+
vi.advanceTimersByTime(100);
|
|
135
|
+
expect(fn).toHaveBeenCalledTimes(1);
|
|
136
|
+
vi.useRealTimers();
|
|
137
|
+
});
|
|
138
|
+
it('should call function with latest arguments', () => {
|
|
139
|
+
vi.useFakeTimers();
|
|
140
|
+
const fn = vi.fn();
|
|
141
|
+
const debouncedFn = debounce(fn, 100);
|
|
142
|
+
debouncedFn(1);
|
|
143
|
+
debouncedFn(2);
|
|
144
|
+
debouncedFn(3);
|
|
145
|
+
vi.advanceTimersByTime(100);
|
|
146
|
+
expect(fn).toHaveBeenCalledWith(3);
|
|
147
|
+
expect(fn).toHaveBeenCalledTimes(1);
|
|
148
|
+
vi.useRealTimers();
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
describe('throttle', () => {
|
|
152
|
+
it('should throttle function calls', () => {
|
|
153
|
+
vi.useFakeTimers();
|
|
154
|
+
const fn = vi.fn();
|
|
155
|
+
const throttledFn = throttle(fn, 100);
|
|
156
|
+
throttledFn();
|
|
157
|
+
throttledFn();
|
|
158
|
+
throttledFn();
|
|
159
|
+
expect(fn).toHaveBeenCalledTimes(1);
|
|
160
|
+
vi.advanceTimersByTime(100);
|
|
161
|
+
throttledFn();
|
|
162
|
+
expect(fn).toHaveBeenCalledTimes(2);
|
|
163
|
+
vi.useRealTimers();
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
describe('sleep', () => {
|
|
167
|
+
it('should sleep for specified duration', async () => {
|
|
168
|
+
vi.useFakeTimers();
|
|
169
|
+
const start = Date.now();
|
|
170
|
+
const promise = sleep(100);
|
|
171
|
+
vi.advanceTimersByTime(100);
|
|
172
|
+
await promise;
|
|
173
|
+
vi.useRealTimers();
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
describe('retry', () => {
|
|
177
|
+
it('should succeed on first try', async () => {
|
|
178
|
+
const fn = vi.fn().mockResolvedValue('success');
|
|
179
|
+
const result = await retry(fn);
|
|
180
|
+
expect(result).toBe('success');
|
|
181
|
+
expect(fn).toHaveBeenCalledTimes(1);
|
|
182
|
+
});
|
|
183
|
+
it('should retry on failure', async () => {
|
|
184
|
+
const fn = vi.fn()
|
|
185
|
+
.mockRejectedValueOnce(new Error('fail'))
|
|
186
|
+
.mockResolvedValue('success');
|
|
187
|
+
const result = await retry(fn, { maxAttempts: 3, initialDelay: 10 });
|
|
188
|
+
expect(result).toBe('success');
|
|
189
|
+
expect(fn).toHaveBeenCalledTimes(2);
|
|
190
|
+
});
|
|
191
|
+
it('should throw after max attempts', async () => {
|
|
192
|
+
const fn = vi.fn().mockRejectedValue(new Error('fail'));
|
|
193
|
+
await expect(retry(fn, { maxAttempts: 2, initialDelay: 10 })).rejects.toThrow('fail');
|
|
194
|
+
expect(fn).toHaveBeenCalledTimes(2);
|
|
195
|
+
});
|
|
196
|
+
it('should use exponential backoff', async () => {
|
|
197
|
+
vi.useFakeTimers();
|
|
198
|
+
const fn = vi.fn()
|
|
199
|
+
.mockRejectedValueOnce(new Error('fail'))
|
|
200
|
+
.mockRejectedValueOnce(new Error('fail'))
|
|
201
|
+
.mockResolvedValue('success');
|
|
202
|
+
const promise = retry(fn, {
|
|
203
|
+
maxAttempts: 3,
|
|
204
|
+
initialDelay: 100,
|
|
205
|
+
backoffMultiplier: 2,
|
|
206
|
+
});
|
|
207
|
+
// First attempt fails
|
|
208
|
+
await vi.advanceTimersByTimeAsync(100);
|
|
209
|
+
// Second attempt fails
|
|
210
|
+
await vi.advanceTimersByTimeAsync(200);
|
|
211
|
+
await promise;
|
|
212
|
+
expect(fn).toHaveBeenCalledTimes(3);
|
|
213
|
+
vi.useRealTimers();
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
describe('resolveHomePath', () => {
|
|
217
|
+
it('should replace ~ with HOME env var', () => {
|
|
218
|
+
const originalHome = process.env.HOME;
|
|
219
|
+
process.env.HOME = '/Users/test';
|
|
220
|
+
expect(resolveHomePath('~/Documents')).toBe('/Users/test/Documents');
|
|
221
|
+
expect(resolveHomePath('~/')).toBe('/Users/test/');
|
|
222
|
+
process.env.HOME = originalHome;
|
|
223
|
+
});
|
|
224
|
+
it('should not replace ~ in middle of path', () => {
|
|
225
|
+
expect(resolveHomePath('/path/~test')).toBe('/path/~test');
|
|
226
|
+
});
|
|
227
|
+
it('should return unchanged path if no ~', () => {
|
|
228
|
+
expect(resolveHomePath('/absolute/path')).toBe('/absolute/path');
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
describe('isPathAllowed', () => {
|
|
232
|
+
it('should check allowed paths', () => {
|
|
233
|
+
expect(isPathAllowed('/Users/test/project', ['/Users/test'], []))
|
|
234
|
+
.toBe(true);
|
|
235
|
+
expect(isPathAllowed('/Users/test/project', ['/Users/other'], []))
|
|
236
|
+
.toBe(false);
|
|
237
|
+
});
|
|
238
|
+
it('should check denied patterns', () => {
|
|
239
|
+
// Denied patterns match the entire path
|
|
240
|
+
expect(isPathAllowed('/Users/test/project/node_modules', ['/Users/test'], ['*/node_modules']))
|
|
241
|
+
.toBe(false);
|
|
242
|
+
expect(isPathAllowed('/Users/test/project/src', ['/Users/test'], ['*/node_modules']))
|
|
243
|
+
.toBe(true);
|
|
244
|
+
});
|
|
245
|
+
it('should support glob patterns', () => {
|
|
246
|
+
// Test with simpler glob patterns
|
|
247
|
+
expect(isPathAllowed('/Users/test/.env', ['/Users/test'], ['*/.env']))
|
|
248
|
+
.toBe(false);
|
|
249
|
+
expect(isPathAllowed('/Users/test/src', ['/Users/test'], ['*/.env']))
|
|
250
|
+
.toBe(true);
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
describe('extractDeviceId', () => {
|
|
254
|
+
it('should extract device ID from valid JWT', () => {
|
|
255
|
+
// Create a mock JWT token
|
|
256
|
+
const header = btoa(JSON.stringify({ alg: 'HS256', typ: 'JWT' }));
|
|
257
|
+
const payload = btoa(JSON.stringify({ sub: 'device-123', exp: Date.now() / 1000 + 3600 }));
|
|
258
|
+
const signature = 'signature';
|
|
259
|
+
const token = `${header}.${payload}.${signature}`;
|
|
260
|
+
expect(extractDeviceId(token)).toBe('device-123');
|
|
261
|
+
});
|
|
262
|
+
it('should return null for invalid JWT', () => {
|
|
263
|
+
expect(extractDeviceId('invalid')).toBeNull();
|
|
264
|
+
expect(extractDeviceId('not.a.jwt')).toBeNull();
|
|
265
|
+
});
|
|
266
|
+
it('should return null if no sub in payload', () => {
|
|
267
|
+
const header = btoa(JSON.stringify({ alg: 'HS256' }));
|
|
268
|
+
const payload = btoa(JSON.stringify({ exp: 123 }));
|
|
269
|
+
const token = `${header}.${payload}.signature`;
|
|
270
|
+
expect(extractDeviceId(token)).toBeNull();
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
describe('isTokenExpired', () => {
|
|
274
|
+
it('should return false for valid token', () => {
|
|
275
|
+
const header = btoa(JSON.stringify({ alg: 'HS256' }));
|
|
276
|
+
const payload = btoa(JSON.stringify({ exp: Math.floor(Date.now() / 1000) + 3600 }));
|
|
277
|
+
const token = `${header}.${payload}.signature`;
|
|
278
|
+
expect(isTokenExpired(token)).toBe(false);
|
|
279
|
+
});
|
|
280
|
+
it('should return true for expired token', () => {
|
|
281
|
+
const header = btoa(JSON.stringify({ alg: 'HS256' }));
|
|
282
|
+
const payload = btoa(JSON.stringify({ exp: Math.floor(Date.now() / 1000) - 3600 }));
|
|
283
|
+
const token = `${header}.${payload}.signature`;
|
|
284
|
+
expect(isTokenExpired(token)).toBe(true);
|
|
285
|
+
});
|
|
286
|
+
it('should return true for invalid token', () => {
|
|
287
|
+
expect(isTokenExpired('invalid')).toBe(true);
|
|
288
|
+
expect(isTokenExpired('not.a.jwt')).toBe(true);
|
|
289
|
+
});
|
|
290
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "cc-control-shared",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"package.json"
|
|
11
|
+
],
|
|
12
|
+
"main": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"import": "./dist/index.js",
|
|
18
|
+
"require": "./dist/index.cjs"
|
|
19
|
+
},
|
|
20
|
+
"./types": {
|
|
21
|
+
"types": "./dist/types/index.d.ts",
|
|
22
|
+
"import": "./dist/types/index.js"
|
|
23
|
+
},
|
|
24
|
+
"./protocols": {
|
|
25
|
+
"types": "./dist/protocols/index.d.ts",
|
|
26
|
+
"import": "./dist/protocols/index.js"
|
|
27
|
+
},
|
|
28
|
+
"./utils": {
|
|
29
|
+
"types": "./dist/utils/index.d.ts",
|
|
30
|
+
"import": "./dist/utils/index.js"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "tsc",
|
|
35
|
+
"dev": "tsc --watch",
|
|
36
|
+
"clean": "rm -rf dist",
|
|
37
|
+
"lint": "eslint src --ext .ts",
|
|
38
|
+
"typecheck": "tsc --noEmit"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"zod": "^3.23.0"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@types/node": "^20.0.0",
|
|
45
|
+
"typescript": "^5.6.0"
|
|
46
|
+
}
|
|
47
|
+
}
|