@zhin.js/adapter-line 4.0.3 → 4.0.5
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/CHANGELOG.md +70 -0
- package/adapters/line.js +2 -2
- package/adapters/line.ts +2 -2
- package/lib/endpoint.js +13 -12
- package/lib/line-agent-deps.d.ts +1 -1
- package/lib/line-agent-deps.js +4 -4
- package/lib/protocol.d.ts +3 -3
- package/lib/protocol.js +5 -5
- package/package.json +15 -15
- package/schema.json +41 -5
- package/src/endpoint.ts +14 -13
- package/src/line-agent-deps.ts +4 -4
- package/src/protocol.ts +7 -7
- /package/commands/endpoint/add/{[name].js → [id].js} +0 -0
- /package/commands/endpoint/add/{[name].ts → [id].ts} +0 -0
- /package/commands/endpoint/remove/{[name].js → [id].js} +0 -0
- /package/commands/endpoint/remove/{[name].ts → [id].ts} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,75 @@
|
|
|
1
1
|
# @zhin.js/adapter-line
|
|
2
2
|
|
|
3
|
+
## 4.0.5
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [6f9c366]
|
|
8
|
+
- Updated dependencies [373a56b]
|
|
9
|
+
- Updated dependencies [0de46a8]
|
|
10
|
+
- Updated dependencies [c106ecc]
|
|
11
|
+
- Updated dependencies [ca92e03]
|
|
12
|
+
- Updated dependencies [b0f37ae]
|
|
13
|
+
- Updated dependencies [ba08a2f]
|
|
14
|
+
- Updated dependencies [b08f7fe]
|
|
15
|
+
- Updated dependencies [daffd4c]
|
|
16
|
+
- Updated dependencies [36c7400]
|
|
17
|
+
- Updated dependencies [a9fa72e]
|
|
18
|
+
- Updated dependencies [c8de3ef]
|
|
19
|
+
- Updated dependencies [60f0fc8]
|
|
20
|
+
- Updated dependencies [574c990]
|
|
21
|
+
- Updated dependencies [d096f16]
|
|
22
|
+
- Updated dependencies [3f29623]
|
|
23
|
+
- Updated dependencies [2916852]
|
|
24
|
+
- Updated dependencies [d047869]
|
|
25
|
+
- Updated dependencies [162fa34]
|
|
26
|
+
- Updated dependencies [e62561e]
|
|
27
|
+
- Updated dependencies [3eeeb46]
|
|
28
|
+
- Updated dependencies [85d0f82]
|
|
29
|
+
- Updated dependencies [61bfc1c]
|
|
30
|
+
- Updated dependencies [04bad47]
|
|
31
|
+
- Updated dependencies [e40b048]
|
|
32
|
+
- Updated dependencies [5a5b1bb]
|
|
33
|
+
- Updated dependencies [f1708c3]
|
|
34
|
+
- Updated dependencies [d254a81]
|
|
35
|
+
- Updated dependencies [7123c47]
|
|
36
|
+
- Updated dependencies [05befc1]
|
|
37
|
+
- Updated dependencies [0663b6a]
|
|
38
|
+
- Updated dependencies [f28f9b3]
|
|
39
|
+
- Updated dependencies [9f340f7]
|
|
40
|
+
- Updated dependencies [e53444f]
|
|
41
|
+
- Updated dependencies [5eedd26]
|
|
42
|
+
- Updated dependencies [92b0dd7]
|
|
43
|
+
- Updated dependencies [f919b6f]
|
|
44
|
+
- Updated dependencies [098e411]
|
|
45
|
+
- Updated dependencies [e1b7c01]
|
|
46
|
+
- Updated dependencies [9b94f87]
|
|
47
|
+
- Updated dependencies [a7df753]
|
|
48
|
+
- @zhin.js/agent@1.1.5
|
|
49
|
+
- @zhin.js/host-http@1.0.8
|
|
50
|
+
- @zhin.js/im-contract@1.0.3
|
|
51
|
+
- @zhin.js/adapter@1.1.7
|
|
52
|
+
- @zhin.js/plugin-runtime@1.1.5
|
|
53
|
+
- @zhin.js/command@1.0.9
|
|
54
|
+
- @zhin.js/core@1.5.4
|
|
55
|
+
- zhin.js@6.0.4
|
|
56
|
+
|
|
57
|
+
## 4.0.4
|
|
58
|
+
|
|
59
|
+
### Patch Changes
|
|
60
|
+
|
|
61
|
+
- f8c7a54: fix: im
|
|
62
|
+
- Updated dependencies [f8c7a54]
|
|
63
|
+
- @zhin.js/logger@1.0.76
|
|
64
|
+
- @zhin.js/host-http@1.0.7
|
|
65
|
+
- @zhin.js/adapter@1.1.6
|
|
66
|
+
- @zhin.js/agent@1.1.4
|
|
67
|
+
- @zhin.js/command@1.0.8
|
|
68
|
+
- @zhin.js/core@1.5.3
|
|
69
|
+
- @zhin.js/im-contract@1.0.2
|
|
70
|
+
- @zhin.js/plugin-runtime@1.1.4
|
|
71
|
+
- zhin.js@6.0.3
|
|
72
|
+
|
|
3
73
|
## 4.0.3
|
|
4
74
|
|
|
5
75
|
### Patch Changes
|
package/adapters/line.js
CHANGED
|
@@ -19,8 +19,8 @@ export default defineAdapter({
|
|
|
19
19
|
create(context) {
|
|
20
20
|
const config = resolveLineConfig(context.config);
|
|
21
21
|
// 注册到插件运行时状态(line.endpoint list 的"运行中"数据源)
|
|
22
|
-
context.use(lineRuntimeStateToken).endpoints.set(config.
|
|
23
|
-
|
|
22
|
+
context.use(lineRuntimeStateToken).endpoints.set(config.id, {
|
|
23
|
+
id: config.id,
|
|
24
24
|
mode: 'webhook',
|
|
25
25
|
});
|
|
26
26
|
return new LineEndpoint({
|
package/adapters/line.ts
CHANGED
|
@@ -24,8 +24,8 @@ export default defineAdapter<LineAdapterConfig>({
|
|
|
24
24
|
create(context) {
|
|
25
25
|
const config = resolveLineConfig(context.config);
|
|
26
26
|
// 注册到插件运行时状态(line.endpoint list 的"运行中"数据源)
|
|
27
|
-
context.use(lineRuntimeStateToken).endpoints.set(config.
|
|
28
|
-
|
|
27
|
+
context.use(lineRuntimeStateToken).endpoints.set(config.id, {
|
|
28
|
+
id: config.id,
|
|
29
29
|
mode: 'webhook',
|
|
30
30
|
});
|
|
31
31
|
return new LineEndpoint({
|
package/lib/endpoint.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { formatCompact,
|
|
1
|
+
import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
|
|
2
2
|
import { registerLineAgentEndpoint } from './line-agent-deps.js';
|
|
3
3
|
import { formatInboundContent, formatOutboundMessages, generateMessageId, isMessageEvent, isValidLineRecipientId, lineInboundConversation, } from './protocol.js';
|
|
4
4
|
import { registerLineWebhookRoutes } from './webhook.js';
|
|
5
|
-
const logger = getLogger('line');
|
|
6
5
|
/** LINE replyToken 有效期短,过期后 reply 必 400;缓存带时间戳,超时弃用改走 push。 */
|
|
7
6
|
const REPLY_TOKEN_TTL_MS = 60_000;
|
|
8
7
|
/** 出站 HTTP 调用统一 30s 超时。 */
|
|
9
8
|
const OUTBOUND_TIMEOUT_MS = 30_000;
|
|
10
9
|
export class LineEndpoint {
|
|
10
|
+
#logger;
|
|
11
11
|
#options;
|
|
12
12
|
#fetch;
|
|
13
13
|
#routeReleases = [];
|
|
@@ -17,6 +17,7 @@ export class LineEndpoint {
|
|
|
17
17
|
#unregisterAgent;
|
|
18
18
|
management = createLineEndpointManagement(this);
|
|
19
19
|
constructor(options) {
|
|
20
|
+
this.#logger = getAdapterLogger('line', options.config.id);
|
|
20
21
|
this.#options = options;
|
|
21
22
|
this.#fetch = options.fetch ?? globalThis.fetch;
|
|
22
23
|
}
|
|
@@ -38,17 +39,17 @@ export class LineEndpoint {
|
|
|
38
39
|
return;
|
|
39
40
|
this.#started = true;
|
|
40
41
|
try {
|
|
41
|
-
this.#unregisterAgent = registerLineAgentEndpoint(this.#options.config.
|
|
42
|
+
this.#unregisterAgent = registerLineAgentEndpoint(this.#options.config.id, this);
|
|
42
43
|
this.#routeReleases.push(...registerLineWebhookRoutes(this.#options.http, this));
|
|
43
|
-
logger.debug(formatCompact({
|
|
44
|
-
endpoint: this.#options.config.
|
|
44
|
+
this.#logger.debug(formatCompact({
|
|
45
|
+
endpoint: this.#options.config.id,
|
|
45
46
|
op: 'webhook',
|
|
46
47
|
path: this.#options.config.webhookPath,
|
|
47
48
|
}));
|
|
48
49
|
}
|
|
49
50
|
catch (error) {
|
|
50
51
|
await this.stop();
|
|
51
|
-
logger.error('Failed to connect LINE endpoint:', error);
|
|
52
|
+
this.#logger.error('Failed to connect LINE endpoint:', error);
|
|
52
53
|
throw error;
|
|
53
54
|
}
|
|
54
55
|
}
|
|
@@ -66,7 +67,7 @@ export class LineEndpoint {
|
|
|
66
67
|
this.#unregisterAgent?.();
|
|
67
68
|
this.#unregisterAgent = undefined;
|
|
68
69
|
this.#started = false;
|
|
69
|
-
logger.debug(formatCompact({ op: 'disconnect'
|
|
70
|
+
this.#logger.debug(formatCompact({ op: 'disconnect' }));
|
|
70
71
|
}
|
|
71
72
|
async send({ conversation, payload }) {
|
|
72
73
|
const messages = formatOutboundMessages(payload);
|
|
@@ -86,9 +87,9 @@ export class LineEndpoint {
|
|
|
86
87
|
// replyToken 过期/失效时 LINE 返回 400,回退 push 保证消息不丢
|
|
87
88
|
if (error.status !== 400)
|
|
88
89
|
throw error;
|
|
89
|
-
logger.warn(formatCompact({
|
|
90
|
+
this.#logger.warn(formatCompact({
|
|
90
91
|
op: 'line_reply_fallback_push',
|
|
91
|
-
endpoint: this.#options.config.
|
|
92
|
+
endpoint: this.#options.config.id,
|
|
92
93
|
target,
|
|
93
94
|
}));
|
|
94
95
|
}
|
|
@@ -111,16 +112,16 @@ export class LineEndpoint {
|
|
|
111
112
|
conversation,
|
|
112
113
|
message: { conversation, id: generateMessageId(event) },
|
|
113
114
|
content: formatInboundContent(event),
|
|
114
|
-
sender: event.source.userId || conversation.id,
|
|
115
|
+
sender: { id: event.source.userId || conversation.id },
|
|
116
|
+
endpointId: this.#options.config.id,
|
|
115
117
|
metadata: Object.freeze({
|
|
116
118
|
eventType: event.type,
|
|
117
119
|
sourceType: event.source.type,
|
|
118
|
-
endpoint: this.#options.config.name,
|
|
119
120
|
timestamp: event.timestamp,
|
|
120
121
|
...(isMessageEvent(event) ? { messageType: event.message.type } : {}),
|
|
121
122
|
}),
|
|
122
123
|
}).catch((err) => {
|
|
123
|
-
logger.warn(formatCompact({
|
|
124
|
+
this.#logger.warn(formatCompact({
|
|
124
125
|
op: 'line_gateway_receive_failed',
|
|
125
126
|
target: `${conversation.kind}:${conversation.id}`,
|
|
126
127
|
error: err instanceof Error ? err.message : String(err),
|
package/lib/line-agent-deps.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ export interface LineAgentDeps {
|
|
|
14
14
|
apiBaseUrl: string;
|
|
15
15
|
};
|
|
16
16
|
}
|
|
17
|
-
export declare function registerLineAgentEndpoint(
|
|
17
|
+
export declare function registerLineAgentEndpoint(endpointKey: string, endpoint: LineAgentEndpoint): () => void;
|
|
18
18
|
/** Optional override used by tests / transitional callers. Pass `null` to clear. */
|
|
19
19
|
export declare function setLineAgentDeps(deps: LineAgentDeps | null): void;
|
|
20
20
|
export declare function getLineAgentDeps(): LineAgentDeps;
|
package/lib/line-agent-deps.js
CHANGED
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
*/
|
|
5
5
|
const endpoints = new Map();
|
|
6
6
|
let override = null;
|
|
7
|
-
export function registerLineAgentEndpoint(
|
|
8
|
-
endpoints.set(
|
|
7
|
+
export function registerLineAgentEndpoint(endpointKey, endpoint) {
|
|
8
|
+
endpoints.set(endpointKey, endpoint);
|
|
9
9
|
return () => {
|
|
10
|
-
if (endpoints.get(
|
|
11
|
-
endpoints.delete(
|
|
10
|
+
if (endpoints.get(endpointKey) === endpoint) {
|
|
11
|
+
endpoints.delete(endpointKey);
|
|
12
12
|
}
|
|
13
13
|
};
|
|
14
14
|
}
|
package/lib/protocol.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ import type { IncomingMessage } from 'node:http';
|
|
|
6
6
|
import type { ConversationRef } from '@zhin.js/im-contract';
|
|
7
7
|
/** Plugin Runtime owner config (`plugins.<instanceKey>` / schema.json). */
|
|
8
8
|
export interface LineAdapterConfig {
|
|
9
|
-
readonly
|
|
9
|
+
readonly id?: string;
|
|
10
10
|
readonly channelSecret?: string;
|
|
11
11
|
readonly channelAccessToken?: string;
|
|
12
12
|
readonly webhookPath?: string;
|
|
@@ -18,7 +18,7 @@ export interface LineAdapterConfig {
|
|
|
18
18
|
}
|
|
19
19
|
export interface ResolvedLineConfig {
|
|
20
20
|
readonly context: 'line';
|
|
21
|
-
readonly
|
|
21
|
+
readonly id: string;
|
|
22
22
|
readonly channelSecret: string;
|
|
23
23
|
readonly channelAccessToken: string;
|
|
24
24
|
readonly webhookPath: string;
|
|
@@ -141,7 +141,7 @@ export declare function resolveChannel(source: LineSource): LineChannel;
|
|
|
141
141
|
* 入站归一化 → ConversationRef:LINE user → private、group → group、room → channel。
|
|
142
142
|
* LINE 没有 guild/频道容器概念,无 parent;recipient id 前缀(U/G/R)自带场景信息。
|
|
143
143
|
*/
|
|
144
|
-
export declare function lineInboundConversation(
|
|
144
|
+
export declare function lineInboundConversation(endpointKey: string, source: LineSource): ConversationRef;
|
|
145
145
|
export declare function generateMessageId(event: LineEvent): string;
|
|
146
146
|
/** Build inbound text for MessageGateway.receive. */
|
|
147
147
|
export declare function formatInboundContent(event: LineEvent): string;
|
package/lib/protocol.js
CHANGED
|
@@ -17,15 +17,15 @@ export function resolveLineConfig(config = {}) {
|
|
|
17
17
|
if (!channelSecret || !channelAccessToken) {
|
|
18
18
|
throw new TypeError('LINE adapter requires channelSecret + channelAccessToken (plugins.<key> or endpoints with context: line)');
|
|
19
19
|
}
|
|
20
|
-
const
|
|
21
|
-
|| (typeof entry?.
|
|
20
|
+
const id = (typeof config.id === 'string' && config.id)
|
|
21
|
+
|| (typeof entry?.id === 'string' && entry.id)
|
|
22
22
|
|| process.env.LINE_BOT_NAME
|
|
23
23
|
|| 'line-bot';
|
|
24
24
|
const webhookPath = normalizeWebhookPath(config.webhookPath ?? entry?.webhookPath ?? '/line/webhook');
|
|
25
25
|
const apiBaseUrl = (config.apiBaseUrl ?? entry?.apiBaseUrl ?? 'https://api.line.me').replace(/\/$/, '');
|
|
26
26
|
return {
|
|
27
27
|
context: 'line',
|
|
28
|
-
|
|
28
|
+
id,
|
|
29
29
|
channelSecret,
|
|
30
30
|
channelAccessToken,
|
|
31
31
|
webhookPath,
|
|
@@ -58,10 +58,10 @@ export function resolveChannel(source) {
|
|
|
58
58
|
* 入站归一化 → ConversationRef:LINE user → private、group → group、room → channel。
|
|
59
59
|
* LINE 没有 guild/频道容器概念,无 parent;recipient id 前缀(U/G/R)自带场景信息。
|
|
60
60
|
*/
|
|
61
|
-
export function lineInboundConversation(
|
|
61
|
+
export function lineInboundConversation(endpointKey, source) {
|
|
62
62
|
const channel = resolveChannel(source);
|
|
63
63
|
return {
|
|
64
|
-
endpoint: { id:
|
|
64
|
+
endpoint: { id: endpointKey, adapter: endpointKey.split('\0')[0] ?? endpointKey },
|
|
65
65
|
kind: channel.channelType,
|
|
66
66
|
id: channel.channelId,
|
|
67
67
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/adapter-line",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.5",
|
|
4
4
|
"description": "Zhin.js LINE Messaging API adapter for Plugin Runtime (HTTP webhook)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -32,22 +32,22 @@
|
|
|
32
32
|
"directory": "plugins/adapters/line"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@zhin.js/adapter": "1.1.
|
|
36
|
-
"@zhin.js/command": "1.0.
|
|
37
|
-
"@zhin.js/core": "1.5.
|
|
38
|
-
"@zhin.js/host-http": "1.0.
|
|
39
|
-
"@zhin.js/im-contract": "1.0.
|
|
40
|
-
"@zhin.js/logger": "1.0.
|
|
41
|
-
"@zhin.js/plugin-runtime": "1.1.
|
|
35
|
+
"@zhin.js/adapter": "1.1.7",
|
|
36
|
+
"@zhin.js/command": "1.0.9",
|
|
37
|
+
"@zhin.js/core": "1.5.4",
|
|
38
|
+
"@zhin.js/host-http": "1.0.8",
|
|
39
|
+
"@zhin.js/im-contract": "1.0.3",
|
|
40
|
+
"@zhin.js/logger": "1.0.76",
|
|
41
|
+
"@zhin.js/plugin-runtime": "1.1.5"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
44
|
"zod": "^4.0.0",
|
|
45
|
-
"@zhin.js/adapter": "1.1.
|
|
46
|
-
"@zhin.js/agent": "1.1.
|
|
47
|
-
"@zhin.js/core": "1.5.
|
|
48
|
-
"@zhin.js/host-http": "1.0.
|
|
49
|
-
"@zhin.js/plugin-runtime": "1.1.
|
|
50
|
-
"zhin.js": "6.0.
|
|
45
|
+
"@zhin.js/adapter": "1.1.7",
|
|
46
|
+
"@zhin.js/agent": "1.1.5",
|
|
47
|
+
"@zhin.js/core": "1.5.4",
|
|
48
|
+
"@zhin.js/host-http": "1.0.8",
|
|
49
|
+
"@zhin.js/plugin-runtime": "1.1.5",
|
|
50
|
+
"zhin.js": "6.0.4"
|
|
51
51
|
},
|
|
52
52
|
"peerDependenciesMeta": {
|
|
53
53
|
"zhin.js": {
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"typescript": "^6.0.3",
|
|
66
66
|
"vitest": "^4.1.10",
|
|
67
67
|
"zod": "^4.4.3",
|
|
68
|
-
"@zhin.js/agent": "1.1.
|
|
68
|
+
"@zhin.js/agent": "1.1.5"
|
|
69
69
|
},
|
|
70
70
|
"files": [
|
|
71
71
|
"adapters",
|
package/schema.json
CHANGED
|
@@ -11,16 +11,48 @@
|
|
|
11
11
|
"type": "string",
|
|
12
12
|
"default": "https://api.line.me"
|
|
13
13
|
},
|
|
14
|
+
"master": {
|
|
15
|
+
"type": [
|
|
16
|
+
"string",
|
|
17
|
+
"number"
|
|
18
|
+
],
|
|
19
|
+
"description": "框架 master(LINE user id;AI/工具权限、endpoint 管理)。endpoints[i].master 可逐项覆盖"
|
|
20
|
+
},
|
|
21
|
+
"trusted": {
|
|
22
|
+
"type": "array",
|
|
23
|
+
"items": {
|
|
24
|
+
"type": [
|
|
25
|
+
"string",
|
|
26
|
+
"number"
|
|
27
|
+
],
|
|
28
|
+
"description": "Trusted LINE user id"
|
|
29
|
+
},
|
|
30
|
+
"description": "框架 trusted 用户列表(弱于 master)。endpoints[i].trusted 可逐项追加"
|
|
31
|
+
},
|
|
14
32
|
"endpoints": {
|
|
15
33
|
"type": "array",
|
|
16
|
-
"description": "多账号:一个插件实例挂多个 endpoint。每项与顶层字段同构(
|
|
34
|
+
"description": "多账号:一个插件实例挂多个 endpoint。每项与顶层字段同构(id 必填,其余覆盖顶层)",
|
|
17
35
|
"items": {
|
|
18
36
|
"type": "object",
|
|
19
37
|
"additionalProperties": true,
|
|
20
38
|
"properties": {
|
|
21
|
-
"
|
|
22
|
-
"type":
|
|
23
|
-
|
|
39
|
+
"master": {
|
|
40
|
+
"type": [
|
|
41
|
+
"string",
|
|
42
|
+
"number"
|
|
43
|
+
],
|
|
44
|
+
"description": "本 endpoint 的框架 master(LINE user id);覆盖顶层 master"
|
|
45
|
+
},
|
|
46
|
+
"trusted": {
|
|
47
|
+
"type": "array",
|
|
48
|
+
"items": {
|
|
49
|
+
"type": [
|
|
50
|
+
"string",
|
|
51
|
+
"number"
|
|
52
|
+
],
|
|
53
|
+
"description": "Trusted LINE user id"
|
|
54
|
+
},
|
|
55
|
+
"description": "本 endpoint 的 trusted 列表"
|
|
24
56
|
},
|
|
25
57
|
"channelSecret": {
|
|
26
58
|
"type": "string",
|
|
@@ -29,10 +61,14 @@
|
|
|
29
61
|
"channelAccessToken": {
|
|
30
62
|
"type": "string",
|
|
31
63
|
"description": "LINE channel access token"
|
|
64
|
+
},
|
|
65
|
+
"id": {
|
|
66
|
+
"type": "string",
|
|
67
|
+
"description": "LINE bot name"
|
|
32
68
|
}
|
|
33
69
|
},
|
|
34
70
|
"required": [
|
|
35
|
-
"
|
|
71
|
+
"id",
|
|
36
72
|
"channelSecret",
|
|
37
73
|
"channelAccessToken"
|
|
38
74
|
]
|
package/src/endpoint.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import type { EndpointInstance, EndpointManagement, EndpointSendRequest } from '@zhin.js/adapter';
|
|
5
5
|
import type { MessageGateway } from '@zhin.js/core/runtime';
|
|
6
6
|
import type { HttpHost, HttpRouteRegistration } from '@zhin.js/host-http';
|
|
7
|
-
import { formatCompact,
|
|
7
|
+
import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
|
|
8
8
|
import type { CapabilityId } from '@zhin.js/plugin-runtime';
|
|
9
9
|
import { registerLineAgentEndpoint } from './line-agent-deps.js';
|
|
10
10
|
import {
|
|
@@ -20,8 +20,6 @@ import {
|
|
|
20
20
|
} from './protocol.js';
|
|
21
21
|
import { registerLineWebhookRoutes } from './webhook.js';
|
|
22
22
|
|
|
23
|
-
const logger = getLogger('line');
|
|
24
|
-
|
|
25
23
|
/** LINE replyToken 有效期短,过期后 reply 必 400;缓存带时间戳,超时弃用改走 push。 */
|
|
26
24
|
const REPLY_TOKEN_TTL_MS = 60_000;
|
|
27
25
|
/** 出站 HTTP 调用统一 30s 超时。 */
|
|
@@ -51,6 +49,8 @@ export interface LineEndpointOptions {
|
|
|
51
49
|
}
|
|
52
50
|
|
|
53
51
|
export class LineEndpoint implements EndpointInstance {
|
|
52
|
+
readonly #logger!: ReturnType<typeof getAdapterLogger>;
|
|
53
|
+
|
|
54
54
|
readonly #options: LineEndpointOptions;
|
|
55
55
|
readonly #fetch: LineFetch;
|
|
56
56
|
#routeReleases: HttpRouteRegistration[] = [];
|
|
@@ -61,6 +61,7 @@ export class LineEndpoint implements EndpointInstance {
|
|
|
61
61
|
readonly management: EndpointManagement = createLineEndpointManagement(this);
|
|
62
62
|
|
|
63
63
|
constructor(options: LineEndpointOptions) {
|
|
64
|
+
this.#logger = getAdapterLogger('line', options.config.id);
|
|
64
65
|
this.#options = options;
|
|
65
66
|
this.#fetch = options.fetch ?? globalThis.fetch;
|
|
66
67
|
}
|
|
@@ -85,16 +86,16 @@ export class LineEndpoint implements EndpointInstance {
|
|
|
85
86
|
if (this.#started) return;
|
|
86
87
|
this.#started = true;
|
|
87
88
|
try {
|
|
88
|
-
this.#unregisterAgent = registerLineAgentEndpoint(this.#options.config.
|
|
89
|
+
this.#unregisterAgent = registerLineAgentEndpoint(this.#options.config.id, this);
|
|
89
90
|
this.#routeReleases.push(...registerLineWebhookRoutes(this.#options.http, this));
|
|
90
|
-
logger.debug(formatCompact({
|
|
91
|
-
endpoint: this.#options.config.
|
|
91
|
+
this.#logger.debug(formatCompact({
|
|
92
|
+
endpoint: this.#options.config.id,
|
|
92
93
|
op: 'webhook',
|
|
93
94
|
path: this.#options.config.webhookPath,
|
|
94
95
|
}));
|
|
95
96
|
} catch (error) {
|
|
96
97
|
await this.stop();
|
|
97
|
-
logger.error('Failed to connect LINE endpoint:', error);
|
|
98
|
+
this.#logger.error('Failed to connect LINE endpoint:', error);
|
|
98
99
|
throw error;
|
|
99
100
|
}
|
|
100
101
|
}
|
|
@@ -114,7 +115,7 @@ export class LineEndpoint implements EndpointInstance {
|
|
|
114
115
|
this.#unregisterAgent?.();
|
|
115
116
|
this.#unregisterAgent = undefined;
|
|
116
117
|
this.#started = false;
|
|
117
|
-
logger.debug(formatCompact({ op: 'disconnect'
|
|
118
|
+
this.#logger.debug(formatCompact({ op: 'disconnect' }));
|
|
118
119
|
}
|
|
119
120
|
|
|
120
121
|
async send({ conversation, payload }: EndpointSendRequest): Promise<string> {
|
|
@@ -134,9 +135,9 @@ export class LineEndpoint implements EndpointInstance {
|
|
|
134
135
|
} catch (error) {
|
|
135
136
|
// replyToken 过期/失效时 LINE 返回 400,回退 push 保证消息不丢
|
|
136
137
|
if ((error as { status?: number }).status !== 400) throw error;
|
|
137
|
-
logger.warn(formatCompact({
|
|
138
|
+
this.#logger.warn(formatCompact({
|
|
138
139
|
op: 'line_reply_fallback_push',
|
|
139
|
-
endpoint: this.#options.config.
|
|
140
|
+
endpoint: this.#options.config.id,
|
|
140
141
|
target,
|
|
141
142
|
}));
|
|
142
143
|
}
|
|
@@ -162,16 +163,16 @@ export class LineEndpoint implements EndpointInstance {
|
|
|
162
163
|
conversation,
|
|
163
164
|
message: { conversation, id: generateMessageId(event) },
|
|
164
165
|
content: formatInboundContent(event),
|
|
165
|
-
sender: event.source.userId || conversation.id,
|
|
166
|
+
sender: { id: event.source.userId || conversation.id },
|
|
167
|
+
endpointId: this.#options.config.id,
|
|
166
168
|
metadata: Object.freeze({
|
|
167
169
|
eventType: event.type,
|
|
168
170
|
sourceType: event.source.type,
|
|
169
|
-
endpoint: this.#options.config.name,
|
|
170
171
|
timestamp: event.timestamp,
|
|
171
172
|
...(isMessageEvent(event) ? { messageType: event.message.type } : {}),
|
|
172
173
|
}),
|
|
173
174
|
}).catch((err) => {
|
|
174
|
-
logger.warn(formatCompact({
|
|
175
|
+
this.#logger.warn(formatCompact({
|
|
175
176
|
op: 'line_gateway_receive_failed',
|
|
176
177
|
target: `${conversation.kind}:${conversation.id}`,
|
|
177
178
|
error: err instanceof Error ? err.message : String(err),
|
package/src/line-agent-deps.ts
CHANGED
|
@@ -15,13 +15,13 @@ const endpoints = new Map<string, LineAgentEndpoint>();
|
|
|
15
15
|
let override: LineAgentDeps | null = null;
|
|
16
16
|
|
|
17
17
|
export function registerLineAgentEndpoint(
|
|
18
|
-
|
|
18
|
+
endpointKey: string,
|
|
19
19
|
endpoint: LineAgentEndpoint,
|
|
20
20
|
): () => void {
|
|
21
|
-
endpoints.set(
|
|
21
|
+
endpoints.set(endpointKey, endpoint);
|
|
22
22
|
return () => {
|
|
23
|
-
if (endpoints.get(
|
|
24
|
-
endpoints.delete(
|
|
23
|
+
if (endpoints.get(endpointKey) === endpoint) {
|
|
24
|
+
endpoints.delete(endpointKey);
|
|
25
25
|
}
|
|
26
26
|
};
|
|
27
27
|
}
|
package/src/protocol.ts
CHANGED
|
@@ -13,7 +13,7 @@ const logger = getLogger('line');
|
|
|
13
13
|
|
|
14
14
|
/** Plugin Runtime owner config (`plugins.<instanceKey>` / schema.json). */
|
|
15
15
|
export interface LineAdapterConfig {
|
|
16
|
-
readonly
|
|
16
|
+
readonly id?: string;
|
|
17
17
|
readonly channelSecret?: string;
|
|
18
18
|
readonly channelAccessToken?: string;
|
|
19
19
|
readonly webhookPath?: string;
|
|
@@ -26,7 +26,7 @@ export interface LineAdapterConfig {
|
|
|
26
26
|
|
|
27
27
|
export interface ResolvedLineConfig {
|
|
28
28
|
readonly context: 'line';
|
|
29
|
-
readonly
|
|
29
|
+
readonly id: string;
|
|
30
30
|
readonly channelSecret: string;
|
|
31
31
|
readonly channelAccessToken: string;
|
|
32
32
|
readonly webhookPath: string;
|
|
@@ -174,8 +174,8 @@ export function resolveLineConfig(config: LineAdapterConfig = {}): ResolvedLineC
|
|
|
174
174
|
'LINE adapter requires channelSecret + channelAccessToken (plugins.<key> or endpoints with context: line)',
|
|
175
175
|
);
|
|
176
176
|
}
|
|
177
|
-
const
|
|
178
|
-
|| (typeof entry?.
|
|
177
|
+
const id = (typeof config.id === 'string' && config.id)
|
|
178
|
+
|| (typeof entry?.id === 'string' && entry.id)
|
|
179
179
|
|| process.env.LINE_BOT_NAME
|
|
180
180
|
|| 'line-bot';
|
|
181
181
|
const webhookPath = normalizeWebhookPath(
|
|
@@ -184,7 +184,7 @@ export function resolveLineConfig(config: LineAdapterConfig = {}): ResolvedLineC
|
|
|
184
184
|
const apiBaseUrl = (config.apiBaseUrl ?? entry?.apiBaseUrl ?? 'https://api.line.me').replace(/\/$/, '');
|
|
185
185
|
return {
|
|
186
186
|
context: 'line',
|
|
187
|
-
|
|
187
|
+
id,
|
|
188
188
|
channelSecret,
|
|
189
189
|
channelAccessToken,
|
|
190
190
|
webhookPath,
|
|
@@ -222,10 +222,10 @@ export function resolveChannel(source: LineSource): LineChannel {
|
|
|
222
222
|
* 入站归一化 → ConversationRef:LINE user → private、group → group、room → channel。
|
|
223
223
|
* LINE 没有 guild/频道容器概念,无 parent;recipient id 前缀(U/G/R)自带场景信息。
|
|
224
224
|
*/
|
|
225
|
-
export function lineInboundConversation(
|
|
225
|
+
export function lineInboundConversation(endpointKey: string, source: LineSource): ConversationRef {
|
|
226
226
|
const channel = resolveChannel(source);
|
|
227
227
|
return {
|
|
228
|
-
endpoint: { id:
|
|
228
|
+
endpoint: { id: endpointKey, adapter: endpointKey.split('\0')[0] ?? endpointKey },
|
|
229
229
|
kind: channel.channelType,
|
|
230
230
|
id: channel.channelId,
|
|
231
231
|
};
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|