agent-messenger 2.12.1 → 2.13.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/plugin.json +1 -1
- package/dist/package.json +1 -1
- package/dist/src/platforms/kakaotalk/chat-classifier.d.ts +18 -0
- package/dist/src/platforms/kakaotalk/chat-classifier.d.ts.map +1 -0
- package/dist/src/platforms/kakaotalk/chat-classifier.js +29 -0
- package/dist/src/platforms/kakaotalk/chat-classifier.js.map +1 -0
- package/dist/src/platforms/kakaotalk/cli.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/cli.js +2 -1
- package/dist/src/platforms/kakaotalk/cli.js.map +1 -1
- package/dist/src/platforms/kakaotalk/client.d.ts +35 -1
- package/dist/src/platforms/kakaotalk/client.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/client.js +318 -15
- package/dist/src/platforms/kakaotalk/client.js.map +1 -1
- package/dist/src/platforms/kakaotalk/commands/chat.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/commands/chat.js +2 -1
- package/dist/src/platforms/kakaotalk/commands/chat.js.map +1 -1
- package/dist/src/platforms/kakaotalk/commands/index.d.ts +1 -0
- package/dist/src/platforms/kakaotalk/commands/index.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/commands/index.js +1 -0
- package/dist/src/platforms/kakaotalk/commands/index.js.map +1 -1
- package/dist/src/platforms/kakaotalk/commands/member.d.ts +3 -0
- package/dist/src/platforms/kakaotalk/commands/member.d.ts.map +1 -0
- package/dist/src/platforms/kakaotalk/commands/member.js +22 -0
- package/dist/src/platforms/kakaotalk/commands/member.js.map +1 -0
- package/dist/src/platforms/kakaotalk/index.d.ts +4 -2
- package/dist/src/platforms/kakaotalk/index.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/index.js +2 -1
- package/dist/src/platforms/kakaotalk/index.js.map +1 -1
- package/dist/src/platforms/kakaotalk/listener.d.ts +4 -7
- package/dist/src/platforms/kakaotalk/listener.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/listener.js +48 -74
- package/dist/src/platforms/kakaotalk/listener.js.map +1 -1
- package/dist/src/platforms/kakaotalk/protocol/session.d.ts +28 -0
- package/dist/src/platforms/kakaotalk/protocol/session.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/protocol/session.js +44 -0
- package/dist/src/platforms/kakaotalk/protocol/session.js.map +1 -1
- package/dist/src/platforms/kakaotalk/types.d.ts +37 -0
- package/dist/src/platforms/kakaotalk/types.d.ts.map +1 -1
- package/dist/src/platforms/kakaotalk/types.js +17 -0
- package/dist/src/platforms/kakaotalk/types.js.map +1 -1
- package/dist/src/platforms/slackbot/client.d.ts +5 -0
- package/dist/src/platforms/slackbot/client.d.ts.map +1 -1
- package/dist/src/platforms/slackbot/client.js +5 -0
- package/dist/src/platforms/slackbot/client.js.map +1 -1
- package/dist/src/tui/adapters/kakaotalk-adapter.js +3 -3
- package/dist/src/tui/adapters/kakaotalk-adapter.js.map +1 -1
- package/docs/content/docs/cli/kakaotalk.mdx +26 -1
- package/docs/content/docs/sdk/kakaotalk.mdx +45 -13
- package/docs/content/docs/sdk/slackbot.mdx +11 -0
- package/package.json +1 -1
- package/scripts/kakao-loco-capture.ts +466 -0
- package/skills/agent-channeltalk/SKILL.md +1 -1
- package/skills/agent-channeltalkbot/SKILL.md +1 -1
- package/skills/agent-discord/SKILL.md +1 -1
- package/skills/agent-discordbot/SKILL.md +1 -1
- package/skills/agent-instagram/SKILL.md +1 -1
- package/skills/agent-kakaotalk/SKILL.md +30 -3
- package/skills/agent-kakaotalk/references/common-patterns.md +49 -1
- package/skills/agent-line/SKILL.md +1 -1
- package/skills/agent-slack/SKILL.md +1 -1
- package/skills/agent-slackbot/SKILL.md +1 -2
- package/skills/agent-teams/SKILL.md +1 -1
- package/skills/agent-telegram/SKILL.md +1 -1
- package/skills/agent-telegrambot/SKILL.md +1 -1
- package/skills/agent-webex/SKILL.md +1 -1
- package/skills/agent-wechatbot/SKILL.md +1 -1
- package/skills/agent-whatsapp/SKILL.md +1 -1
- package/skills/agent-whatsappbot/SKILL.md +1 -1
- package/src/platforms/kakaotalk/chat-classifier.test.ts +33 -0
- package/src/platforms/kakaotalk/chat-classifier.ts +31 -0
- package/src/platforms/kakaotalk/cli.ts +2 -1
- package/src/platforms/kakaotalk/client-listener-integration.test.ts +411 -0
- package/src/platforms/kakaotalk/client.test.ts +785 -1
- package/src/platforms/kakaotalk/client.ts +369 -18
- package/src/platforms/kakaotalk/commands/chat.ts +3 -1
- package/src/platforms/kakaotalk/commands/index.ts +1 -0
- package/src/platforms/kakaotalk/commands/member.test.ts +102 -0
- package/src/platforms/kakaotalk/commands/member.ts +32 -0
- package/src/platforms/kakaotalk/index.test.ts +5 -0
- package/src/platforms/kakaotalk/index.ts +4 -0
- package/src/platforms/kakaotalk/listener.test.ts +184 -149
- package/src/platforms/kakaotalk/listener.ts +51 -82
- package/src/platforms/kakaotalk/protocol/session.ts +44 -0
- package/src/platforms/kakaotalk/types.ts +39 -0
- package/src/platforms/slackbot/client.test.ts +67 -0
- package/src/platforms/slackbot/client.ts +17 -1
- package/src/tui/adapters/kakaotalk-adapter.ts +3 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-messenger",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.13.0",
|
|
4
4
|
"description": "Messaging platform interaction skills for AI agents. Interact with Slack, Discord, Microsoft Teams, Webex, Telegram, Telegram Bot, WhatsApp, LINE, Instagram, KakaoTalk, and Channel Talk - send messages, read channels, manage reactions, upload files, and more through simple CLI interfaces.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "agent-messenger",
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-messenger",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.13.0",
|
|
4
4
|
"description": "Multi-platform messaging CLI for AI agents (Slack, Discord, Teams, Webex, Telegram, Telegram Bot, WhatsApp, LINE, Instagram, KakaoTalk, Channel Talk)",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { KakaoChat } from './types.js';
|
|
2
|
+
export type KakaoChatKind = 'dm' | 'group' | 'open' | 'unknown';
|
|
3
|
+
/**
|
|
4
|
+
* Classify a KakaoTalk chat as `'dm'`, `'group'`, `'open'`, or `'unknown'`.
|
|
5
|
+
*
|
|
6
|
+
* REGRESSION GUARD: An earlier implementation hard-coded `0=dm`, `1=group`,
|
|
7
|
+
* `2=open` on the raw `type` number. Modern KakaoTalk uses codes like `11`
|
|
8
|
+
* for normal DMs and `10` for normal groups, so the old mapping silently
|
|
9
|
+
* classified every real DM as `'unknown'` and bucketed it as a group. Do
|
|
10
|
+
* NOT "simplify" this back to a pure type-code mapping without verifying
|
|
11
|
+
* against a real KakaoTalk session.
|
|
12
|
+
*
|
|
13
|
+
* `'unknown'` is reserved for future protocol drift; the current heuristic
|
|
14
|
+
* never returns it, but it is part of the union so consumers can handle
|
|
15
|
+
* the case defensively.
|
|
16
|
+
*/
|
|
17
|
+
export declare function classifyKakaoChat(chat: Pick<KakaoChat, 'type' | 'active_members'>): KakaoChatKind;
|
|
18
|
+
//# sourceMappingURL=chat-classifier.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-classifier.d.ts","sourceRoot":"","sources":["../../../../src/platforms/kakaotalk/chat-classifier.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAExC,MAAM,MAAM,aAAa,GAAG,IAAI,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAAA;AAQ/D;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,gBAAgB,CAAC,GAAG,aAAa,CAMjG"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// OpenChat-family `type` codes observed on the wire. KakaoTalk's LOCO
|
|
2
|
+
// protocol exposes a numeric `type` field with no documented mapping; these
|
|
3
|
+
// five codes consistently identify OpenChat rooms across normal OpenChat,
|
|
4
|
+
// OpenChat DMs, and the various OpenChat sub-types seen in production.
|
|
5
|
+
const OPEN_CHAT_TYPE_CODES = new Set([2, 13, 14, 15, 16]);
|
|
6
|
+
/**
|
|
7
|
+
* Classify a KakaoTalk chat as `'dm'`, `'group'`, `'open'`, or `'unknown'`.
|
|
8
|
+
*
|
|
9
|
+
* REGRESSION GUARD: An earlier implementation hard-coded `0=dm`, `1=group`,
|
|
10
|
+
* `2=open` on the raw `type` number. Modern KakaoTalk uses codes like `11`
|
|
11
|
+
* for normal DMs and `10` for normal groups, so the old mapping silently
|
|
12
|
+
* classified every real DM as `'unknown'` and bucketed it as a group. Do
|
|
13
|
+
* NOT "simplify" this back to a pure type-code mapping without verifying
|
|
14
|
+
* against a real KakaoTalk session.
|
|
15
|
+
*
|
|
16
|
+
* `'unknown'` is reserved for future protocol drift; the current heuristic
|
|
17
|
+
* never returns it, but it is part of the union so consumers can handle
|
|
18
|
+
* the case defensively.
|
|
19
|
+
*/
|
|
20
|
+
export function classifyKakaoChat(chat) {
|
|
21
|
+
if (OPEN_CHAT_TYPE_CODES.has(chat.type))
|
|
22
|
+
return 'open';
|
|
23
|
+
// active_members counts the logged-in user, so a 1:1 DM is exactly 2
|
|
24
|
+
// (self + one other) and a "lone" room with only self is 1.
|
|
25
|
+
if (chat.active_members <= 2)
|
|
26
|
+
return 'dm';
|
|
27
|
+
return 'group';
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=chat-classifier.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-classifier.js","sourceRoot":"","sources":["../../../../src/platforms/kakaotalk/chat-classifier.ts"],"names":[],"mappings":"AAIA,sEAAsE;AACtE,4EAA4E;AAC5E,0EAA0E;AAC1E,uEAAuE;AACvE,MAAM,oBAAoB,GAAwB,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,CAAA;AAE9E;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAgD;IAChF,IAAI,oBAAoB,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,MAAM,CAAA;IACtD,qEAAqE;IACrE,4DAA4D;IAC5D,IAAI,IAAI,CAAC,cAAc,IAAI,CAAC;QAAE,OAAO,IAAI,CAAA;IACzC,OAAO,OAAO,CAAA;AAChB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../../../src/platforms/kakaotalk/cli.ts"],"names":[],"mappings":";AAEA,OAAO,KAAK,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,WAAW,CAAA;AAgBvD,QAAA,MAAM,OAAO,aAAgB,CAAA;
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../../../src/platforms/kakaotalk/cli.ts"],"names":[],"mappings":";AAEA,OAAO,KAAK,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,WAAW,CAAA;AAgBvD,QAAA,MAAM,OAAO,aAAgB,CAAA;AAoB7B,eAAe,OAAO,CAAA"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
import { Command } from 'commander';
|
|
3
3
|
import pkg from '../../../package.json' with { type: 'json' };
|
|
4
|
-
import { authCommand, chatCommand, messageCommand, whoamiCommand } from './commands/index.js';
|
|
4
|
+
import { authCommand, chatCommand, memberCommand, messageCommand, whoamiCommand } from './commands/index.js';
|
|
5
5
|
import { ensureKakaoAuth } from './ensure-auth.js';
|
|
6
6
|
function isAuthCommand(command) {
|
|
7
7
|
let cmd = command;
|
|
@@ -24,6 +24,7 @@ program.hook('preAction', async (_thisCommand, actionCommand) => {
|
|
|
24
24
|
});
|
|
25
25
|
program.addCommand(authCommand);
|
|
26
26
|
program.addCommand(chatCommand);
|
|
27
|
+
program.addCommand(memberCommand);
|
|
27
28
|
program.addCommand(messageCommand);
|
|
28
29
|
program.addCommand(whoamiCommand);
|
|
29
30
|
program.parse(process.argv);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../../../src/platforms/kakaotalk/cli.ts"],"names":[],"mappings":";AAGA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAEnC,OAAO,GAAG,MAAM,uBAAuB,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAA;AAC7D,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../../../src/platforms/kakaotalk/cli.ts"],"names":[],"mappings":";AAGA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAEnC,OAAO,GAAG,MAAM,uBAAuB,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAA;AAC7D,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,aAAa,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AACzG,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAE/C,SAAS,aAAa,CAAC,OAAoB;IACzC,IAAI,GAAG,GAAuB,OAAO,CAAA;IACrC,OAAO,GAAG,EAAE,CAAC;QACX,IAAI,GAAG,CAAC,IAAI,EAAE,KAAK,MAAM;YAAE,OAAO,IAAI,CAAA;QACtC,GAAG,GAAG,GAAG,CAAC,MAAM,CAAA;IAClB,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAA;AAE7B,OAAO;KACJ,IAAI,CAAC,iBAAiB,CAAC;KACvB,WAAW,CAAC,8EAA8E,CAAC;KAC3F,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;AAEvB,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,EAAE;IAC9D,IAAI,aAAa,CAAC,aAAa,CAAC;QAAE,OAAM;IACxC,MAAM,eAAe,EAAE,CAAA;AACzB,CAAC,CAAC,CAAA;AAEF,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC,CAAA;AAC/B,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC,CAAA;AAC/B,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,CAAA;AACjC,OAAO,CAAC,UAAU,CAAC,cAAc,CAAC,CAAA;AAClC,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,CAAA;AAEjC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;AAE3B,eAAe,OAAO,CAAA"}
|
|
@@ -1,4 +1,17 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { LocoSession } from './protocol/session.js';
|
|
2
|
+
import type { LocoPacket } from './protocol/types.js';
|
|
3
|
+
import type { KakaoChat, KakaoDeviceType, KakaoMember, KakaoMessage, KakaoProfile, KakaoSendResult } from './types.js';
|
|
4
|
+
export type KakaoSessionEvent = {
|
|
5
|
+
type: 'connected';
|
|
6
|
+
userId: string;
|
|
7
|
+
} | {
|
|
8
|
+
type: 'disconnected';
|
|
9
|
+
} | {
|
|
10
|
+
type: 'kicked';
|
|
11
|
+
reason: string;
|
|
12
|
+
};
|
|
13
|
+
export type KakaoPushHandler = (packet: LocoPacket) => void;
|
|
14
|
+
export type KakaoSessionEventHandler = (event: KakaoSessionEvent) => void;
|
|
2
15
|
export declare class KakaoTalkError extends Error {
|
|
3
16
|
code: string;
|
|
4
17
|
constructor(message: string, code: string, options?: {
|
|
@@ -13,6 +26,9 @@ export declare class KakaoTalkClient {
|
|
|
13
26
|
private state;
|
|
14
27
|
private initPromise;
|
|
15
28
|
private closed;
|
|
29
|
+
private pushHandlers;
|
|
30
|
+
private sessionEventHandlers;
|
|
31
|
+
private nameCache;
|
|
16
32
|
login(credentials?: {
|
|
17
33
|
oauthToken: string;
|
|
18
34
|
userId: string;
|
|
@@ -27,18 +43,36 @@ export declare class KakaoTalkClient {
|
|
|
27
43
|
};
|
|
28
44
|
private ensureAuth;
|
|
29
45
|
private ensureSession;
|
|
46
|
+
acquireSession(): Promise<LocoSession>;
|
|
47
|
+
onPush(handler: KakaoPushHandler): () => void;
|
|
48
|
+
onSessionEvent(handler: KakaoSessionEventHandler): () => void;
|
|
49
|
+
isConnected(): boolean;
|
|
30
50
|
private executeWithReconnect;
|
|
31
51
|
private connect;
|
|
52
|
+
private dispatchPush;
|
|
53
|
+
private invalidateSession;
|
|
54
|
+
private emitSessionEvent;
|
|
32
55
|
getChats(options?: {
|
|
33
56
|
all?: boolean;
|
|
34
57
|
search?: string;
|
|
58
|
+
resolveTitles?: boolean;
|
|
35
59
|
}): Promise<KakaoChat[]>;
|
|
60
|
+
/**
|
|
61
|
+
* Resolve the user-set room title via CHATINFO. Returns null on any error
|
|
62
|
+
* (network, malformed response, or no TITLE meta present). Designed to be
|
|
63
|
+
* fire-and-forget per chat — failures don't poison the whole `getChats` call.
|
|
64
|
+
*/
|
|
65
|
+
getChatTitle(chatId: string): Promise<string | null>;
|
|
66
|
+
private fetchChatTitle;
|
|
36
67
|
getMessages(chatId: string, options?: {
|
|
37
68
|
count?: number;
|
|
38
69
|
from?: string;
|
|
39
70
|
}): Promise<KakaoMessage[]>;
|
|
71
|
+
getMembers(chatId: string): Promise<KakaoMember[]>;
|
|
72
|
+
getMembersByIds(chatId: string, userIds: string[]): Promise<KakaoMember[]>;
|
|
40
73
|
sendMessage(chatId: string, text: string): Promise<KakaoSendResult>;
|
|
41
74
|
getProfile(): Promise<KakaoProfile>;
|
|
42
75
|
close(): void;
|
|
76
|
+
lookupAuthorName(chatId: string, authorId: number): string | null;
|
|
43
77
|
}
|
|
44
78
|
//# sourceMappingURL=client.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../../src/platforms/kakaotalk/client.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../../src/platforms/kakaotalk/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -14,6 +14,53 @@ export class KakaoTalkError extends Error {
|
|
|
14
14
|
this.code = code;
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
|
+
class MemberNameCache {
|
|
18
|
+
byChatId = new Map();
|
|
19
|
+
ingest(chatDatas) {
|
|
20
|
+
for (const chat of chatDatas) {
|
|
21
|
+
const ids = chat.i;
|
|
22
|
+
const names = chat.k;
|
|
23
|
+
if (!Array.isArray(ids) || !Array.isArray(names))
|
|
24
|
+
continue;
|
|
25
|
+
const chatId = String(chat.c);
|
|
26
|
+
let map = this.byChatId.get(chatId);
|
|
27
|
+
if (!map) {
|
|
28
|
+
map = new Map();
|
|
29
|
+
this.byChatId.set(chatId, map);
|
|
30
|
+
}
|
|
31
|
+
const len = Math.min(ids.length, names.length);
|
|
32
|
+
for (let i = 0; i < len; i++) {
|
|
33
|
+
const numericId = toNumericUserId(ids[i]);
|
|
34
|
+
if (numericId === null)
|
|
35
|
+
continue;
|
|
36
|
+
const name = names[i];
|
|
37
|
+
if (typeof name === 'string' && name.length > 0) {
|
|
38
|
+
map.set(numericId, name);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
lookup(chatId, userId) {
|
|
44
|
+
return this.byChatId.get(chatId)?.get(userId) ?? null;
|
|
45
|
+
}
|
|
46
|
+
forget(chatId) {
|
|
47
|
+
this.byChatId.delete(chatId);
|
|
48
|
+
}
|
|
49
|
+
clear() {
|
|
50
|
+
this.byChatId.clear();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function toNumericUserId(v) {
|
|
54
|
+
if (typeof v === 'number')
|
|
55
|
+
return Number.isFinite(v) ? v : null;
|
|
56
|
+
if (v && typeof v === 'object' && 'low' in v && 'high' in v) {
|
|
57
|
+
const { low, high } = v;
|
|
58
|
+
// chatDatas[].i entries are member user IDs. KakaoTalk user IDs fit in
|
|
59
|
+
// 53 bits — safe to flatten the BSON Long pair to a JS number for keying.
|
|
60
|
+
return (high >>> 0) * 0x100000000 + (low >>> 0);
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
17
64
|
function bsonToLong(v) {
|
|
18
65
|
if (v && typeof v === 'object' && 'high' in v && 'low' in v) {
|
|
19
66
|
const { high, low } = v;
|
|
@@ -34,25 +81,68 @@ function parseLong(s) {
|
|
|
34
81
|
const high = Number((big >> 32n) & 0xffffffffn);
|
|
35
82
|
return new Long(low, high);
|
|
36
83
|
}
|
|
37
|
-
function
|
|
84
|
+
function parseChatId(chatId) {
|
|
85
|
+
try {
|
|
86
|
+
return parseLong(chatId);
|
|
87
|
+
}
|
|
88
|
+
catch (cause) {
|
|
89
|
+
throw new KakaoTalkError(`Invalid chatId: ${chatId}`, 'invalid_chat_id', { cause });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function parseUserId(userId) {
|
|
93
|
+
try {
|
|
94
|
+
return parseLong(userId);
|
|
95
|
+
}
|
|
96
|
+
catch (cause) {
|
|
97
|
+
throw new KakaoTalkError(`Invalid userId: ${userId}`, 'invalid_user_id', { cause });
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function formatChat(chat, title, nameCache) {
|
|
38
101
|
const memberNames = (chat.k ?? []);
|
|
39
102
|
const lastLog = chat.l;
|
|
40
103
|
const displayName = memberNames.join(', ') || null;
|
|
104
|
+
const chatId = String(chat.c);
|
|
41
105
|
return {
|
|
42
|
-
chat_id:
|
|
106
|
+
chat_id: chatId,
|
|
43
107
|
type: chat.t,
|
|
44
108
|
display_name: displayName,
|
|
109
|
+
title,
|
|
45
110
|
active_members: chat.a,
|
|
46
111
|
unread_count: chat.n,
|
|
47
112
|
last_message: lastLog
|
|
48
113
|
? {
|
|
49
114
|
author_id: lastLog.authorId,
|
|
115
|
+
author_name: nameCache.lookup(chatId, lastLog.authorId),
|
|
50
116
|
message: lastLog.message,
|
|
51
117
|
sent_at: lastLog.sendAt,
|
|
52
118
|
}
|
|
53
119
|
: null,
|
|
54
120
|
};
|
|
55
121
|
}
|
|
122
|
+
const META_TYPE_TITLE = 3;
|
|
123
|
+
function extractTitle(body) {
|
|
124
|
+
const info = body.chatInfo;
|
|
125
|
+
const metas = info?.chatMetas;
|
|
126
|
+
if (!Array.isArray(metas))
|
|
127
|
+
return null;
|
|
128
|
+
const titleMeta = metas.find((m) => m?.type === META_TYPE_TITLE);
|
|
129
|
+
const content = titleMeta?.content;
|
|
130
|
+
return typeof content === 'string' && content.length > 0 ? content : null;
|
|
131
|
+
}
|
|
132
|
+
function extractOpenLinkName(body) {
|
|
133
|
+
const ols = body.ols;
|
|
134
|
+
if (!Array.isArray(ols) || ols.length === 0)
|
|
135
|
+
return null;
|
|
136
|
+
const ln = ols[0]?.ln;
|
|
137
|
+
return typeof ln === 'string' && ln.length > 0 ? ln : null;
|
|
138
|
+
}
|
|
139
|
+
const OPEN_CHAT_TYPES = new Set(['OM', 'OD']);
|
|
140
|
+
function isOpenChat(chat) {
|
|
141
|
+
return typeof chat.t === 'string' && OPEN_CHAT_TYPES.has(chat.t);
|
|
142
|
+
}
|
|
143
|
+
function getOpenLinkId(chat) {
|
|
144
|
+
return bsonToLong(chat.li) ?? null;
|
|
145
|
+
}
|
|
56
146
|
function matchesSearch(chat, term) {
|
|
57
147
|
const names = (chat.k ?? []);
|
|
58
148
|
const lower = term.toLowerCase();
|
|
@@ -174,12 +264,56 @@ function mergeSyncState(previous, loginResult) {
|
|
|
174
264
|
}
|
|
175
265
|
return next;
|
|
176
266
|
}
|
|
177
|
-
function
|
|
267
|
+
function nullableString(v) {
|
|
268
|
+
return typeof v === 'string' && v.length > 0 ? v : null;
|
|
269
|
+
}
|
|
270
|
+
function nullableNumber(v) {
|
|
271
|
+
return typeof v === 'number' && Number.isFinite(v) ? v : null;
|
|
272
|
+
}
|
|
273
|
+
function isNonZeroLong(v) {
|
|
274
|
+
if (typeof v === 'number')
|
|
275
|
+
return v !== 0;
|
|
276
|
+
if (v && typeof v === 'object' && 'low' in v && 'high' in v) {
|
|
277
|
+
const { low, high } = v;
|
|
278
|
+
return low !== 0 || high !== 0;
|
|
279
|
+
}
|
|
280
|
+
return v !== undefined && v !== null;
|
|
281
|
+
}
|
|
282
|
+
// Reject synthetic LocoConnection close packets ({ statusCode: -1, body.error: 'connection closed' })
|
|
283
|
+
// and explicit body-level failures. Required for any SDK method whose response body has no
|
|
284
|
+
// caller-visible error channel (e.g. GETMEM/MEMBER return `[]` for both empty rooms and dead
|
|
285
|
+
// sockets). Throwing here lets executeWithReconnect detect session death and reconnect.
|
|
286
|
+
function assertLocoOk(response, command) {
|
|
287
|
+
if (response.statusCode !== 0) {
|
|
288
|
+
throw new Error(`${command} failed: statusCode=${response.statusCode}`);
|
|
289
|
+
}
|
|
290
|
+
const bodyStatus = response.body.status;
|
|
291
|
+
if (typeof bodyStatus === 'number' && bodyStatus !== 0) {
|
|
292
|
+
throw new Error(`${command} failed: body.status=${bodyStatus}`);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
function formatMember(member) {
|
|
296
|
+
return {
|
|
297
|
+
user_id: longToString(member.userId),
|
|
298
|
+
nickname: typeof member.nickName === 'string' ? member.nickName : '',
|
|
299
|
+
profile_image_url: nullableString(member.profileImageUrl ?? member.pi),
|
|
300
|
+
full_profile_image_url: nullableString(member.fullProfileImageUrl ?? member.fpi),
|
|
301
|
+
original_profile_image_url: nullableString(member.originalProfileImageUrl ?? member.opi),
|
|
302
|
+
status_message: nullableString(member.statusMessage),
|
|
303
|
+
country_iso: nullableString(member.countryIso),
|
|
304
|
+
user_type: nullableNumber(member.type),
|
|
305
|
+
open_token: nullableNumber(member.opt),
|
|
306
|
+
open_profile_link_id: isNonZeroLong(member.pli) ? longToString(member.pli) : null,
|
|
307
|
+
open_permission: nullableNumber(member.mt),
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
function formatMessages(logs, count, chatId, nameCache) {
|
|
178
311
|
logs.sort((a, b) => a.sendAt - b.sendAt);
|
|
179
312
|
return logs.slice(-count).map((log) => ({
|
|
180
313
|
log_id: longToString(log.logId),
|
|
181
314
|
type: log.type,
|
|
182
315
|
author_id: log.authorId,
|
|
316
|
+
author_name: nameCache.lookup(chatId, log.authorId),
|
|
183
317
|
message: log.message,
|
|
184
318
|
sent_at: log.sendAt,
|
|
185
319
|
}));
|
|
@@ -192,6 +326,9 @@ export class KakaoTalkClient {
|
|
|
192
326
|
state = null;
|
|
193
327
|
initPromise = null;
|
|
194
328
|
closed = false;
|
|
329
|
+
pushHandlers = new Set();
|
|
330
|
+
sessionEventHandlers = new Set();
|
|
331
|
+
nameCache = new MemberNameCache();
|
|
195
332
|
async login(credentials, accountId) {
|
|
196
333
|
if (credentials) {
|
|
197
334
|
if (!credentials.oauthToken)
|
|
@@ -234,6 +371,7 @@ export class KakaoTalkClient {
|
|
|
234
371
|
if (this.state)
|
|
235
372
|
return this.state;
|
|
236
373
|
// Guard against concurrent init — reuse the in-flight promise
|
|
374
|
+
const isOwner = !this.initPromise;
|
|
237
375
|
if (!this.initPromise) {
|
|
238
376
|
this.initPromise = this.connect();
|
|
239
377
|
}
|
|
@@ -244,7 +382,11 @@ export class KakaoTalkClient {
|
|
|
244
382
|
state.session.close();
|
|
245
383
|
throw new KakaoTalkError('Client is closed', 'client_closed');
|
|
246
384
|
}
|
|
385
|
+
const wasNew = this.state !== state;
|
|
247
386
|
this.state = state;
|
|
387
|
+
if (isOwner && wasNew) {
|
|
388
|
+
this.emitSessionEvent({ type: 'connected', userId: this.userId });
|
|
389
|
+
}
|
|
248
390
|
return state;
|
|
249
391
|
}
|
|
250
392
|
catch (error) {
|
|
@@ -254,6 +396,25 @@ export class KakaoTalkClient {
|
|
|
254
396
|
throw error;
|
|
255
397
|
}
|
|
256
398
|
}
|
|
399
|
+
async acquireSession() {
|
|
400
|
+
const state = await this.ensureSession();
|
|
401
|
+
return state.session;
|
|
402
|
+
}
|
|
403
|
+
onPush(handler) {
|
|
404
|
+
this.pushHandlers.add(handler);
|
|
405
|
+
return () => {
|
|
406
|
+
this.pushHandlers.delete(handler);
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
onSessionEvent(handler) {
|
|
410
|
+
this.sessionEventHandlers.add(handler);
|
|
411
|
+
return () => {
|
|
412
|
+
this.sessionEventHandlers.delete(handler);
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
isConnected() {
|
|
416
|
+
return this.state !== null && !this.closed;
|
|
417
|
+
}
|
|
257
418
|
async executeWithReconnect(operation) {
|
|
258
419
|
let state = await this.ensureSession();
|
|
259
420
|
try {
|
|
@@ -269,24 +430,30 @@ export class KakaoTalkClient {
|
|
|
269
430
|
state.session.close();
|
|
270
431
|
}
|
|
271
432
|
catch { }
|
|
272
|
-
|
|
433
|
+
// initPromise is intentionally NOT cleared here: a concurrent caller may already
|
|
434
|
+
// be awaiting an in-flight replacement, and starting a parallel one would send a
|
|
435
|
+
// second LOGINLIST with the same duuid — re-introducing the very self-eviction
|
|
436
|
+
// this layer prevents. Lifecycle paths (onClose / invalidateSession) own that field.
|
|
273
437
|
state = await this.ensureSession();
|
|
274
438
|
return operation(state);
|
|
275
439
|
}
|
|
276
440
|
}
|
|
277
441
|
async connect() {
|
|
278
442
|
const session = new LocoSession();
|
|
443
|
+
session.onPush((packet) => this.dispatchPush(session, packet));
|
|
444
|
+
session.onClose(() => {
|
|
445
|
+
if (this.state?.session === session) {
|
|
446
|
+
this.state = null;
|
|
447
|
+
this.initPromise = null;
|
|
448
|
+
this.emitSessionEvent({ type: 'disconnected' });
|
|
449
|
+
}
|
|
450
|
+
});
|
|
279
451
|
try {
|
|
280
452
|
const syncState = await loadSyncState(this.deviceUuid);
|
|
281
453
|
const loginResult = await session.login(this.oauthToken, this.userId, this.deviceUuid, syncState, this.deviceType);
|
|
282
454
|
const newSyncState = mergeSyncState(syncState, loginResult);
|
|
283
455
|
await saveSyncState(this.deviceUuid, newSyncState);
|
|
284
|
-
|
|
285
|
-
if (this.state?.session === session) {
|
|
286
|
-
this.state = null;
|
|
287
|
-
this.initPromise = null;
|
|
288
|
-
}
|
|
289
|
-
});
|
|
456
|
+
this.nameCache.ingest((loginResult.chatDatas ?? []));
|
|
290
457
|
return { session, loginResult };
|
|
291
458
|
}
|
|
292
459
|
catch (error) {
|
|
@@ -294,6 +461,59 @@ export class KakaoTalkClient {
|
|
|
294
461
|
throw new KakaoTalkError(error instanceof Error ? error.message : String(error), 'login_failed', { cause: error });
|
|
295
462
|
}
|
|
296
463
|
}
|
|
464
|
+
dispatchPush(session, packet) {
|
|
465
|
+
// Only fan out pushes from the currently adopted session. While state is null
|
|
466
|
+
// (pre-adoption during connect, or post-invalidation during reconnect) the
|
|
467
|
+
// packet is discarded — we never want a not-yet-adopted or already-dead session
|
|
468
|
+
// to reach subscribers and look "live".
|
|
469
|
+
if (this.state?.session !== session)
|
|
470
|
+
return;
|
|
471
|
+
if (packet.method === 'KICKOUT') {
|
|
472
|
+
this.emitSessionEvent({ type: 'kicked', reason: 'Session kicked — another device logged in' });
|
|
473
|
+
this.invalidateSession(session);
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
if (packet.method === 'CHANGESVR') {
|
|
477
|
+
for (const handler of this.pushHandlers) {
|
|
478
|
+
try {
|
|
479
|
+
handler(packet);
|
|
480
|
+
}
|
|
481
|
+
catch { }
|
|
482
|
+
}
|
|
483
|
+
this.invalidateSession(session);
|
|
484
|
+
this.emitSessionEvent({ type: 'disconnected' });
|
|
485
|
+
this.ensureSession().catch(() => {
|
|
486
|
+
// ensureSession already cleared state on failure; subsequent API calls will retry
|
|
487
|
+
// and surface the error. Listeners do not receive 'connected' until a reconnect
|
|
488
|
+
// succeeds, which is the correct outcome.
|
|
489
|
+
});
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
for (const handler of this.pushHandlers) {
|
|
493
|
+
try {
|
|
494
|
+
handler(packet);
|
|
495
|
+
}
|
|
496
|
+
catch { }
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
invalidateSession(session) {
|
|
500
|
+
if (this.state?.session === session) {
|
|
501
|
+
this.state = null;
|
|
502
|
+
this.initPromise = null;
|
|
503
|
+
}
|
|
504
|
+
try {
|
|
505
|
+
session.close();
|
|
506
|
+
}
|
|
507
|
+
catch { }
|
|
508
|
+
}
|
|
509
|
+
emitSessionEvent(event) {
|
|
510
|
+
for (const handler of this.sessionEventHandlers) {
|
|
511
|
+
try {
|
|
512
|
+
handler(event);
|
|
513
|
+
}
|
|
514
|
+
catch { }
|
|
515
|
+
}
|
|
516
|
+
}
|
|
297
517
|
async getChats(options) {
|
|
298
518
|
return this.executeWithReconnect(async ({ session, loginResult }) => {
|
|
299
519
|
try {
|
|
@@ -324,6 +544,7 @@ export class KakaoTalkClient {
|
|
|
324
544
|
if (chatDatas.length === 0)
|
|
325
545
|
break;
|
|
326
546
|
collectChats(chatDatas, allChats, seenChatIds);
|
|
547
|
+
this.nameCache.ingest(chatDatas);
|
|
327
548
|
cursor = body;
|
|
328
549
|
pages++;
|
|
329
550
|
}
|
|
@@ -333,13 +554,58 @@ export class KakaoTalkClient {
|
|
|
333
554
|
if (options?.search) {
|
|
334
555
|
results = allChats.filter((c) => matchesSearch(c, options.search));
|
|
335
556
|
}
|
|
336
|
-
|
|
557
|
+
const titles = options?.resolveTitles
|
|
558
|
+
? await Promise.all(results.map((chat) => this.fetchChatTitle(session, parseLong(String(chat.c)), chat)))
|
|
559
|
+
: null;
|
|
560
|
+
return results.map((chat, i) => formatChat(chat, titles ? titles[i] : null, this.nameCache));
|
|
337
561
|
}
|
|
338
562
|
catch (error) {
|
|
339
563
|
throw wrapError(error, 'get_chats_failed');
|
|
340
564
|
}
|
|
341
565
|
});
|
|
342
566
|
}
|
|
567
|
+
/**
|
|
568
|
+
* Resolve the user-set room title via CHATINFO. Returns null on any error
|
|
569
|
+
* (network, malformed response, or no TITLE meta present). Designed to be
|
|
570
|
+
* fire-and-forget per chat — failures don't poison the whole `getChats` call.
|
|
571
|
+
*/
|
|
572
|
+
async getChatTitle(chatId) {
|
|
573
|
+
let parsed;
|
|
574
|
+
try {
|
|
575
|
+
parsed = parseLong(chatId);
|
|
576
|
+
}
|
|
577
|
+
catch {
|
|
578
|
+
return null;
|
|
579
|
+
}
|
|
580
|
+
return this.executeWithReconnect(async ({ session, loginResult }) => {
|
|
581
|
+
const chat = (loginResult.chatDatas ?? []).find((c) => String(c.c) === chatId);
|
|
582
|
+
return this.fetchChatTitle(session, parsed, chat);
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
async fetchChatTitle(session, chatId, chat) {
|
|
586
|
+
let title = null;
|
|
587
|
+
try {
|
|
588
|
+
const response = await session.getChannelInfo(chatId);
|
|
589
|
+
title = extractTitle(response.body);
|
|
590
|
+
}
|
|
591
|
+
catch {
|
|
592
|
+
title = null;
|
|
593
|
+
}
|
|
594
|
+
if (title)
|
|
595
|
+
return title;
|
|
596
|
+
if (!chat || !isOpenChat(chat))
|
|
597
|
+
return null;
|
|
598
|
+
const linkId = getOpenLinkId(chat);
|
|
599
|
+
if (!linkId)
|
|
600
|
+
return null;
|
|
601
|
+
try {
|
|
602
|
+
const response = await session.getOpenLinkInfo([linkId]);
|
|
603
|
+
return extractOpenLinkName(response.body);
|
|
604
|
+
}
|
|
605
|
+
catch {
|
|
606
|
+
return null;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
343
609
|
async getMessages(chatId, options) {
|
|
344
610
|
return this.executeWithReconnect(async ({ session }) => {
|
|
345
611
|
try {
|
|
@@ -358,7 +624,7 @@ export class KakaoTalkClient {
|
|
|
358
624
|
}
|
|
359
625
|
const batch = (response.body.chatLogs ?? []).filter((log) => longToString(log.chatId) === chatId);
|
|
360
626
|
if (batch.length === 0) {
|
|
361
|
-
return formatMessages(allMessages, count);
|
|
627
|
+
return formatMessages(allMessages, count, chatId, this.nameCache);
|
|
362
628
|
}
|
|
363
629
|
for (const log of batch) {
|
|
364
630
|
const lid = longToString(log.logId);
|
|
@@ -369,7 +635,7 @@ export class KakaoTalkClient {
|
|
|
369
635
|
}
|
|
370
636
|
const maxLog = findMaxLogId(batch, 'logId');
|
|
371
637
|
if (!maxLog || maxLog.equals(cur) || response.body.eof) {
|
|
372
|
-
return formatMessages(allMessages, count);
|
|
638
|
+
return formatMessages(allMessages, count, chatId, this.nameCache);
|
|
373
639
|
}
|
|
374
640
|
cur = maxLog;
|
|
375
641
|
}
|
|
@@ -381,7 +647,7 @@ export class KakaoTalkClient {
|
|
|
381
647
|
}
|
|
382
648
|
if (allMessages.length > 0) {
|
|
383
649
|
warn(`[agent-kakaotalk] Warning: message fetch capped at ${MAX_PAGES} pages. Results may be incomplete.`);
|
|
384
|
-
return formatMessages(allMessages, count);
|
|
650
|
+
return formatMessages(allMessages, count, chatId, this.nameCache);
|
|
385
651
|
}
|
|
386
652
|
// Fetch fresh lastLogId via CHATONROOM (not the stale login-time snapshot)
|
|
387
653
|
const chatInfo = await session.getChatInfo(cid);
|
|
@@ -412,13 +678,44 @@ export class KakaoTalkClient {
|
|
|
412
678
|
if (!reachedEnd) {
|
|
413
679
|
warn(`[agent-kakaotalk] Warning: message fetch capped at ${MAX_PAGES} pages. Results may be incomplete.`);
|
|
414
680
|
}
|
|
415
|
-
return formatMessages(allMessages, count);
|
|
681
|
+
return formatMessages(allMessages, count, chatId, this.nameCache);
|
|
416
682
|
}
|
|
417
683
|
catch (error) {
|
|
418
684
|
throw wrapError(error, 'get_messages_failed');
|
|
419
685
|
}
|
|
420
686
|
});
|
|
421
687
|
}
|
|
688
|
+
async getMembers(chatId) {
|
|
689
|
+
const parsedChatId = parseChatId(chatId);
|
|
690
|
+
return this.executeWithReconnect(async ({ session }) => {
|
|
691
|
+
try {
|
|
692
|
+
const response = await session.getAllMembers(parsedChatId);
|
|
693
|
+
assertLocoOk(response, 'GETMEM');
|
|
694
|
+
const members = (response.body.members ?? []);
|
|
695
|
+
return members.map(formatMember);
|
|
696
|
+
}
|
|
697
|
+
catch (error) {
|
|
698
|
+
throw wrapError(error, 'get_members_failed');
|
|
699
|
+
}
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
async getMembersByIds(chatId, userIds) {
|
|
703
|
+
if (userIds.length === 0)
|
|
704
|
+
return [];
|
|
705
|
+
const parsedChatId = parseChatId(chatId);
|
|
706
|
+
const memberIds = userIds.map((id) => parseUserId(id));
|
|
707
|
+
return this.executeWithReconnect(async ({ session }) => {
|
|
708
|
+
try {
|
|
709
|
+
const response = await session.getMembersByIds(parsedChatId, memberIds);
|
|
710
|
+
assertLocoOk(response, 'MEMBER');
|
|
711
|
+
const members = (response.body.members ?? []);
|
|
712
|
+
return members.map(formatMember);
|
|
713
|
+
}
|
|
714
|
+
catch (error) {
|
|
715
|
+
throw wrapError(error, 'get_members_failed');
|
|
716
|
+
}
|
|
717
|
+
});
|
|
718
|
+
}
|
|
422
719
|
async sendMessage(chatId, text) {
|
|
423
720
|
return this.executeWithReconnect(async ({ session }) => {
|
|
424
721
|
try {
|
|
@@ -501,6 +798,12 @@ export class KakaoTalkClient {
|
|
|
501
798
|
}
|
|
502
799
|
this.state = null;
|
|
503
800
|
this.initPromise = null;
|
|
801
|
+
this.pushHandlers.clear();
|
|
802
|
+
this.sessionEventHandlers.clear();
|
|
803
|
+
this.nameCache.clear();
|
|
804
|
+
}
|
|
805
|
+
lookupAuthorName(chatId, authorId) {
|
|
806
|
+
return this.nameCache.lookup(chatId, authorId);
|
|
504
807
|
}
|
|
505
808
|
}
|
|
506
809
|
//# sourceMappingURL=client.js.map
|