latchkey 0.1.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/.nvmrc +1 -0
- package/.pre-commit-config.yaml +22 -0
- package/.prettierignore +4 -0
- package/.prettierrc +7 -0
- package/CLAUDE.md +13 -0
- package/LICENSE +7 -0
- package/README.md +167 -0
- package/dist/scripts/cryptFile.d.ts +21 -0
- package/dist/scripts/cryptFile.d.ts.map +1 -0
- package/dist/scripts/cryptFile.js +106 -0
- package/dist/scripts/cryptFile.js.map +1 -0
- package/dist/scripts/encryptFile.d.ts +21 -0
- package/dist/scripts/encryptFile.d.ts.map +1 -0
- package/dist/scripts/encryptFile.js +101 -0
- package/dist/scripts/encryptFile.js.map +1 -0
- package/dist/scripts/recordBrowserSession.d.ts +18 -0
- package/dist/scripts/recordBrowserSession.d.ts.map +1 -0
- package/dist/scripts/recordBrowserSession.js +213 -0
- package/dist/scripts/recordBrowserSession.js.map +1 -0
- package/dist/src/apiCredentialStore.d.ts +19 -0
- package/dist/src/apiCredentialStore.d.ts.map +1 -0
- package/dist/src/apiCredentialStore.js +65 -0
- package/dist/src/apiCredentialStore.js.map +1 -0
- package/dist/src/apiCredentials.d.ts +134 -0
- package/dist/src/apiCredentials.d.ts.map +1 -0
- package/dist/src/apiCredentials.js +139 -0
- package/dist/src/apiCredentials.js.map +1 -0
- package/dist/src/browserConfig.d.ts +90 -0
- package/dist/src/browserConfig.d.ts.map +1 -0
- package/dist/src/browserConfig.js +259 -0
- package/dist/src/browserConfig.js.map +1 -0
- package/dist/src/browserState.d.ts +8 -0
- package/dist/src/browserState.d.ts.map +1 -0
- package/dist/src/browserState.js +21 -0
- package/dist/src/browserState.js.map +1 -0
- package/dist/src/cli.d.ts +6 -0
- package/dist/src/cli.d.ts.map +1 -0
- package/dist/src/cli.js +25 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/cliCommands.d.ts +29 -0
- package/dist/src/cliCommands.d.ts.map +1 -0
- package/dist/src/cliCommands.js +264 -0
- package/dist/src/cliCommands.js.map +1 -0
- package/dist/src/config.d.ts +35 -0
- package/dist/src/config.d.ts.map +1 -0
- package/dist/src/config.js +96 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/curl.d.ts +29 -0
- package/dist/src/curl.d.ts.map +1 -0
- package/dist/src/curl.js +53 -0
- package/dist/src/curl.js.map +1 -0
- package/dist/src/encryptedStorage.d.ts +39 -0
- package/dist/src/encryptedStorage.d.ts.map +1 -0
- package/dist/src/encryptedStorage.js +128 -0
- package/dist/src/encryptedStorage.js.map +1 -0
- package/dist/src/encryption.d.ts +28 -0
- package/dist/src/encryption.d.ts.map +1 -0
- package/dist/src/encryption.js +86 -0
- package/dist/src/encryption.js.map +1 -0
- package/dist/src/index.d.ts +14 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +17 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/keychain.d.ts +33 -0
- package/dist/src/keychain.d.ts.map +1 -0
- package/dist/src/keychain.js +94 -0
- package/dist/src/keychain.js.map +1 -0
- package/dist/src/playwrightUtils.d.ts +27 -0
- package/dist/src/playwrightUtils.d.ts.map +1 -0
- package/dist/src/playwrightUtils.js +122 -0
- package/dist/src/playwrightUtils.js.map +1 -0
- package/dist/src/registry.d.ts +12 -0
- package/dist/src/registry.d.ts.map +1 -0
- package/dist/src/registry.js +30 -0
- package/dist/src/registry.js.map +1 -0
- package/dist/src/services/base.d.ts +98 -0
- package/dist/src/services/base.d.ts.map +1 -0
- package/dist/src/services/base.js +137 -0
- package/dist/src/services/base.js.map +1 -0
- package/dist/src/services/discord.d.ts +20 -0
- package/dist/src/services/discord.d.ts.map +1 -0
- package/dist/src/services/discord.js +55 -0
- package/dist/src/services/discord.js.map +1 -0
- package/dist/src/services/dropbox.d.ts +23 -0
- package/dist/src/services/dropbox.d.ts.map +1 -0
- package/dist/src/services/dropbox.js +136 -0
- package/dist/src/services/dropbox.js.map +1 -0
- package/dist/src/services/github.d.ts +23 -0
- package/dist/src/services/github.d.ts.map +1 -0
- package/dist/src/services/github.js +110 -0
- package/dist/src/services/github.js.map +1 -0
- package/dist/src/services/index.d.ts +12 -0
- package/dist/src/services/index.d.ts.map +1 -0
- package/dist/src/services/index.js +11 -0
- package/dist/src/services/index.js.map +1 -0
- package/dist/src/services/linear.d.ts +23 -0
- package/dist/src/services/linear.d.ts.map +1 -0
- package/dist/src/services/linear.js +110 -0
- package/dist/src/services/linear.js.map +1 -0
- package/dist/src/services/slack.d.ts +21 -0
- package/dist/src/services/slack.d.ts.map +1 -0
- package/dist/src/services/slack.js +67 -0
- package/dist/src/services/slack.js.map +1 -0
- package/dist/tests/apiCredentialStore.test.d.ts +2 -0
- package/dist/tests/apiCredentialStore.test.d.ts.map +1 -0
- package/dist/tests/apiCredentialStore.test.js +130 -0
- package/dist/tests/apiCredentialStore.test.js.map +1 -0
- package/dist/tests/apiCredentials.test.d.ts +2 -0
- package/dist/tests/apiCredentials.test.d.ts.map +1 -0
- package/dist/tests/apiCredentials.test.js +169 -0
- package/dist/tests/apiCredentials.test.js.map +1 -0
- package/dist/tests/cli.test.d.ts +2 -0
- package/dist/tests/cli.test.d.ts.map +1 -0
- package/dist/tests/cli.test.js +584 -0
- package/dist/tests/cli.test.js.map +1 -0
- package/dist/tests/encryptedStorage.test.d.ts +2 -0
- package/dist/tests/encryptedStorage.test.d.ts.map +1 -0
- package/dist/tests/encryptedStorage.test.js +126 -0
- package/dist/tests/encryptedStorage.test.js.map +1 -0
- package/dist/tests/encryption.test.d.ts +2 -0
- package/dist/tests/encryption.test.d.ts.map +1 -0
- package/dist/tests/encryption.test.js +121 -0
- package/dist/tests/encryption.test.js.map +1 -0
- package/dist/tests/lint.test.d.ts +2 -0
- package/dist/tests/lint.test.d.ts.map +1 -0
- package/dist/tests/lint.test.js +18 -0
- package/dist/tests/lint.test.js.map +1 -0
- package/dist/tests/registry.test.d.ts +2 -0
- package/dist/tests/registry.test.d.ts.map +1 -0
- package/dist/tests/registry.test.js +85 -0
- package/dist/tests/registry.test.js.map +1 -0
- package/dist/tests/servicesAgainstRecordings.test.d.ts +20 -0
- package/dist/tests/servicesAgainstRecordings.test.d.ts.map +1 -0
- package/dist/tests/servicesAgainstRecordings.test.js +157 -0
- package/dist/tests/servicesAgainstRecordings.test.js.map +1 -0
- package/dist/tests/typecheck.test.d.ts +2 -0
- package/dist/tests/typecheck.test.d.ts.map +1 -0
- package/dist/tests/typecheck.test.js +18 -0
- package/dist/tests/typecheck.test.js.map +1 -0
- package/docs/development.md +94 -0
- package/eslint.config.js +30 -0
- package/integrations/SKILL.md +62 -0
- package/package.json +68 -0
- package/scripts/cryptFile.ts +123 -0
- package/scripts/recordBrowserSession.ts +280 -0
- package/scripts/tsconfig.json +10 -0
- package/src/apiCredentialStore.ts +87 -0
- package/src/apiCredentials.ts +180 -0
- package/src/cli.ts +32 -0
- package/src/cliCommands.ts +321 -0
- package/src/config.ts +115 -0
- package/src/curl.ts +78 -0
- package/src/encryptedStorage.ts +161 -0
- package/src/encryption.ts +106 -0
- package/src/index.ts +65 -0
- package/src/keychain.ts +105 -0
- package/src/playwrightUtils.ts +143 -0
- package/src/registry.ts +35 -0
- package/src/services/base.ts +234 -0
- package/src/services/discord.ts +73 -0
- package/src/services/dropbox.ts +173 -0
- package/src/services/github.ts +139 -0
- package/src/services/index.ts +13 -0
- package/src/services/linear.ts +134 -0
- package/src/services/slack.ts +85 -0
- package/tests/apiCredentialStore.test.ts +162 -0
- package/tests/apiCredentials.test.ts +195 -0
- package/tests/cli.test.ts +798 -0
- package/tests/encryptedStorage.test.ts +173 -0
- package/tests/encryption.test.ts +169 -0
- package/tests/lint.test.ts +19 -0
- package/tests/registry.test.ts +103 -0
- package/tests/servicesAgainstRecordings.test.ts +230 -0
- package/tests/typecheck.test.ts +19 -0
- package/tsconfig.json +24 -0
- package/vitest.config.ts +13 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { mkdtempSync, rmSync, readFileSync, existsSync, statSync, writeFileSync, chmodSync, } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { EncryptedStorage, EncryptedStorageError, PathIsDirectoryError, } from '../src/encryptedStorage.js';
|
|
6
|
+
import { generateKey } from '../src/encryption.js';
|
|
7
|
+
describe('EncryptedStorage', () => {
|
|
8
|
+
let tempDir;
|
|
9
|
+
let testKey;
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
tempDir = mkdtempSync(join(tmpdir(), 'latchkey-encrypted-test-'));
|
|
12
|
+
testKey = generateKey();
|
|
13
|
+
});
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
16
|
+
});
|
|
17
|
+
describe('with encryption key from Config', () => {
|
|
18
|
+
it('should encrypt data when encryptionKey is set', () => {
|
|
19
|
+
const filePath = join(tempDir, 'test.json.enc');
|
|
20
|
+
const content = '{"token": "secret-value"}';
|
|
21
|
+
const storage = new EncryptedStorage({
|
|
22
|
+
encryptionKeyOverride: testKey,
|
|
23
|
+
});
|
|
24
|
+
storage.writeFile(filePath, content);
|
|
25
|
+
// Verify the file is written to the exact path specified
|
|
26
|
+
expect(existsSync(filePath)).toBe(true);
|
|
27
|
+
// Verify the file is encrypted (starts with prefix)
|
|
28
|
+
const rawContent = readFileSync(filePath, 'utf-8');
|
|
29
|
+
expect(rawContent).toMatch(/^LATCHKEY_ENCRYPTED:/);
|
|
30
|
+
expect(rawContent).not.toContain('secret-value');
|
|
31
|
+
});
|
|
32
|
+
it('should decrypt data with the same key', () => {
|
|
33
|
+
const filePath = join(tempDir, 'test.json.enc');
|
|
34
|
+
const content = '{"token": "secret-value"}';
|
|
35
|
+
const storage = new EncryptedStorage({
|
|
36
|
+
encryptionKeyOverride: testKey,
|
|
37
|
+
});
|
|
38
|
+
storage.writeFile(filePath, content);
|
|
39
|
+
const retrieved = storage.readFile(filePath);
|
|
40
|
+
expect(retrieved).toBe(content);
|
|
41
|
+
});
|
|
42
|
+
it('should fail to decrypt with wrong key', () => {
|
|
43
|
+
const filePath = join(tempDir, 'test.json.enc');
|
|
44
|
+
const content = '{"token": "secret-value"}';
|
|
45
|
+
const key1 = generateKey();
|
|
46
|
+
const key2 = generateKey();
|
|
47
|
+
// Write with one key
|
|
48
|
+
const storageWrite = new EncryptedStorage({
|
|
49
|
+
encryptionKeyOverride: key1,
|
|
50
|
+
});
|
|
51
|
+
storageWrite.writeFile(filePath, content);
|
|
52
|
+
// Read with different key
|
|
53
|
+
const storageRead = new EncryptedStorage({
|
|
54
|
+
encryptionKeyOverride: key2,
|
|
55
|
+
});
|
|
56
|
+
expect(() => storageRead.readFile(filePath)).toThrow(EncryptedStorageError);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
describe('file permissions', () => {
|
|
60
|
+
it('should set chmod 600 on written files', () => {
|
|
61
|
+
const filePath = join(tempDir, 'test.json.enc');
|
|
62
|
+
const content = '{"token": "secret-value"}';
|
|
63
|
+
const storage = new EncryptedStorage({
|
|
64
|
+
encryptionKeyOverride: testKey,
|
|
65
|
+
});
|
|
66
|
+
storage.writeFile(filePath, content);
|
|
67
|
+
// Check file permissions (600 = 0o600 = 384 in decimal)
|
|
68
|
+
const stats = statSync(filePath);
|
|
69
|
+
const permissions = stats.mode & 0o777;
|
|
70
|
+
expect(permissions).toBe(0o600);
|
|
71
|
+
});
|
|
72
|
+
it('should create parent directories with chmod 700', () => {
|
|
73
|
+
const filePath = join(tempDir, 'nested', 'deep', 'test.json.enc');
|
|
74
|
+
const content = '{"token": "secret-value"}';
|
|
75
|
+
const storage = new EncryptedStorage({
|
|
76
|
+
encryptionKeyOverride: testKey,
|
|
77
|
+
});
|
|
78
|
+
storage.writeFile(filePath, content);
|
|
79
|
+
expect(existsSync(filePath)).toBe(true);
|
|
80
|
+
});
|
|
81
|
+
it('should allow overwriting file with secure permissions', () => {
|
|
82
|
+
const filePath = join(tempDir, 'secure.json.enc');
|
|
83
|
+
const content = '{"token": "secret-value"}';
|
|
84
|
+
// Create a file with secure permissions
|
|
85
|
+
writeFileSync(filePath, 'existing content', { encoding: 'utf-8' });
|
|
86
|
+
chmodSync(filePath, 0o600);
|
|
87
|
+
const storage = new EncryptedStorage({
|
|
88
|
+
encryptionKeyOverride: testKey,
|
|
89
|
+
});
|
|
90
|
+
// Should not throw
|
|
91
|
+
storage.writeFile(filePath, content);
|
|
92
|
+
const retrieved = storage.readFile(filePath);
|
|
93
|
+
expect(retrieved).toBe(content);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
describe('readFile', () => {
|
|
97
|
+
it('should return null for non-existent file', () => {
|
|
98
|
+
const filePath = join(tempDir, 'nonexistent.json.enc');
|
|
99
|
+
const storage = new EncryptedStorage({
|
|
100
|
+
encryptionKeyOverride: testKey,
|
|
101
|
+
});
|
|
102
|
+
expect(storage.readFile(filePath)).toBeNull();
|
|
103
|
+
});
|
|
104
|
+
it('should throw PathIsDirectoryError when path is a directory', () => {
|
|
105
|
+
const storage = new EncryptedStorage({
|
|
106
|
+
encryptionKeyOverride: testKey,
|
|
107
|
+
});
|
|
108
|
+
expect(() => storage.readFile(tempDir)).toThrow(PathIsDirectoryError);
|
|
109
|
+
expect(() => storage.readFile(tempDir)).toThrow('Path is a directory, not a file');
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
describe('writeFile', () => {
|
|
113
|
+
it('should throw PathIsDirectoryError when path is a directory', () => {
|
|
114
|
+
const storage = new EncryptedStorage({
|
|
115
|
+
encryptionKeyOverride: testKey,
|
|
116
|
+
});
|
|
117
|
+
expect(() => {
|
|
118
|
+
storage.writeFile(tempDir, 'content');
|
|
119
|
+
}).toThrow(PathIsDirectoryError);
|
|
120
|
+
expect(() => {
|
|
121
|
+
storage.writeFile(tempDir, 'content');
|
|
122
|
+
}).toThrow('Path is a directory, not a file');
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
//# sourceMappingURL=encryptedStorage.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"encryptedStorage.test.js","sourceRoot":"","sources":["../../tests/encryptedStorage.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"encryption.test.d.ts","sourceRoot":"","sources":["../../tests/encryption.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { encrypt, decrypt, generateKey, DecryptionError, EncryptionError, } from '../src/encryption.js';
|
|
3
|
+
describe('encryption', () => {
|
|
4
|
+
describe('encrypt/decrypt roundtrip', () => {
|
|
5
|
+
it('should encrypt and decrypt a simple string', () => {
|
|
6
|
+
const key = generateKey();
|
|
7
|
+
const plaintext = 'Hello, World!';
|
|
8
|
+
const encrypted = encrypt(plaintext, key);
|
|
9
|
+
const decrypted = decrypt(encrypted, key);
|
|
10
|
+
expect(decrypted).toBe(plaintext);
|
|
11
|
+
});
|
|
12
|
+
it('should encrypt and decrypt a JSON string', () => {
|
|
13
|
+
const key = generateKey();
|
|
14
|
+
const data = { token: 'xoxc-test', dCookie: 'd-value' };
|
|
15
|
+
const plaintext = JSON.stringify(data, null, 2);
|
|
16
|
+
const encrypted = encrypt(plaintext, key);
|
|
17
|
+
const decrypted = decrypt(encrypted, key);
|
|
18
|
+
expect(decrypted).toBe(plaintext);
|
|
19
|
+
expect(JSON.parse(decrypted)).toEqual(data);
|
|
20
|
+
});
|
|
21
|
+
it('should encrypt and decrypt unicode characters', () => {
|
|
22
|
+
const key = generateKey();
|
|
23
|
+
const plaintext = '日本語 🎉 émojis and ünïcödé';
|
|
24
|
+
const encrypted = encrypt(plaintext, key);
|
|
25
|
+
const decrypted = decrypt(encrypted, key);
|
|
26
|
+
expect(decrypted).toBe(plaintext);
|
|
27
|
+
});
|
|
28
|
+
it('should encrypt and decrypt empty string', () => {
|
|
29
|
+
const key = generateKey();
|
|
30
|
+
const plaintext = '';
|
|
31
|
+
const encrypted = encrypt(plaintext, key);
|
|
32
|
+
const decrypted = decrypt(encrypted, key);
|
|
33
|
+
expect(decrypted).toBe(plaintext);
|
|
34
|
+
});
|
|
35
|
+
it('should encrypt and decrypt long text', () => {
|
|
36
|
+
const key = generateKey();
|
|
37
|
+
const plaintext = 'a'.repeat(10000);
|
|
38
|
+
const encrypted = encrypt(plaintext, key);
|
|
39
|
+
const decrypted = decrypt(encrypted, key);
|
|
40
|
+
expect(decrypted).toBe(plaintext);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
describe('encrypt', () => {
|
|
44
|
+
it('should produce different ciphertext for same plaintext (due to random IV)', () => {
|
|
45
|
+
const key = generateKey();
|
|
46
|
+
const plaintext = 'Hello, World!';
|
|
47
|
+
const encrypted1 = encrypt(plaintext, key);
|
|
48
|
+
const encrypted2 = encrypt(plaintext, key);
|
|
49
|
+
expect(encrypted1).not.toBe(encrypted2);
|
|
50
|
+
});
|
|
51
|
+
it('should produce base64 output', () => {
|
|
52
|
+
const key = generateKey();
|
|
53
|
+
const plaintext = 'Hello, World!';
|
|
54
|
+
const encrypted = encrypt(plaintext, key);
|
|
55
|
+
// Base64 only contains alphanumeric, +, /, and = characters
|
|
56
|
+
expect(encrypted).toMatch(/^[A-Za-z0-9+/]+=*$/);
|
|
57
|
+
});
|
|
58
|
+
it('should fail with invalid key length', () => {
|
|
59
|
+
const shortKey = Buffer.from('too-short').toString('base64');
|
|
60
|
+
const plaintext = 'Hello, World!';
|
|
61
|
+
expect(() => encrypt(plaintext, shortKey)).toThrow(EncryptionError);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
describe('decrypt', () => {
|
|
65
|
+
it('should fail with wrong key', () => {
|
|
66
|
+
const key1 = generateKey();
|
|
67
|
+
const key2 = generateKey();
|
|
68
|
+
const plaintext = 'Hello, World!';
|
|
69
|
+
const encrypted = encrypt(plaintext, key1);
|
|
70
|
+
expect(() => decrypt(encrypted, key2)).toThrow(DecryptionError);
|
|
71
|
+
});
|
|
72
|
+
it('should fail with corrupted data', () => {
|
|
73
|
+
const key = generateKey();
|
|
74
|
+
const plaintext = 'Hello, World!';
|
|
75
|
+
const encrypted = encrypt(plaintext, key);
|
|
76
|
+
// Corrupt the encrypted data
|
|
77
|
+
const corrupted = encrypted.slice(0, -5) + 'xxxxx';
|
|
78
|
+
expect(() => decrypt(corrupted, key)).toThrow(DecryptionError);
|
|
79
|
+
});
|
|
80
|
+
it('should fail with too short data', () => {
|
|
81
|
+
const key = generateKey();
|
|
82
|
+
expect(() => decrypt('dG9vIHNob3J0', key)).toThrow(DecryptionError);
|
|
83
|
+
});
|
|
84
|
+
it('should fail with invalid key length', () => {
|
|
85
|
+
const key = generateKey();
|
|
86
|
+
const shortKey = Buffer.from('too-short').toString('base64');
|
|
87
|
+
const plaintext = 'Hello, World!';
|
|
88
|
+
const encrypted = encrypt(plaintext, key);
|
|
89
|
+
expect(() => decrypt(encrypted, shortKey)).toThrow(DecryptionError);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
describe('generateKey', () => {
|
|
93
|
+
it('should generate a 256-bit key (44 base64 chars)', () => {
|
|
94
|
+
const key = generateKey();
|
|
95
|
+
// 32 bytes encoded as base64 = 44 characters (with padding)
|
|
96
|
+
expect(key.length).toBe(44);
|
|
97
|
+
});
|
|
98
|
+
it('should generate different keys each time', () => {
|
|
99
|
+
const key1 = generateKey();
|
|
100
|
+
const key2 = generateKey();
|
|
101
|
+
expect(key1).not.toBe(key2);
|
|
102
|
+
});
|
|
103
|
+
it('should generate valid base64 keys', () => {
|
|
104
|
+
const key = generateKey();
|
|
105
|
+
expect(key).toMatch(/^[A-Za-z0-9+/]+=*$/);
|
|
106
|
+
});
|
|
107
|
+
it('should generate keys that decode to 32 bytes', () => {
|
|
108
|
+
const key = generateKey();
|
|
109
|
+
const decoded = Buffer.from(key, 'base64');
|
|
110
|
+
expect(decoded.length).toBe(32);
|
|
111
|
+
});
|
|
112
|
+
it('should generate keys that work for encryption', () => {
|
|
113
|
+
const key = generateKey();
|
|
114
|
+
const plaintext = 'test data';
|
|
115
|
+
const encrypted = encrypt(plaintext, key);
|
|
116
|
+
const decrypted = decrypt(encrypted, key);
|
|
117
|
+
expect(decrypted).toBe(plaintext);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
//# sourceMappingURL=encryption.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"encryption.test.js","sourceRoot":"","sources":["../../tests/encryption.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lint.test.d.ts","sourceRoot":"","sources":["../../tests/lint.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { execSync } from 'child_process';
|
|
3
|
+
import { dirname, join } from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
const projectRoot = join(__dirname, '..');
|
|
7
|
+
describe('Linter', () => {
|
|
8
|
+
it('should pass linting', () => {
|
|
9
|
+
expect(() => {
|
|
10
|
+
execSync('npm run lint', {
|
|
11
|
+
cwd: projectRoot,
|
|
12
|
+
encoding: 'utf-8',
|
|
13
|
+
stdio: 'pipe',
|
|
14
|
+
});
|
|
15
|
+
}).not.toThrow();
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
//# sourceMappingURL=lint.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lint.test.js","sourceRoot":"","sources":["../../tests/lint.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,IAAI,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;AAC1D,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;AAE1C,QAAQ,CAAC,QAAQ,EAAE,GAAG,EAAE;IACtB,EAAE,CAAC,qBAAqB,EAAE,GAAG,EAAE;QAC7B,MAAM,CAAC,GAAG,EAAE;YACV,QAAQ,CAAC,cAAc,EAAE;gBACvB,GAAG,EAAE,WAAW;gBAChB,QAAQ,EAAE,OAAO;gBACjB,KAAK,EAAE,MAAM;aACd,CAAC,CAAC;QACL,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;IACnB,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.test.d.ts","sourceRoot":"","sources":["../../tests/registry.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { Registry, REGISTRY } from '../src/registry.js';
|
|
3
|
+
import { SLACK, DISCORD, GITHUB, DROPBOX, LINEAR } from '../src/services/index.js';
|
|
4
|
+
describe('Registry', () => {
|
|
5
|
+
describe('getByName', () => {
|
|
6
|
+
it('should find Slack by name', () => {
|
|
7
|
+
expect(REGISTRY.getByName('slack')).toBe(SLACK);
|
|
8
|
+
});
|
|
9
|
+
it('should find Discord by name', () => {
|
|
10
|
+
expect(REGISTRY.getByName('discord')).toBe(DISCORD);
|
|
11
|
+
});
|
|
12
|
+
it('should find GitHub by name', () => {
|
|
13
|
+
expect(REGISTRY.getByName('github')).toBe(GITHUB);
|
|
14
|
+
});
|
|
15
|
+
it('should find Dropbox by name', () => {
|
|
16
|
+
expect(REGISTRY.getByName('dropbox')).toBe(DROPBOX);
|
|
17
|
+
});
|
|
18
|
+
it('should find Linear by name', () => {
|
|
19
|
+
expect(REGISTRY.getByName('linear')).toBe(LINEAR);
|
|
20
|
+
});
|
|
21
|
+
it('should return null for unknown service', () => {
|
|
22
|
+
expect(REGISTRY.getByName('unknown')).toBeNull();
|
|
23
|
+
});
|
|
24
|
+
it('should be case-sensitive', () => {
|
|
25
|
+
expect(REGISTRY.getByName('Slack')).toBeNull();
|
|
26
|
+
expect(REGISTRY.getByName('SLACK')).toBeNull();
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
describe('getByUrl', () => {
|
|
30
|
+
it('should find Slack by API URL', () => {
|
|
31
|
+
expect(REGISTRY.getByUrl('https://slack.com/api/auth.test')).toBe(SLACK);
|
|
32
|
+
expect(REGISTRY.getByUrl('https://slack.com/api/users.list')).toBe(SLACK);
|
|
33
|
+
});
|
|
34
|
+
it('should find Discord by API URL', () => {
|
|
35
|
+
expect(REGISTRY.getByUrl('https://discord.com/api/v9/users/@me')).toBe(DISCORD);
|
|
36
|
+
expect(REGISTRY.getByUrl('https://discord.com/api/guilds')).toBe(DISCORD);
|
|
37
|
+
});
|
|
38
|
+
it('should find GitHub by API URL', () => {
|
|
39
|
+
expect(REGISTRY.getByUrl('https://api.github.com/user')).toBe(GITHUB);
|
|
40
|
+
expect(REGISTRY.getByUrl('https://api.github.com/repos')).toBe(GITHUB);
|
|
41
|
+
});
|
|
42
|
+
it('should find Dropbox by API URL', () => {
|
|
43
|
+
expect(REGISTRY.getByUrl('https://api.dropboxapi.com/2/users/get_current_account')).toBe(DROPBOX);
|
|
44
|
+
expect(REGISTRY.getByUrl('https://content.dropboxapi.com/upload')).toBe(DROPBOX);
|
|
45
|
+
expect(REGISTRY.getByUrl('https://notify.dropboxapi.com/subscribe')).toBe(DROPBOX);
|
|
46
|
+
});
|
|
47
|
+
it('should find Linear by API URL', () => {
|
|
48
|
+
expect(REGISTRY.getByUrl('https://api.linear.app/graphql')).toBe(LINEAR);
|
|
49
|
+
});
|
|
50
|
+
it('should return null for unknown URL', () => {
|
|
51
|
+
expect(REGISTRY.getByUrl('https://example.com/api')).toBeNull();
|
|
52
|
+
expect(REGISTRY.getByUrl('https://google.com')).toBeNull();
|
|
53
|
+
});
|
|
54
|
+
it('should not match partial URLs', () => {
|
|
55
|
+
expect(REGISTRY.getByUrl('https://slack.com/')).toBeNull();
|
|
56
|
+
expect(REGISTRY.getByUrl('https://slack.com')).toBeNull();
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
describe('services', () => {
|
|
60
|
+
it('should contain all services', () => {
|
|
61
|
+
expect(REGISTRY.services).toHaveLength(5);
|
|
62
|
+
expect(REGISTRY.services).toContain(SLACK);
|
|
63
|
+
expect(REGISTRY.services).toContain(DISCORD);
|
|
64
|
+
expect(REGISTRY.services).toContain(GITHUB);
|
|
65
|
+
expect(REGISTRY.services).toContain(DROPBOX);
|
|
66
|
+
expect(REGISTRY.services).toContain(LINEAR);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
describe('custom registry', () => {
|
|
70
|
+
it('should work with custom service list', () => {
|
|
71
|
+
const customRegistry = new Registry([SLACK, GITHUB]);
|
|
72
|
+
expect(customRegistry.services).toHaveLength(2);
|
|
73
|
+
expect(customRegistry.getByName('slack')).toBe(SLACK);
|
|
74
|
+
expect(customRegistry.getByName('github')).toBe(GITHUB);
|
|
75
|
+
expect(customRegistry.getByName('discord')).toBeNull();
|
|
76
|
+
});
|
|
77
|
+
it('should work with empty service list', () => {
|
|
78
|
+
const emptyRegistry = new Registry([]);
|
|
79
|
+
expect(emptyRegistry.services).toHaveLength(0);
|
|
80
|
+
expect(emptyRegistry.getByName('slack')).toBeNull();
|
|
81
|
+
expect(emptyRegistry.getByUrl('https://slack.com/api/test')).toBeNull();
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
//# sourceMappingURL=registry.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.test.js","sourceRoot":"","sources":["../../tests/registry.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test login recordings against service implementations.
|
|
3
|
+
*
|
|
4
|
+
* This module validates that recorded login sessions can be used to test
|
|
5
|
+
* service API credential extraction logic. It discovers recordings in
|
|
6
|
+
* scripts/recordings/<service_name>/ and verifies that the service's
|
|
7
|
+
* getApiCredentialsFromResponse() method can extract valid API credentials
|
|
8
|
+
* from the recorded requests.
|
|
9
|
+
*
|
|
10
|
+
* The tests work by loading recorded HTTP request/response pairs from login_session.json
|
|
11
|
+
* and creating mock Response objects to pass to the service's API credential extraction
|
|
12
|
+
* method. This validates that the service can correctly identify and extract
|
|
13
|
+
* API credentials from outgoing browser requests.
|
|
14
|
+
*
|
|
15
|
+
* Usage:
|
|
16
|
+
* npm test -- tests/servicesAgainstRecordings.test.ts # Test all recordings
|
|
17
|
+
* npm test -- tests/servicesAgainstRecordings.test.ts -t slack # Test only Slack
|
|
18
|
+
*/
|
|
19
|
+
export {};
|
|
20
|
+
//# sourceMappingURL=servicesAgainstRecordings.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"servicesAgainstRecordings.test.d.ts","sourceRoot":"","sources":["../../tests/servicesAgainstRecordings.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test login recordings against service implementations.
|
|
3
|
+
*
|
|
4
|
+
* This module validates that recorded login sessions can be used to test
|
|
5
|
+
* service API credential extraction logic. It discovers recordings in
|
|
6
|
+
* scripts/recordings/<service_name>/ and verifies that the service's
|
|
7
|
+
* getApiCredentialsFromResponse() method can extract valid API credentials
|
|
8
|
+
* from the recorded requests.
|
|
9
|
+
*
|
|
10
|
+
* The tests work by loading recorded HTTP request/response pairs from login_session.json
|
|
11
|
+
* and creating mock Response objects to pass to the service's API credential extraction
|
|
12
|
+
* method. This validates that the service can correctly identify and extract
|
|
13
|
+
* API credentials from outgoing browser requests.
|
|
14
|
+
*
|
|
15
|
+
* Usage:
|
|
16
|
+
* npm test -- tests/servicesAgainstRecordings.test.ts # Test all recordings
|
|
17
|
+
* npm test -- tests/servicesAgainstRecordings.test.ts -t slack # Test only Slack
|
|
18
|
+
*/
|
|
19
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
20
|
+
import { dirname, join, resolve } from 'node:path';
|
|
21
|
+
import { fileURLToPath } from 'node:url';
|
|
22
|
+
import { describe, it, expect } from 'vitest';
|
|
23
|
+
import { REGISTRY } from '../src/registry.js';
|
|
24
|
+
import { SimpleServiceSession } from '../src/services/base.js';
|
|
25
|
+
// Get the directory of this file
|
|
26
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
27
|
+
const __dirname = dirname(__filename);
|
|
28
|
+
// Recordings directory relative to this file
|
|
29
|
+
const RECORDINGS_DIRECTORY = resolve(__dirname, '..', 'scripts', 'recordings');
|
|
30
|
+
// Default recording filename (matches recordBrowserSession.ts)
|
|
31
|
+
const DEFAULT_RECORDING_NAME = 'login_session.json';
|
|
32
|
+
// Do not test services that require special followup steps.
|
|
33
|
+
const BLACKLIST = new Set(['dropbox', 'github', 'linear']);
|
|
34
|
+
class InvalidRecordingError extends Error {
|
|
35
|
+
constructor(message) {
|
|
36
|
+
super(message);
|
|
37
|
+
this.name = 'InvalidRecordingError';
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
class ApiCredentialExtractionError extends Error {
|
|
41
|
+
constructor(message) {
|
|
42
|
+
super(message);
|
|
43
|
+
this.name = 'ApiCredentialExtractionError';
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function loadRecordingEntries(requestsPath) {
|
|
47
|
+
const content = readFileSync(requestsPath, 'utf-8');
|
|
48
|
+
return JSON.parse(content);
|
|
49
|
+
}
|
|
50
|
+
function createMockRequest(requestData) {
|
|
51
|
+
const mockRequest = {
|
|
52
|
+
url: () => requestData.url,
|
|
53
|
+
method: () => requestData.method,
|
|
54
|
+
headers: () => requestData.headers,
|
|
55
|
+
allHeaders: () => Promise.resolve(requestData.headers),
|
|
56
|
+
resourceType: () => requestData.resource_type,
|
|
57
|
+
postData: () => requestData.post_data ?? null,
|
|
58
|
+
};
|
|
59
|
+
return mockRequest;
|
|
60
|
+
}
|
|
61
|
+
function createMockResponse(responseData, mockRequest) {
|
|
62
|
+
const body = responseData.body ?? '';
|
|
63
|
+
const mockResponse = {
|
|
64
|
+
status: () => responseData.status,
|
|
65
|
+
statusText: () => responseData.status_text,
|
|
66
|
+
headers: () => responseData.headers,
|
|
67
|
+
allHeaders: () => Promise.resolve(responseData.headers),
|
|
68
|
+
request: () => mockRequest,
|
|
69
|
+
text: () => Promise.resolve(body),
|
|
70
|
+
json: () => Promise.resolve(JSON.parse(body)),
|
|
71
|
+
body: () => Promise.resolve(Buffer.from(body)),
|
|
72
|
+
};
|
|
73
|
+
return mockResponse;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Test a service's API credential extraction using a recorded session.
|
|
77
|
+
*
|
|
78
|
+
* Loads recorded HTTP request/response pairs and tests that the service can
|
|
79
|
+
* extract API credentials from them using getApiCredentialsFromResponse().
|
|
80
|
+
*/
|
|
81
|
+
async function testServiceWithRecording(service, recordingDirectory) {
|
|
82
|
+
const requestsPath = join(recordingDirectory, DEFAULT_RECORDING_NAME);
|
|
83
|
+
if (!existsSync(requestsPath)) {
|
|
84
|
+
throw new InvalidRecordingError(`Requests file not found: ${requestsPath}`);
|
|
85
|
+
}
|
|
86
|
+
const recordingEntries = loadRecordingEntries(requestsPath);
|
|
87
|
+
if (recordingEntries.length === 0) {
|
|
88
|
+
throw new InvalidRecordingError('No requests recorded');
|
|
89
|
+
}
|
|
90
|
+
const session = service.getSession();
|
|
91
|
+
if (!(session instanceof SimpleServiceSession)) {
|
|
92
|
+
throw new InvalidRecordingError(`Service ${service.name} does not use SimpleServiceSession, cannot test with recordings`);
|
|
93
|
+
}
|
|
94
|
+
// Try to extract API credentials from each recorded request/response pair
|
|
95
|
+
for (const entry of recordingEntries) {
|
|
96
|
+
const mockRequest = createMockRequest(entry.request);
|
|
97
|
+
const mockResponse = createMockResponse(entry.response, mockRequest);
|
|
98
|
+
// Call onResponse which internally calls getApiCredentialsFromResponse
|
|
99
|
+
session.onResponse(mockResponse);
|
|
100
|
+
// Give async operations time to complete (e.g., Slack reads response body)
|
|
101
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
102
|
+
}
|
|
103
|
+
// Give additional time for any async operations to complete
|
|
104
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
105
|
+
// Access the apiCredentials via the session (it's protected but accessible for testing)
|
|
106
|
+
const apiCredentials = session
|
|
107
|
+
.apiCredentials;
|
|
108
|
+
if (apiCredentials !== null) {
|
|
109
|
+
return apiCredentials;
|
|
110
|
+
}
|
|
111
|
+
throw new ApiCredentialExtractionError(`No API credentials could be extracted from ${String(recordingEntries.length)} recorded entries`);
|
|
112
|
+
}
|
|
113
|
+
function discoverRecordings() {
|
|
114
|
+
const recordings = [];
|
|
115
|
+
if (!existsSync(RECORDINGS_DIRECTORY)) {
|
|
116
|
+
return recordings;
|
|
117
|
+
}
|
|
118
|
+
const items = readdirSync(RECORDINGS_DIRECTORY, { withFileTypes: true });
|
|
119
|
+
for (const item of items) {
|
|
120
|
+
if (item.isDirectory() && !item.name.startsWith('.') && !BLACKLIST.has(item.name)) {
|
|
121
|
+
const recordingPath = join(RECORDINGS_DIRECTORY, item.name);
|
|
122
|
+
const requestsPath = join(recordingPath, DEFAULT_RECORDING_NAME);
|
|
123
|
+
if (existsSync(requestsPath)) {
|
|
124
|
+
recordings.push({
|
|
125
|
+
serviceName: item.name,
|
|
126
|
+
recordingPath,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return recordings.sort((a, b) => a.serviceName.localeCompare(b.serviceName));
|
|
132
|
+
}
|
|
133
|
+
// Discover recordings at module load time
|
|
134
|
+
const discoveredRecordings = discoverRecordings();
|
|
135
|
+
describe('Services Against Recordings', () => {
|
|
136
|
+
if (discoveredRecordings.length === 0) {
|
|
137
|
+
it.skip('No recordings found', () => {
|
|
138
|
+
// Skip if no recordings exist
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
for (const { serviceName, recordingPath } of discoveredRecordings) {
|
|
142
|
+
it(`should extract credentials from ${serviceName} recording`, async () => {
|
|
143
|
+
const service = REGISTRY.getByName(serviceName);
|
|
144
|
+
if (service === null) {
|
|
145
|
+
// Skip if service not found in registry
|
|
146
|
+
expect.fail(`Service '${serviceName}' not found in registry`);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const apiCredentials = await testServiceWithRecording(service, recordingPath);
|
|
150
|
+
// Verify API credentials are valid
|
|
151
|
+
expect(apiCredentials).not.toBeNull();
|
|
152
|
+
const curlArgs = apiCredentials.asCurlArguments();
|
|
153
|
+
expect(curlArgs.length).toBeGreaterThan(0);
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
//# sourceMappingURL=servicesAgainstRecordings.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"servicesAgainstRecordings.test.js","sourceRoot":"","sources":["../../tests/servicesAgainstRecordings.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"typecheck.test.d.ts","sourceRoot":"","sources":["../../tests/typecheck.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { execSync } from 'child_process';
|
|
3
|
+
import { dirname, join } from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
const projectRoot = join(__dirname, '..');
|
|
7
|
+
describe('TypeScript', () => {
|
|
8
|
+
it('should pass type checking', () => {
|
|
9
|
+
expect(() => {
|
|
10
|
+
execSync('npm run typecheck', {
|
|
11
|
+
cwd: projectRoot,
|
|
12
|
+
encoding: 'utf-8',
|
|
13
|
+
stdio: 'pipe',
|
|
14
|
+
});
|
|
15
|
+
}).not.toThrow();
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
//# sourceMappingURL=typecheck.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"typecheck.test.js","sourceRoot":"","sources":["../../tests/typecheck.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,IAAI,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;AAC1D,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;AAE1C,QAAQ,CAAC,YAAY,EAAE,GAAG,EAAE;IAC1B,EAAE,CAAC,2BAA2B,EAAE,GAAG,EAAE;QACnC,MAAM,CAAC,GAAG,EAAE;YACV,QAAQ,CAAC,mBAAmB,EAAE;gBAC5B,GAAG,EAAE,WAAW;gBAChB,QAAQ,EAAE,OAAO;gBACjB,KAAK,EAAE,MAAM;aACd,CAAC,CAAC;QACL,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;IACnB,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|