agent-messenger 2.0.0 → 2.2.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/.claude-plugin/marketplace.json +14 -1
- package/.claude-plugin/plugin.json +4 -2
- package/.env.template +35 -17
- package/README.md +37 -33
- package/bun.lock +6 -6
- package/dist/package.json +11 -3
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +3 -0
- package/dist/src/cli.js.map +1 -1
- package/dist/src/platforms/channeltalk/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/channeltalk/commands/auth.js +35 -28
- package/dist/src/platforms/channeltalk/commands/auth.js.map +1 -1
- package/dist/src/platforms/channeltalk/ensure-auth.js +6 -6
- package/dist/src/platforms/channeltalk/ensure-auth.js.map +1 -1
- package/dist/src/platforms/channeltalk/token-extractor.d.ts +23 -1
- package/dist/src/platforms/channeltalk/token-extractor.d.ts.map +1 -1
- package/dist/src/platforms/channeltalk/token-extractor.js +299 -29
- package/dist/src/platforms/channeltalk/token-extractor.js.map +1 -1
- package/dist/src/platforms/discord/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/discord/commands/auth.js +57 -49
- package/dist/src/platforms/discord/commands/auth.js.map +1 -1
- package/dist/src/platforms/discord/ensure-auth.js +3 -3
- package/dist/src/platforms/discord/ensure-auth.js.map +1 -1
- package/dist/src/platforms/discord/token-extractor.d.ts +6 -1
- package/dist/src/platforms/discord/token-extractor.d.ts.map +1 -1
- package/dist/src/platforms/discord/token-extractor.js +167 -14
- package/dist/src/platforms/discord/token-extractor.js.map +1 -1
- package/dist/src/platforms/instagram/client.d.ts +2 -0
- package/dist/src/platforms/instagram/client.d.ts.map +1 -1
- package/dist/src/platforms/instagram/client.js +2 -2
- package/dist/src/platforms/instagram/client.js.map +1 -1
- package/dist/src/platforms/instagram/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/instagram/commands/auth.js +107 -14
- package/dist/src/platforms/instagram/commands/auth.js.map +1 -1
- package/dist/src/platforms/instagram/ensure-auth.d.ts.map +1 -1
- package/dist/src/platforms/instagram/ensure-auth.js +57 -11
- package/dist/src/platforms/instagram/ensure-auth.js.map +1 -1
- package/dist/src/platforms/instagram/index.d.ts +1 -0
- package/dist/src/platforms/instagram/index.d.ts.map +1 -1
- package/dist/src/platforms/instagram/index.js +1 -0
- package/dist/src/platforms/instagram/index.js.map +1 -1
- package/dist/src/platforms/instagram/token-extractor.d.ts +44 -0
- package/dist/src/platforms/instagram/token-extractor.d.ts.map +1 -0
- package/dist/src/platforms/instagram/token-extractor.js +407 -0
- package/dist/src/platforms/instagram/token-extractor.js.map +1 -0
- package/dist/src/platforms/kakaotalk/client.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/client.js +2 -1
- package/dist/src/platforms/kakaotalk/client.js.map +1 -1
- package/dist/src/platforms/kakaotalk/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/commands/auth.js +14 -13
- package/dist/src/platforms/kakaotalk/commands/auth.js.map +1 -1
- package/dist/src/platforms/kakaotalk/protocol/connection.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/protocol/connection.js +2 -1
- package/dist/src/platforms/kakaotalk/protocol/connection.js.map +1 -1
- package/dist/src/platforms/line/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/line/commands/auth.js +6 -5
- package/dist/src/platforms/line/commands/auth.js.map +1 -1
- package/dist/src/platforms/slack/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/slack/commands/auth.js +11 -10
- package/dist/src/platforms/slack/commands/auth.js.map +1 -1
- package/dist/src/platforms/slack/token-extractor.d.ts +9 -0
- package/dist/src/platforms/slack/token-extractor.d.ts.map +1 -1
- package/dist/src/platforms/slack/token-extractor.js +300 -23
- package/dist/src/platforms/slack/token-extractor.js.map +1 -1
- package/dist/src/platforms/teams/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/teams/commands/auth.js +9 -8
- package/dist/src/platforms/teams/commands/auth.js.map +1 -1
- package/dist/src/platforms/teams/ensure-auth.d.ts.map +1 -1
- package/dist/src/platforms/teams/ensure-auth.js +2 -1
- package/dist/src/platforms/teams/ensure-auth.js.map +1 -1
- package/dist/src/platforms/teams/token-extractor.d.ts +5 -0
- package/dist/src/platforms/teams/token-extractor.d.ts.map +1 -1
- package/dist/src/platforms/teams/token-extractor.js +161 -29
- package/dist/src/platforms/teams/token-extractor.js.map +1 -1
- package/dist/src/platforms/telegram/client.d.ts.map +1 -1
- package/dist/src/platforms/telegram/client.js +25 -7
- package/dist/src/platforms/telegram/client.js.map +1 -1
- package/dist/src/platforms/telegram/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/telegram/commands/auth.js +6 -5
- package/dist/src/platforms/telegram/commands/auth.js.map +1 -1
- package/dist/src/platforms/webex/app-config.d.ts +7 -0
- package/dist/src/platforms/webex/app-config.d.ts.map +1 -0
- package/dist/src/platforms/webex/app-config.js +20 -0
- package/dist/src/platforms/webex/app-config.js.map +1 -0
- package/dist/src/platforms/webex/cli.d.ts +5 -0
- package/dist/src/platforms/webex/cli.d.ts.map +1 -0
- package/dist/src/platforms/webex/cli.js +32 -0
- package/dist/src/platforms/webex/cli.js.map +1 -0
- package/dist/src/platforms/webex/client.d.ts +55 -0
- package/dist/src/platforms/webex/client.d.ts.map +1 -0
- package/dist/src/platforms/webex/client.js +299 -0
- package/dist/src/platforms/webex/client.js.map +1 -0
- package/dist/src/platforms/webex/commands/auth.d.ts +19 -0
- package/dist/src/platforms/webex/commands/auth.d.ts.map +1 -0
- package/dist/src/platforms/webex/commands/auth.js +166 -0
- package/dist/src/platforms/webex/commands/auth.js.map +1 -0
- package/dist/src/platforms/webex/commands/index.d.ts +6 -0
- package/dist/src/platforms/webex/commands/index.d.ts.map +1 -0
- package/dist/src/platforms/webex/commands/index.js +6 -0
- package/dist/src/platforms/webex/commands/index.js.map +1 -0
- package/dist/src/platforms/webex/commands/member.d.ts +7 -0
- package/dist/src/platforms/webex/commands/member.d.ts.map +1 -0
- package/dist/src/platforms/webex/commands/member.js +34 -0
- package/dist/src/platforms/webex/commands/member.js.map +1 -0
- package/dist/src/platforms/webex/commands/message.d.ts +26 -0
- package/dist/src/platforms/webex/commands/message.d.ts.map +1 -0
- package/dist/src/platforms/webex/commands/message.js +153 -0
- package/dist/src/platforms/webex/commands/message.js.map +1 -0
- package/dist/src/platforms/webex/commands/snapshot.d.ts +9 -0
- package/dist/src/platforms/webex/commands/snapshot.d.ts.map +1 -0
- package/dist/src/platforms/webex/commands/snapshot.js +72 -0
- package/dist/src/platforms/webex/commands/snapshot.js.map +1 -0
- package/dist/src/platforms/webex/commands/space.d.ts +11 -0
- package/dist/src/platforms/webex/commands/space.d.ts.map +1 -0
- package/dist/src/platforms/webex/commands/space.js +59 -0
- package/dist/src/platforms/webex/commands/space.js.map +1 -0
- package/dist/src/platforms/webex/credential-manager.d.ts +23 -0
- package/dist/src/platforms/webex/credential-manager.d.ts.map +1 -0
- package/dist/src/platforms/webex/credential-manager.js +148 -0
- package/dist/src/platforms/webex/credential-manager.js.map +1 -0
- package/dist/src/platforms/webex/ensure-auth.d.ts +2 -0
- package/dist/src/platforms/webex/ensure-auth.d.ts.map +1 -0
- package/dist/src/platforms/webex/ensure-auth.js +36 -0
- package/dist/src/platforms/webex/ensure-auth.js.map +1 -0
- package/dist/src/platforms/webex/index.d.ts +8 -0
- package/dist/src/platforms/webex/index.d.ts.map +1 -0
- package/dist/src/platforms/webex/index.js +6 -0
- package/dist/src/platforms/webex/index.js.map +1 -0
- package/dist/src/platforms/webex/token-extractor.d.ts +28 -0
- package/dist/src/platforms/webex/token-extractor.d.ts.map +1 -0
- package/dist/src/platforms/webex/token-extractor.js +344 -0
- package/dist/src/platforms/webex/token-extractor.js.map +1 -0
- package/dist/src/platforms/webex/types.d.ts +127 -0
- package/dist/src/platforms/webex/types.d.ts.map +1 -0
- package/dist/src/platforms/webex/types.js +64 -0
- package/dist/src/platforms/webex/types.js.map +1 -0
- package/dist/src/platforms/whatsapp/client.d.ts.map +1 -1
- package/dist/src/platforms/whatsapp/client.js +6 -2
- package/dist/src/platforms/whatsapp/client.js.map +1 -1
- package/dist/src/shared/utils/derived-key-cache.d.ts +1 -1
- package/dist/src/shared/utils/derived-key-cache.d.ts.map +1 -1
- package/dist/src/shared/utils/error-handler.d.ts +1 -1
- package/dist/src/shared/utils/error-handler.d.ts.map +1 -1
- package/dist/src/shared/utils/error-handler.js +3 -2
- package/dist/src/shared/utils/error-handler.js.map +1 -1
- package/dist/src/shared/utils/stderr.d.ts +5 -0
- package/dist/src/shared/utils/stderr.d.ts.map +1 -0
- package/dist/src/shared/utils/stderr.js +18 -0
- package/dist/src/shared/utils/stderr.js.map +1 -0
- package/dist/src/tui/adapters/webex-adapter.d.ts +14 -0
- package/dist/src/tui/adapters/webex-adapter.d.ts.map +1 -0
- package/dist/src/tui/adapters/webex-adapter.js +79 -0
- package/dist/src/tui/adapters/webex-adapter.js.map +1 -0
- package/dist/src/tui/app.d.ts.map +1 -1
- package/dist/src/tui/app.js +2 -0
- package/dist/src/tui/app.js.map +1 -1
- package/docs/content/docs/cli/channeltalk.mdx +7 -7
- package/docs/content/docs/cli/discord.mdx +3 -3
- package/docs/content/docs/cli/instagram.mdx +28 -6
- package/docs/content/docs/cli/meta.json +1 -0
- package/docs/content/docs/cli/slack.mdx +2 -2
- package/docs/content/docs/cli/teams.mdx +6 -4
- package/docs/content/docs/cli/webex.mdx +310 -0
- package/docs/content/docs/sdk/meta.json +1 -1
- package/docs/content/docs/sdk/webex.mdx +260 -0
- package/docs/content/docs/tui.mdx +4 -3
- package/docs/src/app/page.tsx +2 -2
- package/e2e/README.md +132 -8
- package/e2e/channeltalk.e2e.test.ts +2 -7
- package/e2e/channeltalkbot.e2e.test.ts +2 -6
- package/e2e/config.ts +172 -10
- package/e2e/helpers.ts +7 -0
- package/e2e/instagram.e2e.test.ts +97 -0
- package/e2e/kakaotalk.e2e.test.ts +74 -0
- package/e2e/line.e2e.test.ts +92 -0
- package/e2e/teams.e2e.test.ts +46 -1
- package/e2e/telegram.e2e.test.ts +84 -0
- package/e2e/webex.e2e.test.ts +190 -0
- package/e2e/whatsapp.e2e.test.ts +90 -0
- package/e2e/whatsappbot.e2e.test.ts +78 -0
- package/package.json +11 -3
- package/skills/agent-channeltalk/SKILL.md +9 -9
- package/skills/agent-channeltalk/references/authentication.md +21 -18
- package/skills/agent-channeltalkbot/SKILL.md +1 -1
- package/skills/agent-discord/SKILL.md +5 -5
- package/skills/agent-discord/references/authentication.md +8 -8
- package/skills/agent-discordbot/SKILL.md +1 -1
- package/skills/agent-instagram/SKILL.md +51 -9
- package/skills/agent-instagram/references/authentication.md +35 -3
- package/skills/agent-kakaotalk/SKILL.md +1 -1
- package/skills/agent-line/SKILL.md +1 -1
- package/skills/agent-slack/SKILL.md +5 -5
- package/skills/agent-slack/references/authentication.md +8 -8
- package/skills/agent-slackbot/SKILL.md +1 -1
- package/skills/agent-teams/SKILL.md +6 -6
- package/skills/agent-teams/references/authentication.md +8 -8
- package/skills/agent-telegram/SKILL.md +1 -1
- package/skills/agent-webex/SKILL.md +406 -0
- package/skills/agent-webex/references/authentication.md +371 -0
- package/skills/agent-webex/references/common-patterns.md +726 -0
- package/skills/agent-webex/templates/monitor-space.sh +165 -0
- package/skills/agent-webex/templates/post-message.sh +170 -0
- package/skills/agent-whatsapp/SKILL.md +1 -1
- package/skills/agent-whatsappbot/SKILL.md +1 -1
- package/src/cli.ts +4 -0
- package/src/platforms/channeltalk/commands/auth.test.ts +5 -5
- package/src/platforms/channeltalk/commands/auth.ts +38 -32
- package/src/platforms/channeltalk/ensure-auth.test.ts +6 -6
- package/src/platforms/channeltalk/ensure-auth.ts +6 -6
- package/src/platforms/channeltalk/token-extractor.test.ts +182 -15
- package/src/platforms/channeltalk/token-extractor.ts +344 -30
- package/src/platforms/discord/commands/auth.test.ts +3 -3
- package/src/platforms/discord/commands/auth.ts +58 -54
- package/src/platforms/discord/ensure-auth.test.ts +3 -3
- package/src/platforms/discord/ensure-auth.ts +3 -3
- package/src/platforms/discord/token-extractor.test.ts +199 -27
- package/src/platforms/discord/token-extractor.ts +190 -17
- package/src/platforms/instagram/client.ts +2 -2
- package/src/platforms/instagram/commands/auth.ts +133 -14
- package/src/platforms/instagram/ensure-auth.ts +63 -12
- package/src/platforms/instagram/index.ts +1 -0
- package/src/platforms/instagram/token-extractor.test.ts +424 -0
- package/src/platforms/instagram/token-extractor.ts +478 -0
- package/src/platforms/kakaotalk/client.ts +3 -1
- package/src/platforms/kakaotalk/commands/auth.ts +14 -13
- package/src/platforms/kakaotalk/protocol/connection.ts +3 -1
- package/src/platforms/line/commands/auth.ts +7 -6
- package/src/platforms/slack/cli.test.ts +6 -5
- package/src/platforms/slack/commands/auth.test.ts +11 -7
- package/src/platforms/slack/commands/auth.ts +11 -10
- package/src/platforms/slack/token-extractor.test.ts +98 -1
- package/src/platforms/slack/token-extractor.ts +338 -26
- package/src/platforms/teams/commands/auth.ts +9 -8
- package/src/platforms/teams/ensure-auth.ts +3 -1
- package/src/platforms/teams/token-extractor.test.ts +136 -17
- package/src/platforms/teams/token-extractor.ts +182 -31
- package/src/platforms/telegram/client.test.ts +134 -0
- package/src/platforms/telegram/client.ts +27 -6
- package/src/platforms/telegram/commands/auth.ts +6 -5
- package/src/platforms/webex/app-config.test.ts +98 -0
- package/src/platforms/webex/app-config.ts +31 -0
- package/src/platforms/webex/cli.test.ts +58 -0
- package/src/platforms/webex/cli.ts +39 -0
- package/src/platforms/webex/client.test.ts +743 -0
- package/src/platforms/webex/client.ts +405 -0
- package/src/platforms/webex/commands/auth.test.ts +222 -0
- package/src/platforms/webex/commands/auth.ts +243 -0
- package/src/platforms/webex/commands/index.ts +5 -0
- package/src/platforms/webex/commands/member.test.ts +112 -0
- package/src/platforms/webex/commands/member.ts +45 -0
- package/src/platforms/webex/commands/message.test.ts +235 -0
- package/src/platforms/webex/commands/message.ts +204 -0
- package/src/platforms/webex/commands/snapshot.test.ts +105 -0
- package/src/platforms/webex/commands/snapshot.ts +91 -0
- package/src/platforms/webex/commands/space.test.ts +216 -0
- package/src/platforms/webex/commands/space.ts +74 -0
- package/src/platforms/webex/credential-manager.test.ts +314 -0
- package/src/platforms/webex/credential-manager.ts +197 -0
- package/src/platforms/webex/ensure-auth.test.ts +89 -0
- package/src/platforms/webex/ensure-auth.ts +38 -0
- package/src/platforms/webex/index.test.ts +25 -0
- package/src/platforms/webex/index.ts +19 -0
- package/src/platforms/webex/token-extractor.test.ts +327 -0
- package/src/platforms/webex/token-extractor.ts +393 -0
- package/src/platforms/webex/types.test.ts +307 -0
- package/src/platforms/webex/types.ts +129 -0
- package/src/platforms/whatsapp/client.ts +11 -7
- package/src/shared/utils/derived-key-cache.ts +1 -1
- package/src/shared/utils/error-handler.ts +4 -2
- package/src/shared/utils/stderr.ts +22 -0
- package/src/tui/adapters/webex-adapter.ts +103 -0
- package/src/tui/app.ts +2 -0
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
import {
|
|
2
|
+
copyFileSync,
|
|
3
|
+
existsSync,
|
|
4
|
+
mkdirSync,
|
|
5
|
+
readdirSync,
|
|
6
|
+
readFileSync,
|
|
7
|
+
rmSync,
|
|
8
|
+
statSync,
|
|
9
|
+
} from 'node:fs'
|
|
10
|
+
import { homedir, tmpdir } from 'node:os'
|
|
11
|
+
import { join } from 'node:path'
|
|
12
|
+
|
|
13
|
+
import { ClassicLevel } from 'classic-level'
|
|
14
|
+
|
|
15
|
+
export interface ExtractedWebexToken {
|
|
16
|
+
accessToken: string
|
|
17
|
+
refreshToken?: string
|
|
18
|
+
expiresAt?: number
|
|
19
|
+
deviceUrl?: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface BrowserConfig {
|
|
23
|
+
name: string
|
|
24
|
+
darwin: string
|
|
25
|
+
linux: string
|
|
26
|
+
win32: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const BROWSERS: BrowserConfig[] = [
|
|
30
|
+
{
|
|
31
|
+
name: 'Chrome',
|
|
32
|
+
darwin: join('Google', 'Chrome'),
|
|
33
|
+
linux: 'google-chrome',
|
|
34
|
+
win32: join('Google', 'Chrome', 'User Data'),
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: 'Chrome Canary',
|
|
38
|
+
darwin: join('Google', 'Chrome Canary'),
|
|
39
|
+
linux: 'google-chrome-unstable',
|
|
40
|
+
win32: join('Google', 'Chrome SxS', 'User Data'),
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: 'Edge',
|
|
44
|
+
darwin: join('Microsoft Edge'),
|
|
45
|
+
linux: 'microsoft-edge',
|
|
46
|
+
win32: join('Microsoft', 'Edge', 'User Data'),
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: 'Arc',
|
|
50
|
+
darwin: join('Arc', 'User Data'),
|
|
51
|
+
linux: '',
|
|
52
|
+
win32: join('Arc', 'User Data'),
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: 'Brave',
|
|
56
|
+
darwin: join('BraveSoftware', 'Brave-Browser'),
|
|
57
|
+
linux: join('BraveSoftware', 'Brave-Browser'),
|
|
58
|
+
win32: join('BraveSoftware', 'Brave-Browser', 'User Data'),
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: 'Vivaldi',
|
|
62
|
+
darwin: 'Vivaldi',
|
|
63
|
+
linux: 'vivaldi',
|
|
64
|
+
win32: join('Vivaldi', 'User Data'),
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: 'Chromium',
|
|
68
|
+
darwin: 'Chromium',
|
|
69
|
+
linux: 'chromium',
|
|
70
|
+
win32: join('Chromium', 'User Data'),
|
|
71
|
+
},
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
const WEBEX_STORAGE_KEY = '_https://web.webex.com\x00\x01webex-web-client-bounded'
|
|
75
|
+
|
|
76
|
+
export class WebexTokenExtractor {
|
|
77
|
+
private platform: NodeJS.Platform
|
|
78
|
+
private baseDir: string | null
|
|
79
|
+
private debugLog: ((message: string) => void) | null
|
|
80
|
+
|
|
81
|
+
constructor(platform?: NodeJS.Platform, debugLog?: (message: string) => void, baseDir?: string) {
|
|
82
|
+
this.platform = platform ?? process.platform
|
|
83
|
+
this.debugLog = debugLog ?? null
|
|
84
|
+
this.baseDir = baseDir ?? null
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private debug(message: string): void {
|
|
88
|
+
this.debugLog?.(message)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
getBrowserProfileDirs(): string[] {
|
|
92
|
+
if (this.baseDir) {
|
|
93
|
+
return this.discoverProfileDirs(this.baseDir)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const dirs: string[] = []
|
|
97
|
+
|
|
98
|
+
for (const browser of BROWSERS) {
|
|
99
|
+
const browserBase = this.getBrowserBasePath(browser)
|
|
100
|
+
if (!browserBase) continue
|
|
101
|
+
|
|
102
|
+
const profileDirs = this.discoverProfileDirs(browserBase)
|
|
103
|
+
dirs.push(...profileDirs)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return dirs
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private getBrowserBasePath(browser: BrowserConfig): string | null {
|
|
110
|
+
let relative: string
|
|
111
|
+
|
|
112
|
+
switch (this.platform) {
|
|
113
|
+
case 'darwin':
|
|
114
|
+
relative = browser.darwin
|
|
115
|
+
if (!relative) return null
|
|
116
|
+
return join(homedir(), 'Library', 'Application Support', relative)
|
|
117
|
+
case 'linux':
|
|
118
|
+
relative = browser.linux
|
|
119
|
+
if (!relative) return null
|
|
120
|
+
return join(homedir(), '.config', relative)
|
|
121
|
+
case 'win32':
|
|
122
|
+
relative = browser.win32
|
|
123
|
+
if (!relative) return null
|
|
124
|
+
return join(
|
|
125
|
+
process.env.LOCALAPPDATA || join(homedir(), 'AppData', 'Local'),
|
|
126
|
+
relative,
|
|
127
|
+
)
|
|
128
|
+
default:
|
|
129
|
+
return null
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private discoverProfileDirs(browserBase: string): string[] {
|
|
134
|
+
const dirs: string[] = []
|
|
135
|
+
|
|
136
|
+
if (!existsSync(browserBase)) return dirs
|
|
137
|
+
|
|
138
|
+
const defaultLeveldb = join(browserBase, 'Default', 'Local Storage', 'leveldb')
|
|
139
|
+
if (existsSync(defaultLeveldb)) {
|
|
140
|
+
dirs.push(defaultLeveldb)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
const entries = readdirSync(browserBase, { withFileTypes: true })
|
|
145
|
+
for (const entry of entries) {
|
|
146
|
+
if (!entry.isDirectory()) continue
|
|
147
|
+
if (!/^Profile \d+$/i.test(entry.name)) continue
|
|
148
|
+
|
|
149
|
+
const leveldb = join(browserBase, entry.name, 'Local Storage', 'leveldb')
|
|
150
|
+
if (existsSync(leveldb)) {
|
|
151
|
+
dirs.push(leveldb)
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
} catch {
|
|
155
|
+
// Ignore read errors
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return dirs
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async extract(): Promise<ExtractedWebexToken | null> {
|
|
162
|
+
const profileDirs = this.getBrowserProfileDirs()
|
|
163
|
+
|
|
164
|
+
if (profileDirs.length === 0) {
|
|
165
|
+
this.debug('No browser profile directories found')
|
|
166
|
+
return null
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
for (const leveldbDir of profileDirs) {
|
|
170
|
+
this.debug(`Scanning: ${leveldbDir}`)
|
|
171
|
+
|
|
172
|
+
const token = await this.extractViaClassicLevelCopy(leveldbDir)
|
|
173
|
+
?? this.extractFromRawFiles(leveldbDir)
|
|
174
|
+
|
|
175
|
+
if (token) {
|
|
176
|
+
this.debug(`Found token in: ${leveldbDir}`)
|
|
177
|
+
return token
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
this.debug('No Webex tokens found in any browser profile')
|
|
182
|
+
return null
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
private async extractViaClassicLevelCopy(dbPath: string): Promise<ExtractedWebexToken | null> {
|
|
186
|
+
const tempDir = join(tmpdir(), `webex-leveldb-${Date.now()}-${Math.random().toString(36).slice(2)}`)
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
mkdirSync(tempDir, { recursive: true })
|
|
190
|
+
|
|
191
|
+
const files = readdirSync(dbPath)
|
|
192
|
+
for (const file of files) {
|
|
193
|
+
if (file === 'LOCK') continue
|
|
194
|
+
const src = join(dbPath, file)
|
|
195
|
+
try {
|
|
196
|
+
if (statSync(src).isFile()) {
|
|
197
|
+
copyFileSync(src, join(tempDir, file))
|
|
198
|
+
}
|
|
199
|
+
} catch {}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return await this.extractViaClassicLevel(tempDir)
|
|
203
|
+
} catch {
|
|
204
|
+
return null
|
|
205
|
+
} finally {
|
|
206
|
+
try {
|
|
207
|
+
rmSync(tempDir, { recursive: true, force: true })
|
|
208
|
+
} catch {}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
private async extractViaClassicLevel(dbPath: string): Promise<ExtractedWebexToken | null> {
|
|
213
|
+
let db: ClassicLevel<string, Buffer> | null = null
|
|
214
|
+
try {
|
|
215
|
+
db = new ClassicLevel(dbPath, { keyEncoding: 'utf8', valueEncoding: 'buffer' })
|
|
216
|
+
|
|
217
|
+
for await (const [key, value] of db.iterator()) {
|
|
218
|
+
if (!key.includes('web.webex.com')) continue
|
|
219
|
+
|
|
220
|
+
const decoded = this.decodeLevelDBValue(value)
|
|
221
|
+
if (!decoded.includes('"supertoken"') && !decoded.includes('"Credentials"')) continue
|
|
222
|
+
|
|
223
|
+
const token = this.extractTokenFromString(decoded)
|
|
224
|
+
if (token) return token
|
|
225
|
+
}
|
|
226
|
+
} catch (e) {
|
|
227
|
+
this.debug(`ClassicLevel failed: ${e instanceof Error ? e.message : String(e)}`)
|
|
228
|
+
} finally {
|
|
229
|
+
if (db) {
|
|
230
|
+
try {
|
|
231
|
+
await db.close()
|
|
232
|
+
} catch {}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return null
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
private decodeLevelDBValue(buf: Buffer): string {
|
|
239
|
+
if (buf.length < 2) return buf.toString('utf8')
|
|
240
|
+
// Chromium localStorage: 0x00 prefix = UTF-16LE, 0x01 prefix = Latin1/UTF-8
|
|
241
|
+
if (buf[0] === 0x00 && (buf.length - 1) % 2 === 0) {
|
|
242
|
+
return buf.subarray(1).toString('utf16le')
|
|
243
|
+
}
|
|
244
|
+
if (buf[0] === 0x01) {
|
|
245
|
+
return buf.subarray(1).toString('utf8')
|
|
246
|
+
}
|
|
247
|
+
return buf.toString('utf8')
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
private extractFromRawFiles(leveldbDir: string): ExtractedWebexToken | null {
|
|
251
|
+
try {
|
|
252
|
+
const files = readdirSync(leveldbDir)
|
|
253
|
+
|
|
254
|
+
const sorted = [...files]
|
|
255
|
+
.filter((f) => f.endsWith('.log') || f.endsWith('.ldb'))
|
|
256
|
+
.sort((a, b) => {
|
|
257
|
+
const aIsLog = a.endsWith('.log') ? 0 : 1
|
|
258
|
+
const bIsLog = b.endsWith('.log') ? 0 : 1
|
|
259
|
+
if (aIsLog !== bIsLog) return aIsLog - bIsLog
|
|
260
|
+
try {
|
|
261
|
+
return statSync(join(leveldbDir, b)).mtimeMs - statSync(join(leveldbDir, a)).mtimeMs
|
|
262
|
+
} catch {
|
|
263
|
+
return 0
|
|
264
|
+
}
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
for (const file of sorted) {
|
|
268
|
+
const token = this.extractFromFile(join(leveldbDir, file))
|
|
269
|
+
if (token) return token
|
|
270
|
+
}
|
|
271
|
+
} catch {
|
|
272
|
+
this.debug(`Failed to read directory: ${leveldbDir}`)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return null
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
private extractFromFile(filePath: string): ExtractedWebexToken | null {
|
|
279
|
+
try {
|
|
280
|
+
const stat = statSync(filePath)
|
|
281
|
+
if (stat.size > 50 * 1024 * 1024) return null
|
|
282
|
+
|
|
283
|
+
const buffer = readFileSync(filePath)
|
|
284
|
+
return this.extractTokenFromBuffer(buffer)
|
|
285
|
+
} catch {
|
|
286
|
+
return null
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
private extractTokenFromBuffer(buffer: Buffer): ExtractedWebexToken | null {
|
|
291
|
+
// Try UTF-8 first, then strip null bytes for Chromium's UTF-16LE localStorage encoding
|
|
292
|
+
return this.extractTokenFromString(buffer.toString('utf8'))
|
|
293
|
+
?? this.extractTokenFromString(this.stripNullBytes(buffer))
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
private stripNullBytes(buffer: Buffer): string {
|
|
297
|
+
const bytes: number[] = []
|
|
298
|
+
for (let i = 0; i < buffer.length; i++) {
|
|
299
|
+
if (buffer[i] !== 0) bytes.push(buffer[i]!)
|
|
300
|
+
}
|
|
301
|
+
return Buffer.from(bytes).toString('utf8')
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
private extractTokenFromString(content: string): ExtractedWebexToken | null {
|
|
305
|
+
const outerObjectMarkerIdx = content.indexOf('"Credentials"')
|
|
306
|
+
const innermostMarkerIdx = content.indexOf('"supertoken"')
|
|
307
|
+
|
|
308
|
+
const markerIdx =
|
|
309
|
+
outerObjectMarkerIdx !== -1 ? outerObjectMarkerIdx
|
|
310
|
+
: innermostMarkerIdx !== -1 ? innermostMarkerIdx
|
|
311
|
+
: -1
|
|
312
|
+
|
|
313
|
+
if (markerIdx === -1) return null
|
|
314
|
+
|
|
315
|
+
const json = this.extractJsonAroundIndex(content, markerIdx)
|
|
316
|
+
if (!json) return null
|
|
317
|
+
|
|
318
|
+
return this.parseWebexStorage(json)
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
private extractJsonAroundIndex(content: string, markerIdx: number): string | null {
|
|
322
|
+
let depth = 0
|
|
323
|
+
let start = -1
|
|
324
|
+
for (let i = markerIdx; i >= 0; i--) {
|
|
325
|
+
if (content[i] === '}') depth++
|
|
326
|
+
if (content[i] === '{') {
|
|
327
|
+
if (depth === 0) {
|
|
328
|
+
start = i
|
|
329
|
+
break
|
|
330
|
+
}
|
|
331
|
+
depth--
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
if (start === -1) return null
|
|
335
|
+
|
|
336
|
+
depth = 0
|
|
337
|
+
let end = -1
|
|
338
|
+
for (let i = start; i < content.length; i++) {
|
|
339
|
+
if (content[i] === '{') depth++
|
|
340
|
+
if (content[i] === '}') {
|
|
341
|
+
depth--
|
|
342
|
+
if (depth === 0) {
|
|
343
|
+
end = i + 1
|
|
344
|
+
break
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
if (end === -1) return null
|
|
349
|
+
|
|
350
|
+
return content.substring(start, end)
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
parseWebexStorage(jsonStr: string): ExtractedWebexToken | null {
|
|
354
|
+
try {
|
|
355
|
+
const data = JSON.parse(jsonStr)
|
|
356
|
+
|
|
357
|
+
const supertoken =
|
|
358
|
+
data?.Credentials?.['@']?.supertoken ??
|
|
359
|
+
data?.['@']?.supertoken ??
|
|
360
|
+
data?.supertoken ??
|
|
361
|
+
null
|
|
362
|
+
|
|
363
|
+
if (!supertoken?.access_token) return null
|
|
364
|
+
|
|
365
|
+
const accessToken = String(supertoken.access_token)
|
|
366
|
+
if (accessToken.length < 20) return null
|
|
367
|
+
|
|
368
|
+
const result: ExtractedWebexToken = { accessToken }
|
|
369
|
+
|
|
370
|
+
if (supertoken.refresh_token) {
|
|
371
|
+
result.refreshToken = String(supertoken.refresh_token)
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
if (typeof supertoken.expires === 'number') {
|
|
375
|
+
result.expiresAt = supertoken.expires
|
|
376
|
+
} else if (typeof supertoken.expires_in === 'number') {
|
|
377
|
+
result.expiresAt = Date.now() + supertoken.expires_in * 1000
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const deviceUrl =
|
|
381
|
+
data?.Device?.['@']?.url ??
|
|
382
|
+
data?.['@']?.deviceUrl ??
|
|
383
|
+
null
|
|
384
|
+
if (deviceUrl && typeof deviceUrl === 'string') {
|
|
385
|
+
result.deviceUrl = deviceUrl
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
return result
|
|
389
|
+
} catch {
|
|
390
|
+
return null
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import { expect, test } from 'bun:test'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
WebexConfigSchema,
|
|
5
|
+
WebexError,
|
|
6
|
+
WebexMembershipSchema,
|
|
7
|
+
WebexMessageSchema,
|
|
8
|
+
WebexPersonSchema,
|
|
9
|
+
WebexSpaceSchema,
|
|
10
|
+
} from './types'
|
|
11
|
+
|
|
12
|
+
test('WebexSpaceSchema validates valid space', () => {
|
|
13
|
+
const result = WebexSpaceSchema.safeParse({
|
|
14
|
+
id: 'Y2lzY29zcGFyazovL3VzL1JPT00vYWJj',
|
|
15
|
+
title: 'Project Alpha',
|
|
16
|
+
type: 'group',
|
|
17
|
+
isLocked: false,
|
|
18
|
+
lastActivity: '2024-01-15T10:30:00.000Z',
|
|
19
|
+
created: '2024-01-01T00:00:00.000Z',
|
|
20
|
+
creatorId: 'Y2lzY29zcGFyazovL3VzL1BFT1BMRS9hYmM',
|
|
21
|
+
})
|
|
22
|
+
expect(result.success).toBe(true)
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
test('WebexSpaceSchema validates space with optional teamId', () => {
|
|
26
|
+
const result = WebexSpaceSchema.safeParse({
|
|
27
|
+
id: 'Y2lzY29zcGFyazovL3VzL1JPT00vYWJj',
|
|
28
|
+
title: 'Project Alpha',
|
|
29
|
+
type: 'group',
|
|
30
|
+
isLocked: true,
|
|
31
|
+
teamId: 'Y2lzY29zcGFyazovL3VzL1RFQU0vdGVhbQ',
|
|
32
|
+
lastActivity: '2024-01-15T10:30:00.000Z',
|
|
33
|
+
created: '2024-01-01T00:00:00.000Z',
|
|
34
|
+
creatorId: 'Y2lzY29zcGFyazovL3VzL1BFT1BMRS9hYmM',
|
|
35
|
+
})
|
|
36
|
+
expect(result.success).toBe(true)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
test('WebexSpaceSchema validates direct space type', () => {
|
|
40
|
+
const result = WebexSpaceSchema.safeParse({
|
|
41
|
+
id: 'Y2lzY29zcGFyazovL3VzL1JPT00vZGlyZWN0',
|
|
42
|
+
title: 'Direct Message',
|
|
43
|
+
type: 'direct',
|
|
44
|
+
isLocked: false,
|
|
45
|
+
lastActivity: '2024-01-15T10:30:00.000Z',
|
|
46
|
+
created: '2024-01-01T00:00:00.000Z',
|
|
47
|
+
creatorId: 'Y2lzY29zcGFyazovL3VzL1BFT1BMRS9hYmM',
|
|
48
|
+
})
|
|
49
|
+
expect(result.success).toBe(true)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
test('WebexSpaceSchema rejects missing required fields', () => {
|
|
53
|
+
const result = WebexSpaceSchema.safeParse({
|
|
54
|
+
id: 'Y2lzY29zcGFyazovL3VzL1JPT00vYWJj',
|
|
55
|
+
title: 'Project Alpha',
|
|
56
|
+
})
|
|
57
|
+
expect(result.success).toBe(false)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
test('WebexSpaceSchema rejects invalid type', () => {
|
|
61
|
+
const result = WebexSpaceSchema.safeParse({
|
|
62
|
+
id: 'Y2lzY29zcGFyazovL3VzL1JPT00vYWJj',
|
|
63
|
+
title: 'Project Alpha',
|
|
64
|
+
type: 'channel',
|
|
65
|
+
isLocked: false,
|
|
66
|
+
lastActivity: '2024-01-15T10:30:00.000Z',
|
|
67
|
+
created: '2024-01-01T00:00:00.000Z',
|
|
68
|
+
creatorId: 'Y2lzY29zcGFyazovL3VzL1BFT1BMRS9hYmM',
|
|
69
|
+
})
|
|
70
|
+
expect(result.success).toBe(false)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
test('WebexMessageSchema validates valid message', () => {
|
|
74
|
+
const result = WebexMessageSchema.safeParse({
|
|
75
|
+
id: 'Y2lzY29zcGFyazovL3VzL01FU1NBR0UvbXNn',
|
|
76
|
+
roomId: 'Y2lzY29zcGFyazovL3VzL1JPT00vYWJj',
|
|
77
|
+
roomType: 'group',
|
|
78
|
+
text: 'Hello world',
|
|
79
|
+
personId: 'Y2lzY29zcGFyazovL3VzL1BFT1BMRS9hYmM',
|
|
80
|
+
personEmail: 'user@example.com',
|
|
81
|
+
created: '2024-01-15T10:30:00.000Z',
|
|
82
|
+
})
|
|
83
|
+
expect(result.success).toBe(true)
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
test('WebexMessageSchema validates message with optional fields', () => {
|
|
87
|
+
const result = WebexMessageSchema.safeParse({
|
|
88
|
+
id: 'Y2lzY29zcGFyazovL3VzL01FU1NBR0UvbXNn',
|
|
89
|
+
roomId: 'Y2lzY29zcGFyazovL3VzL1JPT00vYWJj',
|
|
90
|
+
roomType: 'group',
|
|
91
|
+
text: 'Hello world',
|
|
92
|
+
markdown: '**Hello world**',
|
|
93
|
+
html: '<strong>Hello world</strong>',
|
|
94
|
+
files: ['https://webexapis.com/v1/contents/file1'],
|
|
95
|
+
personId: 'Y2lzY29zcGFyazovL3VzL1BFT1BMRS9hYmM',
|
|
96
|
+
personEmail: 'user@example.com',
|
|
97
|
+
created: '2024-01-15T10:30:00.000Z',
|
|
98
|
+
parentId: 'Y2lzY29zcGFyazovL3VzL01FU1NBR0UvcGFyZW50',
|
|
99
|
+
mentionedPeople: ['Y2lzY29zcGFyazovL3VzL1BFT1BMRS9tZW50aW9u'],
|
|
100
|
+
})
|
|
101
|
+
expect(result.success).toBe(true)
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
test('WebexMessageSchema rejects missing required fields', () => {
|
|
105
|
+
const result = WebexMessageSchema.safeParse({
|
|
106
|
+
id: 'Y2lzY29zcGFyazovL3VzL01FU1NBR0UvbXNn',
|
|
107
|
+
roomId: 'Y2lzY29zcGFyazovL3VzL1JPT00vYWJj',
|
|
108
|
+
text: 'Hello world',
|
|
109
|
+
})
|
|
110
|
+
expect(result.success).toBe(false)
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
test('WebexMessageSchema rejects invalid roomType', () => {
|
|
114
|
+
const result = WebexMessageSchema.safeParse({
|
|
115
|
+
id: 'Y2lzY29zcGFyazovL3VzL01FU1NBR0UvbXNn',
|
|
116
|
+
roomId: 'Y2lzY29zcGFyazovL3VzL1JPT00vYWJj',
|
|
117
|
+
roomType: 'team',
|
|
118
|
+
personId: 'Y2lzY29zcGFyazovL3VzL1BFT1BMRS9hYmM',
|
|
119
|
+
personEmail: 'user@example.com',
|
|
120
|
+
created: '2024-01-15T10:30:00.000Z',
|
|
121
|
+
})
|
|
122
|
+
expect(result.success).toBe(false)
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
test('WebexPersonSchema validates valid person', () => {
|
|
126
|
+
const result = WebexPersonSchema.safeParse({
|
|
127
|
+
id: 'Y2lzY29zcGFyazovL3VzL1BFT1BMRS9hYmM',
|
|
128
|
+
emails: ['user@example.com'],
|
|
129
|
+
displayName: 'Test User',
|
|
130
|
+
orgId: 'Y2lzY29zcGFyazovL3VzL09SR0FOSVpBVElPTi9vcmc',
|
|
131
|
+
type: 'person',
|
|
132
|
+
created: '2024-01-01T00:00:00.000Z',
|
|
133
|
+
})
|
|
134
|
+
expect(result.success).toBe(true)
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
test('WebexPersonSchema validates person with optional fields', () => {
|
|
138
|
+
const result = WebexPersonSchema.safeParse({
|
|
139
|
+
id: 'Y2lzY29zcGFyazovL3VzL1BFT1BMRS9hYmM',
|
|
140
|
+
emails: ['user@example.com', 'user@work.com'],
|
|
141
|
+
displayName: 'Test User',
|
|
142
|
+
nickName: 'Tester',
|
|
143
|
+
firstName: 'Test',
|
|
144
|
+
lastName: 'User',
|
|
145
|
+
avatar: 'https://example.com/avatar.jpg',
|
|
146
|
+
orgId: 'Y2lzY29zcGFyazovL3VzL09SR0FOSVpBVElPTi9vcmc',
|
|
147
|
+
type: 'person',
|
|
148
|
+
created: '2024-01-01T00:00:00.000Z',
|
|
149
|
+
})
|
|
150
|
+
expect(result.success).toBe(true)
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
test('WebexPersonSchema validates bot type', () => {
|
|
154
|
+
const result = WebexPersonSchema.safeParse({
|
|
155
|
+
id: 'Y2lzY29zcGFyazovL3VzL1BFT1BMRS9ib3Q',
|
|
156
|
+
emails: ['bot@webex.bot'],
|
|
157
|
+
displayName: 'My Bot',
|
|
158
|
+
orgId: 'Y2lzY29zcGFyazovL3VzL09SR0FOSVpBVElPTi9vcmc',
|
|
159
|
+
type: 'bot',
|
|
160
|
+
created: '2024-01-01T00:00:00.000Z',
|
|
161
|
+
})
|
|
162
|
+
expect(result.success).toBe(true)
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
test('WebexPersonSchema rejects missing required fields', () => {
|
|
166
|
+
const result = WebexPersonSchema.safeParse({
|
|
167
|
+
id: 'Y2lzY29zcGFyazovL3VzL1BFT1BMRS9hYmM',
|
|
168
|
+
displayName: 'Test User',
|
|
169
|
+
})
|
|
170
|
+
expect(result.success).toBe(false)
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
test('WebexPersonSchema rejects invalid type', () => {
|
|
174
|
+
const result = WebexPersonSchema.safeParse({
|
|
175
|
+
id: 'Y2lzY29zcGFyazovL3VzL1BFT1BMRS9hYmM',
|
|
176
|
+
emails: ['user@example.com'],
|
|
177
|
+
displayName: 'Test User',
|
|
178
|
+
orgId: 'Y2lzY29zcGFyazovL3VzL09SR0FOSVpBVElPTi9vcmc',
|
|
179
|
+
type: 'admin',
|
|
180
|
+
created: '2024-01-01T00:00:00.000Z',
|
|
181
|
+
})
|
|
182
|
+
expect(result.success).toBe(false)
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
test('WebexMembershipSchema validates valid membership', () => {
|
|
186
|
+
const result = WebexMembershipSchema.safeParse({
|
|
187
|
+
id: 'Y2lzY29zcGFyazovL3VzL01FTUJFUlNISVAvbWVt',
|
|
188
|
+
roomId: 'Y2lzY29zcGFyazovL3VzL1JPT00vYWJj',
|
|
189
|
+
personId: 'Y2lzY29zcGFyazovL3VzL1BFT1BMRS9hYmM',
|
|
190
|
+
personEmail: 'user@example.com',
|
|
191
|
+
personDisplayName: 'Test User',
|
|
192
|
+
isModerator: false,
|
|
193
|
+
created: '2024-01-01T00:00:00.000Z',
|
|
194
|
+
})
|
|
195
|
+
expect(result.success).toBe(true)
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
test('WebexMembershipSchema validates moderator membership', () => {
|
|
199
|
+
const result = WebexMembershipSchema.safeParse({
|
|
200
|
+
id: 'Y2lzY29zcGFyazovL3VzL01FTUJFUlNISVAvbWVt',
|
|
201
|
+
roomId: 'Y2lzY29zcGFyazovL3VzL1JPT00vYWJj',
|
|
202
|
+
personId: 'Y2lzY29zcGFyazovL3VzL1BFT1BMRS9hYmM',
|
|
203
|
+
personEmail: 'moderator@example.com',
|
|
204
|
+
personDisplayName: 'Moderator User',
|
|
205
|
+
isModerator: true,
|
|
206
|
+
created: '2024-01-01T00:00:00.000Z',
|
|
207
|
+
})
|
|
208
|
+
expect(result.success).toBe(true)
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
test('WebexMembershipSchema rejects missing required fields', () => {
|
|
212
|
+
const result = WebexMembershipSchema.safeParse({
|
|
213
|
+
id: 'Y2lzY29zcGFyazovL3VzL01FTUJFUlNISVAvbWVt',
|
|
214
|
+
roomId: 'Y2lzY29zcGFyazovL3VzL1JPT00vYWJj',
|
|
215
|
+
})
|
|
216
|
+
expect(result.success).toBe(false)
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
test('WebexConfigSchema validates valid OAuth config', () => {
|
|
220
|
+
const result = WebexConfigSchema.safeParse({
|
|
221
|
+
accessToken: 'test',
|
|
222
|
+
refreshToken: 'test',
|
|
223
|
+
expiresAt: 1234567890,
|
|
224
|
+
})
|
|
225
|
+
expect(result.success).toBe(true)
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
test('WebexConfigSchema validates config with clientId and clientSecret', () => {
|
|
229
|
+
const result = WebexConfigSchema.safeParse({
|
|
230
|
+
accessToken: 'test',
|
|
231
|
+
refreshToken: 'test',
|
|
232
|
+
expiresAt: 1234567890,
|
|
233
|
+
clientId: 'C123abc',
|
|
234
|
+
clientSecret: 'secret456',
|
|
235
|
+
})
|
|
236
|
+
expect(result.success).toBe(true)
|
|
237
|
+
if (result.success) {
|
|
238
|
+
expect(result.data.clientId).toBe('C123abc')
|
|
239
|
+
expect(result.data.clientSecret).toBe('secret456')
|
|
240
|
+
}
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
test('WebexConfigSchema validates config with tokenType oauth', () => {
|
|
244
|
+
const result = WebexConfigSchema.safeParse({
|
|
245
|
+
accessToken: 'test',
|
|
246
|
+
refreshToken: 'test',
|
|
247
|
+
expiresAt: 1234567890,
|
|
248
|
+
tokenType: 'oauth',
|
|
249
|
+
})
|
|
250
|
+
expect(result.success).toBe(true)
|
|
251
|
+
if (result.success) {
|
|
252
|
+
expect(result.data.tokenType).toBe('oauth')
|
|
253
|
+
}
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
test('WebexConfigSchema validates config with tokenType manual', () => {
|
|
257
|
+
const result = WebexConfigSchema.safeParse({
|
|
258
|
+
accessToken: 'test',
|
|
259
|
+
refreshToken: '',
|
|
260
|
+
expiresAt: 0,
|
|
261
|
+
tokenType: 'manual',
|
|
262
|
+
})
|
|
263
|
+
expect(result.success).toBe(true)
|
|
264
|
+
if (result.success) {
|
|
265
|
+
expect(result.data.tokenType).toBe('manual')
|
|
266
|
+
}
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
test('WebexConfigSchema rejects invalid tokenType', () => {
|
|
270
|
+
const result = WebexConfigSchema.safeParse({
|
|
271
|
+
accessToken: 'test',
|
|
272
|
+
refreshToken: 'test',
|
|
273
|
+
expiresAt: 1234567890,
|
|
274
|
+
tokenType: 'invalid',
|
|
275
|
+
})
|
|
276
|
+
expect(result.success).toBe(false)
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
test('WebexConfigSchema accepts config without clientId/clientSecret (backward compat)', () => {
|
|
280
|
+
const result = WebexConfigSchema.safeParse({
|
|
281
|
+
accessToken: 'test',
|
|
282
|
+
refreshToken: 'test',
|
|
283
|
+
expiresAt: 1234567890,
|
|
284
|
+
})
|
|
285
|
+
expect(result.success).toBe(true)
|
|
286
|
+
if (result.success) {
|
|
287
|
+
expect(result.data.clientId).toBeUndefined()
|
|
288
|
+
expect(result.data.clientSecret).toBeUndefined()
|
|
289
|
+
}
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
test('WebexConfigSchema rejects missing fields', () => {
|
|
293
|
+
const result = WebexConfigSchema.safeParse({})
|
|
294
|
+
expect(result.success).toBe(false)
|
|
295
|
+
})
|
|
296
|
+
|
|
297
|
+
test('WebexError has correct name and code', () => {
|
|
298
|
+
const error = new WebexError('Not found', 'NOT_FOUND')
|
|
299
|
+
expect(error.name).toBe('WebexError')
|
|
300
|
+
expect(error.message).toBe('Not found')
|
|
301
|
+
expect(error.code).toBe('NOT_FOUND')
|
|
302
|
+
})
|
|
303
|
+
|
|
304
|
+
test('WebexError is instance of Error', () => {
|
|
305
|
+
const error = new WebexError('Unauthorized', 'UNAUTHORIZED')
|
|
306
|
+
expect(error instanceof Error).toBe(true)
|
|
307
|
+
})
|