@autobe/ui 0.29.2 → 0.30.0-dev.20260315
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/LICENSE +661 -661
- package/README.md +261 -0
- package/lib/components/AutoBeChatMain.js +5 -5
- package/lib/components/AutoBeChatMain.js.map +1 -1
- package/lib/components/AutoBeConfigModal.js +9 -9
- package/lib/components/AutoBeStatusModal.js +4 -4
- package/lib/components/AutoBeStatusModal.js.map +1 -1
- package/lib/components/AutoBeUserMessageMovie.d.ts +2 -2
- package/lib/components/common/ChatBubble.d.ts +2 -2
- package/lib/components/common/openai/OpenAIContent.d.ts +2 -2
- package/lib/components/common/openai/OpenAIContent.js.map +1 -1
- package/lib/components/common/openai/OpenAIUserAudioContent.js +1 -1
- package/lib/components/common/openai/OpenAIUserAudioContent.js.map +1 -1
- package/lib/components/common/openai/OpenAIUserFileContent.js +1 -1
- package/lib/components/common/openai/OpenAIUserFileContent.js.map +1 -1
- package/lib/components/common/openai/OpenAIUserImageContent.d.ts +2 -2
- package/lib/components/events/AutoBeCompleteEventMovie.d.ts +2 -2
- package/lib/components/events/AutoBeCompleteEventMovie.js +5 -5
- package/lib/components/events/AutoBeCompleteEventMovie.js.map +1 -1
- package/lib/components/events/AutoBeCorrectEventMovie.d.ts +2 -2
- package/lib/components/events/AutoBeCorrectEventMovie.js +4 -4
- package/lib/components/events/AutoBeCorrectEventMovie.js.map +1 -1
- package/lib/components/events/AutoBeEventMovie.js +38 -17
- package/lib/components/events/AutoBeEventMovie.js.map +1 -1
- package/lib/components/events/AutoBeProgressEventMovie.js +73 -13
- package/lib/components/events/AutoBeProgressEventMovie.js.map +1 -1
- package/lib/components/events/AutoBeScenarioEventMovie.d.ts +2 -2
- package/lib/components/events/AutoBeScenarioEventMovie.js +18 -5
- package/lib/components/events/AutoBeScenarioEventMovie.js.map +1 -1
- package/lib/components/events/AutoBeStartEventMovie.d.ts +2 -2
- package/lib/components/events/AutoBeStartEventMovie.js +2 -2
- package/lib/components/events/AutoBeStartEventMovie.js.map +1 -1
- package/lib/components/events/AutoBeValidateEventMovie.d.ts +2 -2
- package/lib/components/events/AutoBeValidateEventMovie.js +3 -11
- package/lib/components/events/AutoBeValidateEventMovie.js.map +1 -1
- package/lib/components/events/groups/CorrectEventGroup.d.ts +2 -2
- package/lib/components/events/groups/CorrectEventGroup.js +1 -1
- package/lib/components/events/groups/CorrectEventGroup.js.map +1 -1
- package/lib/components/events/groups/ValidateEventGroup.d.ts +2 -2
- package/lib/components/events/groups/ValidateEventGroup.js +1 -2
- package/lib/components/events/groups/ValidateEventGroup.js.map +1 -1
- package/lib/components/events/utils/eventGrouper.js +1 -2
- package/lib/components/events/utils/eventGrouper.js.map +1 -1
- package/lib/components/upload/AutoBeChatUploadBox.d.ts +3 -4
- package/lib/components/upload/AutoBeChatUploadBox.js +2 -1
- package/lib/components/upload/AutoBeChatUploadBox.js.map +1 -1
- package/lib/components/upload/AutoBeChatUploadSendButton.js +1 -1
- package/lib/components/upload/AutoBeChatUploadSendButton.js.map +1 -1
- package/lib/context/AutoBeAgentContext.d.ts +1 -3
- package/lib/context/AutoBeAgentContext.js +0 -4
- package/lib/context/AutoBeAgentContext.js.map +1 -1
- package/lib/hooks/useSessionStorage.d.ts +4 -0
- package/lib/hooks/useSessionStorage.js +16 -0
- package/lib/hooks/useSessionStorage.js.map +1 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/strategy/AutoBeAgentSessionStorageStrategy.d.ts +10 -0
- package/lib/strategy/AutoBeAgentSessionStorageStrategy.js +117 -0
- package/lib/strategy/AutoBeAgentSessionStorageStrategy.js.map +1 -0
- package/lib/structure/AutoBeListener.js +91 -23
- package/lib/structure/AutoBeListener.js.map +1 -1
- package/lib/structure/AutoBeListenerState.d.ts +3 -3
- package/lib/structure/AutoBeListenerState.js +4 -4
- package/lib/structure/AutoBeListenerState.js.map +1 -1
- package/lib/structure/IAutoBeAgentSessionStorageStrategy.js +1 -1
- package/lib/structure/IAutoBeAgentSessionStorageStrategy.js.map +1 -1
- package/lib/utils/AutoBeFileUploader.d.ts +2 -2
- package/lib/utils/AutoBeFileUploader.js.map +1 -1
- package/package.json +3 -4
- package/src/components/AutoBeAssistantMessageMovie.tsx +22 -22
- package/src/components/AutoBeChatMain.tsx +376 -376
- package/src/components/AutoBeChatSidebar.tsx +414 -414
- package/src/components/AutoBeConfigButton.tsx +83 -83
- package/src/components/AutoBeConfigModal.tsx +443 -443
- package/src/components/AutoBeStatusButton.tsx +75 -75
- package/src/components/AutoBeStatusModal.tsx +486 -484
- package/src/components/AutoBeUserMessageMovie.tsx +27 -27
- package/src/components/common/ActionButton.tsx +205 -205
- package/src/components/common/ActionButtonGroup.tsx +80 -80
- package/src/components/common/AutoBeConfigInput.tsx +185 -185
- package/src/components/common/ChatBubble.tsx +119 -119
- package/src/components/common/Collapsible.tsx +95 -95
- package/src/components/common/CompactSessionIndicator.tsx +73 -73
- package/src/components/common/CompactSessionList.tsx +82 -82
- package/src/components/common/index.ts +8 -8
- package/src/components/common/openai/OpenAIContent.tsx +53 -53
- package/src/components/common/openai/OpenAIUserAudioContent.tsx +70 -70
- package/src/components/common/openai/OpenAIUserFileContent.tsx +76 -76
- package/src/components/common/openai/OpenAIUserImageContent.tsx +34 -34
- package/src/components/common/openai/OpenAIUserTextContent.tsx +15 -15
- package/src/components/common/openai/index.ts +5 -5
- package/src/components/events/AutoBeCompleteEventMovie.tsx +402 -402
- package/src/components/events/AutoBeCorrectEventMovie.tsx +354 -368
- package/src/components/events/AutoBeEventGroupMovie.tsx +18 -18
- package/src/components/events/AutoBeEventMovie.tsx +158 -139
- package/src/components/events/AutoBeProgressEventMovie.tsx +217 -157
- package/src/components/events/AutoBeScenarioEventMovie.tsx +135 -95
- package/src/components/events/AutoBeStartEventMovie.tsx +82 -82
- package/src/components/events/AutoBeValidateEventMovie.tsx +249 -286
- package/src/components/events/README.md +300 -300
- package/src/components/events/common/CollapsibleEventGroup.tsx +211 -211
- package/src/components/events/common/EventCard.tsx +61 -61
- package/src/components/events/common/EventContent.tsx +31 -31
- package/src/components/events/common/EventHeader.tsx +85 -85
- package/src/components/events/common/EventIcon.tsx +82 -82
- package/src/components/events/common/ProgressBar.tsx +64 -64
- package/src/components/events/common/index.ts +13 -13
- package/src/components/events/groups/CorrectEventGroup.tsx +183 -183
- package/src/components/events/groups/ValidateEventGroup.tsx +143 -146
- package/src/components/events/groups/index.ts +8 -8
- package/src/components/events/index.ts +16 -16
- package/src/components/events/utils/eventGrouper.tsx +116 -117
- package/src/components/events/utils/index.ts +1 -1
- package/src/components/index.ts +13 -13
- package/src/components/upload/AutoBeChatUploadBox.tsx +425 -424
- package/src/components/upload/AutoBeChatUploadSendButton.tsx +66 -66
- package/src/components/upload/AutoBeFileUploadBox.tsx +123 -123
- package/src/components/upload/AutoBeUploadConfig.ts +5 -5
- package/src/components/upload/AutoBeVoiceRecoderButton.tsx +100 -100
- package/src/components/upload/index.ts +5 -5
- package/src/constant/color.ts +28 -28
- package/src/context/AutoBeAgentContext.tsx +245 -258
- package/src/context/AutoBeAgentSessionList.tsx +58 -58
- package/src/context/SearchParamsContext.tsx +49 -49
- package/src/hooks/index.ts +3 -3
- package/src/hooks/useEscapeKey.ts +24 -24
- package/src/hooks/useIsomorphicLayoutEffect.ts +8 -8
- package/src/hooks/useMediaQuery.ts +73 -73
- package/src/hooks/useSessionStorage.ts +10 -0
- package/src/icons/Receipt.tsx +74 -74
- package/src/index.ts +9 -8
- package/src/strategy/AutoBeAgentSessionStorageStrategy.ts +127 -0
- package/src/structure/AutoBeListener.ts +373 -304
- package/src/structure/AutoBeListenerState.ts +53 -53
- package/src/structure/IAutoBeAgentSessionStorageStrategy.ts +87 -87
- package/src/structure/IAutoBeEventGroup.ts +6 -6
- package/src/structure/index.ts +4 -4
- package/src/types/config.ts +44 -44
- package/src/types/index.ts +1 -1
- package/src/utils/AutoBeFileUploader.ts +279 -279
- package/src/utils/AutoBeVoiceRecorder.ts +95 -95
- package/src/utils/__tests__/crypto.test.ts +286 -286
- package/src/utils/__tests__/storage.test.ts +229 -229
- package/src/utils/crypto.ts +95 -95
- package/src/utils/index.ts +6 -6
- package/src/utils/number.ts +17 -17
- package/src/utils/storage.ts +96 -96
- package/src/utils/time.ts +14 -14
- package/tsconfig.json +9 -9
- package/vitest.config.ts +15 -15
|
@@ -1,229 +1,229 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, test, vi } from "vitest";
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
clearEncryptedSessionStorage,
|
|
5
|
-
getEncryptedSessionStorage,
|
|
6
|
-
hasEncryptedSessionStorage,
|
|
7
|
-
removeEncryptedSessionStorage,
|
|
8
|
-
setEncryptedSessionStorage,
|
|
9
|
-
} from "../storage";
|
|
10
|
-
|
|
11
|
-
// Mock sessionStorage for testing
|
|
12
|
-
const mockSessionStorage = {
|
|
13
|
-
store: new Map<string, string>(),
|
|
14
|
-
getItem: vi.fn((key: string) => mockSessionStorage.store.get(key) || null),
|
|
15
|
-
setItem: vi.fn((key: string, value: string) => {
|
|
16
|
-
mockSessionStorage.store.set(key, value);
|
|
17
|
-
}),
|
|
18
|
-
removeItem: vi.fn((key: string) => {
|
|
19
|
-
mockSessionStorage.store.delete(key);
|
|
20
|
-
}),
|
|
21
|
-
clear: vi.fn(() => {
|
|
22
|
-
mockSessionStorage.store.clear();
|
|
23
|
-
}),
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
// Mock global sessionStorage
|
|
27
|
-
Object.defineProperty(global, "sessionStorage", {
|
|
28
|
-
value: mockSessionStorage,
|
|
29
|
-
writable: true,
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
Object.defineProperty(global, "window", {
|
|
33
|
-
value: { sessionStorage: mockSessionStorage },
|
|
34
|
-
writable: true,
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
describe("Storage Utils", () => {
|
|
38
|
-
beforeEach(() => {
|
|
39
|
-
mockSessionStorage.store.clear();
|
|
40
|
-
vi.clearAllMocks();
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
describe("encrypted sessionStorage operations", () => {
|
|
44
|
-
test("should store and retrieve encrypted data", () => {
|
|
45
|
-
const key = "test_key";
|
|
46
|
-
const value = "sk-test123456789";
|
|
47
|
-
|
|
48
|
-
setEncryptedSessionStorage(key, value);
|
|
49
|
-
const retrieved = getEncryptedSessionStorage(key);
|
|
50
|
-
|
|
51
|
-
expect(retrieved).toBe(value);
|
|
52
|
-
expect(mockSessionStorage.setItem).toHaveBeenCalledWith(
|
|
53
|
-
key,
|
|
54
|
-
expect.any(String),
|
|
55
|
-
);
|
|
56
|
-
expect(mockSessionStorage.getItem).toHaveBeenCalledWith(key);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
test("should store encrypted value, not plain text", () => {
|
|
60
|
-
const key = "test_encrypted";
|
|
61
|
-
const value = "secret-api-key";
|
|
62
|
-
|
|
63
|
-
setEncryptedSessionStorage(key, value);
|
|
64
|
-
const storedValue = mockSessionStorage.store.get(key);
|
|
65
|
-
|
|
66
|
-
expect(storedValue).not.toBe(value); // Should not store plain text
|
|
67
|
-
expect(storedValue).toBeTruthy(); // Should store something
|
|
68
|
-
expect(storedValue!.length).toBeGreaterThan(0); // Should have content
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
test("should handle empty values correctly", () => {
|
|
72
|
-
const key = "empty_test";
|
|
73
|
-
const value = "";
|
|
74
|
-
|
|
75
|
-
setEncryptedSessionStorage(key, value);
|
|
76
|
-
const retrieved = getEncryptedSessionStorage(key);
|
|
77
|
-
|
|
78
|
-
expect(retrieved).toBe("");
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
test("should return empty string for non-existent keys", () => {
|
|
82
|
-
const result = getEncryptedSessionStorage("non_existent_key");
|
|
83
|
-
expect(result).toBe("");
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
test("should remove encrypted data", () => {
|
|
87
|
-
const key = "test_remove";
|
|
88
|
-
const value = "test-value";
|
|
89
|
-
|
|
90
|
-
setEncryptedSessionStorage(key, value);
|
|
91
|
-
expect(getEncryptedSessionStorage(key)).toBe(value);
|
|
92
|
-
|
|
93
|
-
removeEncryptedSessionStorage(key);
|
|
94
|
-
expect(getEncryptedSessionStorage(key)).toBe("");
|
|
95
|
-
expect(mockSessionStorage.removeItem).toHaveBeenCalledWith(key);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
test("should check if encrypted data exists", () => {
|
|
99
|
-
const key = "existence_test";
|
|
100
|
-
const value = "test-value";
|
|
101
|
-
|
|
102
|
-
// Initially should not exist
|
|
103
|
-
expect(hasEncryptedSessionStorage(key)).toBe(false);
|
|
104
|
-
|
|
105
|
-
// After storing should exist
|
|
106
|
-
setEncryptedSessionStorage(key, value);
|
|
107
|
-
expect(hasEncryptedSessionStorage(key)).toBe(true);
|
|
108
|
-
|
|
109
|
-
// After removing should not exist
|
|
110
|
-
removeEncryptedSessionStorage(key);
|
|
111
|
-
expect(hasEncryptedSessionStorage(key)).toBe(false);
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
test("should handle storage errors gracefully", () => {
|
|
115
|
-
const originalSetItem = mockSessionStorage.setItem;
|
|
116
|
-
mockSessionStorage.setItem = vi.fn(() => {
|
|
117
|
-
throw new Error("Storage quota exceeded");
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
// Should throw error with meaningful message
|
|
121
|
-
expect(() => {
|
|
122
|
-
setEncryptedSessionStorage("test", "value");
|
|
123
|
-
}).toThrow(
|
|
124
|
-
'Failed to store encrypted data for key "test": Storage quota exceeded',
|
|
125
|
-
);
|
|
126
|
-
|
|
127
|
-
mockSessionStorage.setItem = originalSetItem; // Restore
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
test("should clear all sessionStorage data", () => {
|
|
131
|
-
// Mock setItem to not throw for this test
|
|
132
|
-
const originalSetItem = mockSessionStorage.setItem;
|
|
133
|
-
mockSessionStorage.setItem = vi.fn((key: string, value: string) => {
|
|
134
|
-
mockSessionStorage.store.set(key, value);
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
// Store some test data
|
|
138
|
-
setEncryptedSessionStorage("key1", "value1");
|
|
139
|
-
setEncryptedSessionStorage("key2", "value2");
|
|
140
|
-
|
|
141
|
-
expect(hasEncryptedSessionStorage("key1")).toBe(true);
|
|
142
|
-
expect(hasEncryptedSessionStorage("key2")).toBe(true);
|
|
143
|
-
|
|
144
|
-
// Clear all
|
|
145
|
-
clearEncryptedSessionStorage();
|
|
146
|
-
|
|
147
|
-
expect(hasEncryptedSessionStorage("key1")).toBe(false);
|
|
148
|
-
expect(hasEncryptedSessionStorage("key2")).toBe(false);
|
|
149
|
-
expect(mockSessionStorage.clear).toHaveBeenCalled();
|
|
150
|
-
|
|
151
|
-
mockSessionStorage.setItem = originalSetItem; // Restore
|
|
152
|
-
});
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
describe("Unicode and special data", () => {
|
|
156
|
-
const testCases = [
|
|
157
|
-
{ key: "korean", value: "안녕하세요" },
|
|
158
|
-
{ key: "emoji", value: "🚀🔐💎✨" },
|
|
159
|
-
{ key: "mixed", value: "Hello 안녕 🚀 World!" },
|
|
160
|
-
{ key: "api_key", value: "sk-proj-1234567890abcdefghijklmnop" },
|
|
161
|
-
{ key: "json", value: '{"name":"test","value":123}' },
|
|
162
|
-
{ key: "multiline", value: "line1\nline2\nline3" },
|
|
163
|
-
];
|
|
164
|
-
|
|
165
|
-
test.each(testCases)(
|
|
166
|
-
"should handle $key: $value correctly",
|
|
167
|
-
({ key, value }) => {
|
|
168
|
-
// Mock setItem to not throw for unicode tests
|
|
169
|
-
const originalSetItem = mockSessionStorage.setItem;
|
|
170
|
-
mockSessionStorage.setItem = vi.fn((key: string, value: string) => {
|
|
171
|
-
mockSessionStorage.store.set(key, value);
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
setEncryptedSessionStorage(key, value);
|
|
175
|
-
const retrieved = getEncryptedSessionStorage(key);
|
|
176
|
-
|
|
177
|
-
expect(retrieved).toBe(value);
|
|
178
|
-
expect(hasEncryptedSessionStorage(key)).toBe(true);
|
|
179
|
-
|
|
180
|
-
// Verify it's actually encrypted in storage
|
|
181
|
-
const rawStored = mockSessionStorage.store.get(key);
|
|
182
|
-
expect(rawStored).not.toBe(value);
|
|
183
|
-
expect(rawStored).toBeTruthy();
|
|
184
|
-
|
|
185
|
-
mockSessionStorage.setItem = originalSetItem; // Restore
|
|
186
|
-
},
|
|
187
|
-
);
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
describe("Edge cases", () => {
|
|
191
|
-
test("should handle null/undefined gracefully", () => {
|
|
192
|
-
// These should not throw
|
|
193
|
-
expect(() => getEncryptedSessionStorage("")).not.toThrow();
|
|
194
|
-
expect(() => hasEncryptedSessionStorage("")).not.toThrow();
|
|
195
|
-
expect(() => removeEncryptedSessionStorage("")).not.toThrow();
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
test("should handle SSR environment (no window)", () => {
|
|
199
|
-
const originalWindow = global.window;
|
|
200
|
-
// @ts-ignore
|
|
201
|
-
delete global.window;
|
|
202
|
-
|
|
203
|
-
// Should not throw and return safe defaults
|
|
204
|
-
expect(() => {
|
|
205
|
-
setEncryptedSessionStorage("test", "value");
|
|
206
|
-
const result = getEncryptedSessionStorage("test");
|
|
207
|
-
expect(result).toBe("");
|
|
208
|
-
const exists = hasEncryptedSessionStorage("test");
|
|
209
|
-
expect(exists).toBe(false);
|
|
210
|
-
removeEncryptedSessionStorage("test");
|
|
211
|
-
clearEncryptedSessionStorage();
|
|
212
|
-
}).not.toThrow();
|
|
213
|
-
|
|
214
|
-
global.window = originalWindow; // Restore
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
test("should handle corrupted storage data", () => {
|
|
218
|
-
const key = "corrupted_test";
|
|
219
|
-
|
|
220
|
-
// Manually put invalid encrypted data
|
|
221
|
-
mockSessionStorage.store.set(key, "invalid-base64-data!");
|
|
222
|
-
|
|
223
|
-
// Should throw error on corrupted data
|
|
224
|
-
expect(() => {
|
|
225
|
-
getEncryptedSessionStorage(key);
|
|
226
|
-
}).toThrow('Failed to retrieve encrypted data for key "corrupted_test"');
|
|
227
|
-
});
|
|
228
|
-
});
|
|
229
|
-
});
|
|
1
|
+
import { beforeEach, describe, expect, test, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
clearEncryptedSessionStorage,
|
|
5
|
+
getEncryptedSessionStorage,
|
|
6
|
+
hasEncryptedSessionStorage,
|
|
7
|
+
removeEncryptedSessionStorage,
|
|
8
|
+
setEncryptedSessionStorage,
|
|
9
|
+
} from "../storage";
|
|
10
|
+
|
|
11
|
+
// Mock sessionStorage for testing
|
|
12
|
+
const mockSessionStorage = {
|
|
13
|
+
store: new Map<string, string>(),
|
|
14
|
+
getItem: vi.fn((key: string) => mockSessionStorage.store.get(key) || null),
|
|
15
|
+
setItem: vi.fn((key: string, value: string) => {
|
|
16
|
+
mockSessionStorage.store.set(key, value);
|
|
17
|
+
}),
|
|
18
|
+
removeItem: vi.fn((key: string) => {
|
|
19
|
+
mockSessionStorage.store.delete(key);
|
|
20
|
+
}),
|
|
21
|
+
clear: vi.fn(() => {
|
|
22
|
+
mockSessionStorage.store.clear();
|
|
23
|
+
}),
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// Mock global sessionStorage
|
|
27
|
+
Object.defineProperty(global, "sessionStorage", {
|
|
28
|
+
value: mockSessionStorage,
|
|
29
|
+
writable: true,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
Object.defineProperty(global, "window", {
|
|
33
|
+
value: { sessionStorage: mockSessionStorage },
|
|
34
|
+
writable: true,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe("Storage Utils", () => {
|
|
38
|
+
beforeEach(() => {
|
|
39
|
+
mockSessionStorage.store.clear();
|
|
40
|
+
vi.clearAllMocks();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe("encrypted sessionStorage operations", () => {
|
|
44
|
+
test("should store and retrieve encrypted data", () => {
|
|
45
|
+
const key = "test_key";
|
|
46
|
+
const value = "sk-test123456789";
|
|
47
|
+
|
|
48
|
+
setEncryptedSessionStorage(key, value);
|
|
49
|
+
const retrieved = getEncryptedSessionStorage(key);
|
|
50
|
+
|
|
51
|
+
expect(retrieved).toBe(value);
|
|
52
|
+
expect(mockSessionStorage.setItem).toHaveBeenCalledWith(
|
|
53
|
+
key,
|
|
54
|
+
expect.any(String),
|
|
55
|
+
);
|
|
56
|
+
expect(mockSessionStorage.getItem).toHaveBeenCalledWith(key);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("should store encrypted value, not plain text", () => {
|
|
60
|
+
const key = "test_encrypted";
|
|
61
|
+
const value = "secret-api-key";
|
|
62
|
+
|
|
63
|
+
setEncryptedSessionStorage(key, value);
|
|
64
|
+
const storedValue = mockSessionStorage.store.get(key);
|
|
65
|
+
|
|
66
|
+
expect(storedValue).not.toBe(value); // Should not store plain text
|
|
67
|
+
expect(storedValue).toBeTruthy(); // Should store something
|
|
68
|
+
expect(storedValue!.length).toBeGreaterThan(0); // Should have content
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("should handle empty values correctly", () => {
|
|
72
|
+
const key = "empty_test";
|
|
73
|
+
const value = "";
|
|
74
|
+
|
|
75
|
+
setEncryptedSessionStorage(key, value);
|
|
76
|
+
const retrieved = getEncryptedSessionStorage(key);
|
|
77
|
+
|
|
78
|
+
expect(retrieved).toBe("");
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("should return empty string for non-existent keys", () => {
|
|
82
|
+
const result = getEncryptedSessionStorage("non_existent_key");
|
|
83
|
+
expect(result).toBe("");
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("should remove encrypted data", () => {
|
|
87
|
+
const key = "test_remove";
|
|
88
|
+
const value = "test-value";
|
|
89
|
+
|
|
90
|
+
setEncryptedSessionStorage(key, value);
|
|
91
|
+
expect(getEncryptedSessionStorage(key)).toBe(value);
|
|
92
|
+
|
|
93
|
+
removeEncryptedSessionStorage(key);
|
|
94
|
+
expect(getEncryptedSessionStorage(key)).toBe("");
|
|
95
|
+
expect(mockSessionStorage.removeItem).toHaveBeenCalledWith(key);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test("should check if encrypted data exists", () => {
|
|
99
|
+
const key = "existence_test";
|
|
100
|
+
const value = "test-value";
|
|
101
|
+
|
|
102
|
+
// Initially should not exist
|
|
103
|
+
expect(hasEncryptedSessionStorage(key)).toBe(false);
|
|
104
|
+
|
|
105
|
+
// After storing should exist
|
|
106
|
+
setEncryptedSessionStorage(key, value);
|
|
107
|
+
expect(hasEncryptedSessionStorage(key)).toBe(true);
|
|
108
|
+
|
|
109
|
+
// After removing should not exist
|
|
110
|
+
removeEncryptedSessionStorage(key);
|
|
111
|
+
expect(hasEncryptedSessionStorage(key)).toBe(false);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("should handle storage errors gracefully", () => {
|
|
115
|
+
const originalSetItem = mockSessionStorage.setItem;
|
|
116
|
+
mockSessionStorage.setItem = vi.fn(() => {
|
|
117
|
+
throw new Error("Storage quota exceeded");
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// Should throw error with meaningful message
|
|
121
|
+
expect(() => {
|
|
122
|
+
setEncryptedSessionStorage("test", "value");
|
|
123
|
+
}).toThrow(
|
|
124
|
+
'Failed to store encrypted data for key "test": Storage quota exceeded',
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
mockSessionStorage.setItem = originalSetItem; // Restore
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("should clear all sessionStorage data", () => {
|
|
131
|
+
// Mock setItem to not throw for this test
|
|
132
|
+
const originalSetItem = mockSessionStorage.setItem;
|
|
133
|
+
mockSessionStorage.setItem = vi.fn((key: string, value: string) => {
|
|
134
|
+
mockSessionStorage.store.set(key, value);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// Store some test data
|
|
138
|
+
setEncryptedSessionStorage("key1", "value1");
|
|
139
|
+
setEncryptedSessionStorage("key2", "value2");
|
|
140
|
+
|
|
141
|
+
expect(hasEncryptedSessionStorage("key1")).toBe(true);
|
|
142
|
+
expect(hasEncryptedSessionStorage("key2")).toBe(true);
|
|
143
|
+
|
|
144
|
+
// Clear all
|
|
145
|
+
clearEncryptedSessionStorage();
|
|
146
|
+
|
|
147
|
+
expect(hasEncryptedSessionStorage("key1")).toBe(false);
|
|
148
|
+
expect(hasEncryptedSessionStorage("key2")).toBe(false);
|
|
149
|
+
expect(mockSessionStorage.clear).toHaveBeenCalled();
|
|
150
|
+
|
|
151
|
+
mockSessionStorage.setItem = originalSetItem; // Restore
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
describe("Unicode and special data", () => {
|
|
156
|
+
const testCases = [
|
|
157
|
+
{ key: "korean", value: "안녕하세요" },
|
|
158
|
+
{ key: "emoji", value: "🚀🔐💎✨" },
|
|
159
|
+
{ key: "mixed", value: "Hello 안녕 🚀 World!" },
|
|
160
|
+
{ key: "api_key", value: "sk-proj-1234567890abcdefghijklmnop" },
|
|
161
|
+
{ key: "json", value: '{"name":"test","value":123}' },
|
|
162
|
+
{ key: "multiline", value: "line1\nline2\nline3" },
|
|
163
|
+
];
|
|
164
|
+
|
|
165
|
+
test.each(testCases)(
|
|
166
|
+
"should handle $key: $value correctly",
|
|
167
|
+
({ key, value }) => {
|
|
168
|
+
// Mock setItem to not throw for unicode tests
|
|
169
|
+
const originalSetItem = mockSessionStorage.setItem;
|
|
170
|
+
mockSessionStorage.setItem = vi.fn((key: string, value: string) => {
|
|
171
|
+
mockSessionStorage.store.set(key, value);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
setEncryptedSessionStorage(key, value);
|
|
175
|
+
const retrieved = getEncryptedSessionStorage(key);
|
|
176
|
+
|
|
177
|
+
expect(retrieved).toBe(value);
|
|
178
|
+
expect(hasEncryptedSessionStorage(key)).toBe(true);
|
|
179
|
+
|
|
180
|
+
// Verify it's actually encrypted in storage
|
|
181
|
+
const rawStored = mockSessionStorage.store.get(key);
|
|
182
|
+
expect(rawStored).not.toBe(value);
|
|
183
|
+
expect(rawStored).toBeTruthy();
|
|
184
|
+
|
|
185
|
+
mockSessionStorage.setItem = originalSetItem; // Restore
|
|
186
|
+
},
|
|
187
|
+
);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
describe("Edge cases", () => {
|
|
191
|
+
test("should handle null/undefined gracefully", () => {
|
|
192
|
+
// These should not throw
|
|
193
|
+
expect(() => getEncryptedSessionStorage("")).not.toThrow();
|
|
194
|
+
expect(() => hasEncryptedSessionStorage("")).not.toThrow();
|
|
195
|
+
expect(() => removeEncryptedSessionStorage("")).not.toThrow();
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test("should handle SSR environment (no window)", () => {
|
|
199
|
+
const originalWindow = global.window;
|
|
200
|
+
// @ts-ignore
|
|
201
|
+
delete global.window;
|
|
202
|
+
|
|
203
|
+
// Should not throw and return safe defaults
|
|
204
|
+
expect(() => {
|
|
205
|
+
setEncryptedSessionStorage("test", "value");
|
|
206
|
+
const result = getEncryptedSessionStorage("test");
|
|
207
|
+
expect(result).toBe("");
|
|
208
|
+
const exists = hasEncryptedSessionStorage("test");
|
|
209
|
+
expect(exists).toBe(false);
|
|
210
|
+
removeEncryptedSessionStorage("test");
|
|
211
|
+
clearEncryptedSessionStorage();
|
|
212
|
+
}).not.toThrow();
|
|
213
|
+
|
|
214
|
+
global.window = originalWindow; // Restore
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
test("should handle corrupted storage data", () => {
|
|
218
|
+
const key = "corrupted_test";
|
|
219
|
+
|
|
220
|
+
// Manually put invalid encrypted data
|
|
221
|
+
mockSessionStorage.store.set(key, "invalid-base64-data!");
|
|
222
|
+
|
|
223
|
+
// Should throw error on corrupted data
|
|
224
|
+
expect(() => {
|
|
225
|
+
getEncryptedSessionStorage(key);
|
|
226
|
+
}).toThrow('Failed to retrieve encrypted data for key "corrupted_test"');
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
});
|
package/src/utils/crypto.ts
CHANGED
|
@@ -1,95 +1,95 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Simple and reliable encryption utilities using browser built-in functions
|
|
3
|
-
* Uses TextEncoder/TextDecoder for proper Unicode support + XOR + Base64
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
const ENCRYPTION_KEY = "AutoBE_Secret_2024_v3.0_Unicode"; // Unicode-safe key
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Simple encrypt using TextEncoder for Unicode safety
|
|
10
|
-
*
|
|
11
|
-
* @param text - Text to encrypt
|
|
12
|
-
* @returns Encrypted base64 string
|
|
13
|
-
*/
|
|
14
|
-
export const encrypt = (text: string): string => {
|
|
15
|
-
if (!text) return "";
|
|
16
|
-
|
|
17
|
-
try {
|
|
18
|
-
// Use TextEncoder for proper Unicode → UTF-8 bytes conversion
|
|
19
|
-
const encoder = new TextEncoder();
|
|
20
|
-
const textBytes = encoder.encode(text);
|
|
21
|
-
|
|
22
|
-
// Generate simple salt
|
|
23
|
-
const salt = Math.random().toString(36).substring(2, 10);
|
|
24
|
-
const keyBytes = encoder.encode(ENCRYPTION_KEY + salt);
|
|
25
|
-
|
|
26
|
-
// XOR encryption on bytes level
|
|
27
|
-
const encryptedBytes = new Uint8Array(textBytes.length);
|
|
28
|
-
for (let i = 0; i < textBytes.length; i++) {
|
|
29
|
-
encryptedBytes[i] = textBytes[i] ^ keyBytes[i % keyBytes.length];
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// Convert to hex string for safe concatenation
|
|
33
|
-
const encryptedHex = Array.from(encryptedBytes)
|
|
34
|
-
.map((b) => b.toString(16).padStart(2, "0"))
|
|
35
|
-
.join("");
|
|
36
|
-
|
|
37
|
-
// Combine salt + encrypted hex
|
|
38
|
-
const combined = salt + ":" + encryptedHex;
|
|
39
|
-
|
|
40
|
-
// Base64 encode the final result (handle Unicode properly)
|
|
41
|
-
const result = btoa(unescape(encodeURIComponent(combined)));
|
|
42
|
-
return result;
|
|
43
|
-
} catch (error) {
|
|
44
|
-
throw new Error(
|
|
45
|
-
`Encryption failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Simple decrypt using TextDecoder for Unicode safety
|
|
52
|
-
*
|
|
53
|
-
* @param encryptedText - Base64 encrypted string
|
|
54
|
-
* @returns Decrypted plain text
|
|
55
|
-
*/
|
|
56
|
-
export const decrypt = (encryptedText: string): string => {
|
|
57
|
-
if (!encryptedText) return "";
|
|
58
|
-
|
|
59
|
-
try {
|
|
60
|
-
// Base64 decode (handle Unicode properly)
|
|
61
|
-
const combined = decodeURIComponent(escape(atob(encryptedText)));
|
|
62
|
-
const parts = combined.split(":");
|
|
63
|
-
|
|
64
|
-
if (parts.length !== 2) {
|
|
65
|
-
throw new Error("Invalid encrypted format: expected salt:data format");
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const salt = parts[0];
|
|
69
|
-
const encryptedHex = parts[1];
|
|
70
|
-
|
|
71
|
-
// Convert hex back to bytes
|
|
72
|
-
const encryptedBytes = new Uint8Array(
|
|
73
|
-
encryptedHex.match(/.{2}/g)?.map((hex) => parseInt(hex, 16)) || [],
|
|
74
|
-
);
|
|
75
|
-
|
|
76
|
-
// Recreate key
|
|
77
|
-
const encoder = new TextEncoder();
|
|
78
|
-
const keyBytes = encoder.encode(ENCRYPTION_KEY + salt);
|
|
79
|
-
|
|
80
|
-
// XOR decryption
|
|
81
|
-
const decryptedBytes = new Uint8Array(encryptedBytes.length);
|
|
82
|
-
for (let i = 0; i < encryptedBytes.length; i++) {
|
|
83
|
-
decryptedBytes[i] = encryptedBytes[i] ^ keyBytes[i % keyBytes.length];
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Use TextDecoder for proper UTF-8 → Unicode conversion
|
|
87
|
-
const decoder = new TextDecoder();
|
|
88
|
-
const result = decoder.decode(decryptedBytes);
|
|
89
|
-
return result;
|
|
90
|
-
} catch (error) {
|
|
91
|
-
throw new Error(
|
|
92
|
-
`Decryption failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
93
|
-
);
|
|
94
|
-
}
|
|
95
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* Simple and reliable encryption utilities using browser built-in functions
|
|
3
|
+
* Uses TextEncoder/TextDecoder for proper Unicode support + XOR + Base64
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const ENCRYPTION_KEY = "AutoBE_Secret_2024_v3.0_Unicode"; // Unicode-safe key
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Simple encrypt using TextEncoder for Unicode safety
|
|
10
|
+
*
|
|
11
|
+
* @param text - Text to encrypt
|
|
12
|
+
* @returns Encrypted base64 string
|
|
13
|
+
*/
|
|
14
|
+
export const encrypt = (text: string): string => {
|
|
15
|
+
if (!text) return "";
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
// Use TextEncoder for proper Unicode → UTF-8 bytes conversion
|
|
19
|
+
const encoder = new TextEncoder();
|
|
20
|
+
const textBytes = encoder.encode(text);
|
|
21
|
+
|
|
22
|
+
// Generate simple salt
|
|
23
|
+
const salt = Math.random().toString(36).substring(2, 10);
|
|
24
|
+
const keyBytes = encoder.encode(ENCRYPTION_KEY + salt);
|
|
25
|
+
|
|
26
|
+
// XOR encryption on bytes level
|
|
27
|
+
const encryptedBytes = new Uint8Array(textBytes.length);
|
|
28
|
+
for (let i = 0; i < textBytes.length; i++) {
|
|
29
|
+
encryptedBytes[i] = textBytes[i] ^ keyBytes[i % keyBytes.length];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Convert to hex string for safe concatenation
|
|
33
|
+
const encryptedHex = Array.from(encryptedBytes)
|
|
34
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
35
|
+
.join("");
|
|
36
|
+
|
|
37
|
+
// Combine salt + encrypted hex
|
|
38
|
+
const combined = salt + ":" + encryptedHex;
|
|
39
|
+
|
|
40
|
+
// Base64 encode the final result (handle Unicode properly)
|
|
41
|
+
const result = btoa(unescape(encodeURIComponent(combined)));
|
|
42
|
+
return result;
|
|
43
|
+
} catch (error) {
|
|
44
|
+
throw new Error(
|
|
45
|
+
`Encryption failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Simple decrypt using TextDecoder for Unicode safety
|
|
52
|
+
*
|
|
53
|
+
* @param encryptedText - Base64 encrypted string
|
|
54
|
+
* @returns Decrypted plain text
|
|
55
|
+
*/
|
|
56
|
+
export const decrypt = (encryptedText: string): string => {
|
|
57
|
+
if (!encryptedText) return "";
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
// Base64 decode (handle Unicode properly)
|
|
61
|
+
const combined = decodeURIComponent(escape(atob(encryptedText)));
|
|
62
|
+
const parts = combined.split(":");
|
|
63
|
+
|
|
64
|
+
if (parts.length !== 2) {
|
|
65
|
+
throw new Error("Invalid encrypted format: expected salt:data format");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const salt = parts[0];
|
|
69
|
+
const encryptedHex = parts[1];
|
|
70
|
+
|
|
71
|
+
// Convert hex back to bytes
|
|
72
|
+
const encryptedBytes = new Uint8Array(
|
|
73
|
+
encryptedHex.match(/.{2}/g)?.map((hex) => parseInt(hex, 16)) || [],
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
// Recreate key
|
|
77
|
+
const encoder = new TextEncoder();
|
|
78
|
+
const keyBytes = encoder.encode(ENCRYPTION_KEY + salt);
|
|
79
|
+
|
|
80
|
+
// XOR decryption
|
|
81
|
+
const decryptedBytes = new Uint8Array(encryptedBytes.length);
|
|
82
|
+
for (let i = 0; i < encryptedBytes.length; i++) {
|
|
83
|
+
decryptedBytes[i] = encryptedBytes[i] ^ keyBytes[i % keyBytes.length];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Use TextDecoder for proper UTF-8 → Unicode conversion
|
|
87
|
+
const decoder = new TextDecoder();
|
|
88
|
+
const result = decoder.decode(decryptedBytes);
|
|
89
|
+
return result;
|
|
90
|
+
} catch (error) {
|
|
91
|
+
throw new Error(
|
|
92
|
+
`Decryption failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
};
|
package/src/utils/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export * from "./AutoBeFileUploader";
|
|
2
|
-
export * from "./AutoBeVoiceRecorder";
|
|
3
|
-
export * from "./time";
|
|
4
|
-
export * from "./crypto";
|
|
5
|
-
export * from "./storage";
|
|
6
|
-
export * from "./number";
|
|
1
|
+
export * from "./AutoBeFileUploader";
|
|
2
|
+
export * from "./AutoBeVoiceRecorder";
|
|
3
|
+
export * from "./time";
|
|
4
|
+
export * from "./crypto";
|
|
5
|
+
export * from "./storage";
|
|
6
|
+
export * from "./number";
|