@zhin.js/adapter-slack 1.1.25 → 1.1.27
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 +186 -0
- package/LICENSE +21 -0
- package/PERMITS.md +10 -17
- package/README.md +8 -4
- package/agents/slack/conversation.js +15 -0
- package/agents/slack/conversation.ts +17 -0
- package/agents/slack/messages.js +4 -0
- package/agents/slack/messages.ts +4 -0
- package/agents/slack/skills/slack-channels/tools/archive_channel/index.js +6 -7
- package/agents/slack/skills/slack-channels/tools/archive_channel/index.ts +7 -10
- package/agents/slack/skills/slack-channels/tools/invite_to_channel/index.js +5 -5
- package/agents/slack/skills/slack-channels/tools/invite_to_channel/index.ts +5 -6
- package/agents/slack/skills/slack-channels/tools/set_purpose/index.js +5 -5
- package/agents/slack/skills/slack-channels/tools/set_purpose/index.ts +5 -6
- package/agents/slack/skills/slack-channels/tools/set_topic/index.js +5 -5
- package/agents/slack/skills/slack-channels/tools/set_topic/index.ts +5 -6
- package/agents/slack/skills/slack-channels/tools/unarchive/index.js +2 -3
- package/agents/slack/skills/slack-channels/tools/unarchive/index.ts +2 -3
- package/agents/slack/skills/slack-messages/tools/add_reaction/index.js +5 -4
- package/agents/slack/skills/slack-messages/tools/add_reaction/index.ts +5 -5
- package/agents/slack/skills/slack-messages/tools/edit_message/index.js +6 -5
- package/agents/slack/skills/slack-messages/tools/edit_message/index.ts +6 -6
- package/agents/slack/skills/slack-messages/tools/pin_message/index.js +5 -5
- package/agents/slack/skills/slack-messages/tools/pin_message/index.ts +5 -6
- package/agents/slack/skills/slack-messages/tools/remove_reaction/index.js +5 -4
- package/agents/slack/skills/slack-messages/tools/remove_reaction/index.ts +5 -5
- package/agents/slack/skills/slack-messages/tools/unpin_message/index.js +5 -5
- package/agents/slack/skills/slack-messages/tools/unpin_message/index.ts +5 -6
- package/lib/console-spec.js +119 -11
- package/lib/index.d.ts +2 -2
- package/lib/index.js +2 -2
- package/lib/platform-permit.d.ts +4 -12
- package/lib/platform-permit.js +7 -41
- package/lib/protocol.d.ts +1 -5
- package/lib/protocol.js +1 -19
- package/lib/slack-message-ref.d.ts +0 -1
- package/lib/slack-message-ref.js +0 -3
- package/lib/webhook.d.ts +1 -2
- package/lib/webhook.js +12 -29
- package/package.json +24 -26
- package/plugin.js +0 -6
- package/schema.json +0 -56
- package/src/console-spec.ts +119 -11
- package/src/index.ts +1 -7
- package/src/platform-permit.ts +5 -54
- package/src/protocol.ts +2 -23
- package/src/slack-message-ref.ts +0 -4
- package/src/webhook.ts +12 -32
package/lib/webhook.js
CHANGED
|
@@ -1,23 +1,20 @@
|
|
|
1
|
+
import { readTextBody } from '@zhin.js/host-http-contract';
|
|
1
2
|
import { getLogger } from '@zhin.js/logger';
|
|
2
|
-
import { headerValue,
|
|
3
|
+
import { headerValue, verifySlackSignature, } from './protocol.js';
|
|
3
4
|
const logger = getLogger('slack');
|
|
4
5
|
export function registerSlackWebhookRoutes(http, handler) {
|
|
5
6
|
const path = handler.config.webhookPath;
|
|
6
7
|
return [
|
|
7
|
-
http.route('POST', path,
|
|
8
|
-
await handleSlackWebhookRequest(request, response, handler);
|
|
9
|
-
}, { summary: 'Slack Events API / Interactivity', tags: ['slack'] }),
|
|
8
|
+
http.route('POST', path, (request) => handleSlackWebhookRequest(request, handler), { summary: 'Slack Events API / Interactivity', tags: ['slack'] }),
|
|
10
9
|
];
|
|
11
10
|
}
|
|
12
|
-
export async function handleSlackWebhookRequest(request,
|
|
11
|
+
export async function handleSlackWebhookRequest(request, handler) {
|
|
13
12
|
try {
|
|
14
13
|
const rawBody = await readTextBody(request);
|
|
15
14
|
const timestamp = headerValue(request.headers, 'x-slack-request-timestamp');
|
|
16
15
|
const signature = headerValue(request.headers, 'x-slack-signature');
|
|
17
16
|
if (!verifySlackSignature(handler.config.signingSecret, rawBody, timestamp, signature)) {
|
|
18
|
-
|
|
19
|
-
response.end('Invalid signature');
|
|
20
|
-
return;
|
|
17
|
+
return new Response('Invalid signature', { status: 401, headers: { 'Content-Type': 'text/plain' } });
|
|
21
18
|
}
|
|
22
19
|
const contentType = headerValue(request.headers, 'content-type');
|
|
23
20
|
if (contentType.includes('application/x-www-form-urlencoded')) {
|
|
@@ -25,17 +22,13 @@ export async function handleSlackWebhookRequest(request, response, handler) {
|
|
|
25
22
|
const payloadStr = params.get('payload');
|
|
26
23
|
if (payloadStr) {
|
|
27
24
|
const payload = JSON.parse(payloadStr);
|
|
28
|
-
response.writeHead(200);
|
|
29
|
-
response.end('');
|
|
30
25
|
handler.admitInteraction(payload);
|
|
31
|
-
return;
|
|
26
|
+
return new Response('', { status: 200 });
|
|
32
27
|
}
|
|
33
28
|
const body = Object.fromEntries(params);
|
|
34
29
|
if (body.command) {
|
|
35
|
-
response.writeHead(200);
|
|
36
|
-
response.end('');
|
|
37
30
|
handler.admitSlashCommand(body);
|
|
38
|
-
return;
|
|
31
|
+
return new Response('', { status: 200 });
|
|
39
32
|
}
|
|
40
33
|
}
|
|
41
34
|
let envelope;
|
|
@@ -43,30 +36,20 @@ export async function handleSlackWebhookRequest(request, response, handler) {
|
|
|
43
36
|
envelope = JSON.parse(rawBody);
|
|
44
37
|
}
|
|
45
38
|
catch {
|
|
46
|
-
|
|
47
|
-
response.end('');
|
|
48
|
-
return;
|
|
39
|
+
return new Response('', { status: 200 });
|
|
49
40
|
}
|
|
50
41
|
if (envelope.type === 'url_verification') {
|
|
51
42
|
const challenge = envelope.challenge;
|
|
52
|
-
|
|
53
|
-
response.end(JSON.stringify({ challenge }));
|
|
54
|
-
return;
|
|
43
|
+
return new Response(JSON.stringify({ challenge }), { status: 200, headers: { 'Content-Type': 'application/json' } });
|
|
55
44
|
}
|
|
56
45
|
if (envelope.type === 'event_callback' && envelope.event) {
|
|
57
|
-
response.writeHead(200);
|
|
58
|
-
response.end('');
|
|
59
46
|
handler.handleEnvelope(envelope);
|
|
60
|
-
return;
|
|
47
|
+
return new Response('', { status: 200 });
|
|
61
48
|
}
|
|
62
|
-
|
|
63
|
-
response.end('');
|
|
49
|
+
return new Response('', { status: 200 });
|
|
64
50
|
}
|
|
65
51
|
catch (error) {
|
|
66
52
|
logger.error('Slack webhook error:', error);
|
|
67
|
-
|
|
68
|
-
response.writeHead(200);
|
|
69
|
-
response.end('');
|
|
70
|
-
}
|
|
53
|
+
return new Response('', { status: 200 });
|
|
71
54
|
}
|
|
72
55
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/adapter-slack",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.27",
|
|
4
4
|
"description": "Zhin.js Slack adapter for Plugin Runtime (Socket Mode / HTTP Events)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -33,26 +33,27 @@
|
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"@slack/socket-mode": "^2.0.7",
|
|
35
35
|
"@slack/web-api": "^7.19.0",
|
|
36
|
-
"
|
|
37
|
-
"@zhin.js/
|
|
38
|
-
"@zhin.js/
|
|
39
|
-
"@zhin.js/
|
|
40
|
-
"@zhin.js/
|
|
41
|
-
"@zhin.js/
|
|
42
|
-
"@zhin.js/
|
|
43
|
-
"@zhin.js/
|
|
44
|
-
"@zhin.js/
|
|
45
|
-
"@zhin.js/
|
|
46
|
-
"@zhin.js/
|
|
36
|
+
"zod": "^4.6.7",
|
|
37
|
+
"@zhin.js/adapter": "1.1.33",
|
|
38
|
+
"@zhin.js/agent-feature": "1.1.17",
|
|
39
|
+
"@zhin.js/console-contract": "1.1.6",
|
|
40
|
+
"@zhin.js/core": "1.2.0",
|
|
41
|
+
"@zhin.js/feature-kit": "1.1.16",
|
|
42
|
+
"@zhin.js/host-http": "1.2.0",
|
|
43
|
+
"@zhin.js/host-http-contract": "1.2.0",
|
|
44
|
+
"@zhin.js/im-contract": "1.1.14",
|
|
45
|
+
"@zhin.js/logger": "1.1.3",
|
|
46
|
+
"@zhin.js/page": "1.1.18",
|
|
47
|
+
"@zhin.js/skill": "1.1.17",
|
|
48
|
+
"@zhin.js/tool": "1.1.19"
|
|
47
49
|
},
|
|
48
50
|
"peerDependencies": {
|
|
49
|
-
"
|
|
50
|
-
"@zhin.js/
|
|
51
|
-
"@zhin.js/
|
|
52
|
-
"@zhin.js/
|
|
53
|
-
"@zhin.js/
|
|
54
|
-
"
|
|
55
|
-
"zhin.js": "^1.1.24"
|
|
51
|
+
"@zhin.js/adapter": "^1.1.33",
|
|
52
|
+
"@zhin.js/command": "^1.2.0",
|
|
53
|
+
"@zhin.js/core": "^1.2.0",
|
|
54
|
+
"@zhin.js/host-http": "^1.2.0",
|
|
55
|
+
"@zhin.js/permission": "^1.2.0",
|
|
56
|
+
"zhin.js": "^1.1.26"
|
|
56
57
|
},
|
|
57
58
|
"peerDependenciesMeta": {
|
|
58
59
|
"@zhin.js/command": {
|
|
@@ -60,17 +61,15 @@
|
|
|
60
61
|
},
|
|
61
62
|
"zhin.js": {
|
|
62
63
|
"optional": true
|
|
63
|
-
},
|
|
64
|
-
"zod": {
|
|
65
|
-
"optional": true
|
|
66
64
|
}
|
|
67
65
|
},
|
|
68
66
|
"devDependencies": {
|
|
69
67
|
"@types/node": "^26.1.2",
|
|
68
|
+
"@types/ws": "^8.18.1",
|
|
70
69
|
"typescript": "^6.0.3",
|
|
71
70
|
"vitest": "^4.1.10",
|
|
72
|
-
"
|
|
73
|
-
"zhin.js": "1.1.
|
|
71
|
+
"ws": "^8.21.1",
|
|
72
|
+
"zhin.js": "1.1.26"
|
|
74
73
|
},
|
|
75
74
|
"files": [
|
|
76
75
|
"adapters",
|
|
@@ -80,7 +79,6 @@
|
|
|
80
79
|
"schema.json",
|
|
81
80
|
"src",
|
|
82
81
|
"lib",
|
|
83
|
-
"tools",
|
|
84
82
|
"agents",
|
|
85
83
|
"README.md",
|
|
86
84
|
"CHANGELOG.md",
|
|
@@ -130,6 +128,6 @@
|
|
|
130
128
|
"scripts": {
|
|
131
129
|
"build": "tsc",
|
|
132
130
|
"clean": "rimraf lib",
|
|
133
|
-
"test": "
|
|
131
|
+
"test": "vitest run --root ../../.. plugins/adapters/slack/tests"
|
|
134
132
|
}
|
|
135
133
|
}
|
package/plugin.js
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
2
|
import { definePlugin } from 'zhin.js';
|
|
3
|
-
import { permissionHostToken } from '@zhin.js/permission';
|
|
4
|
-
import { checkSlackPlatformPermit } from "./lib/platform-permit.js";
|
|
5
3
|
import { createSlackConsoleRpc } from "./lib/console.js";
|
|
6
4
|
export default definePlugin({
|
|
7
5
|
name: 'slack',
|
|
@@ -10,12 +8,8 @@ export default definePlugin({
|
|
|
10
8
|
displayName: 'Slack Adapter',
|
|
11
9
|
},
|
|
12
10
|
setup(context) {
|
|
13
|
-
const dispose = context.resources.has(permissionHostToken)
|
|
14
|
-
? context.resources.use(permissionHostToken).registerPlatform('slack', checkSlackPlatformPermit)
|
|
15
|
-
: undefined;
|
|
16
11
|
return {
|
|
17
12
|
consoleRpc: createSlackConsoleRpc(() => context.config.get()),
|
|
18
|
-
...(dispose ? { dispose } : {}),
|
|
19
13
|
};
|
|
20
14
|
},
|
|
21
15
|
});
|
package/schema.json
CHANGED
|
@@ -17,34 +17,6 @@
|
|
|
17
17
|
"default": 15000,
|
|
18
18
|
"description": "Socket Mode client ping timeout (ms)"
|
|
19
19
|
},
|
|
20
|
-
"master": {
|
|
21
|
-
"type": [
|
|
22
|
-
"string",
|
|
23
|
-
"number"
|
|
24
|
-
],
|
|
25
|
-
"description": "框架 master(Slack user id;AI/工具权限、endpoint 管理)。endpoints[i].master 可逐项覆盖",
|
|
26
|
-
"x-i18n": {
|
|
27
|
-
"en": {
|
|
28
|
-
"description": "Framework masters (Slack user id; AI/tool permissions and endpoint management). endpoints[i].master can override this value."
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
},
|
|
32
|
-
"trusted": {
|
|
33
|
-
"type": "array",
|
|
34
|
-
"items": {
|
|
35
|
-
"type": [
|
|
36
|
-
"string",
|
|
37
|
-
"number"
|
|
38
|
-
],
|
|
39
|
-
"description": "Trusted Slack user id"
|
|
40
|
-
},
|
|
41
|
-
"description": "框架 trusted 用户列表(弱于 master)。endpoints[i].trusted 可逐项追加",
|
|
42
|
-
"x-i18n": {
|
|
43
|
-
"en": {
|
|
44
|
-
"description": "Framework trusted users, below master authority. endpoints[i].trusted can add users."
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
},
|
|
48
20
|
"endpoints": {
|
|
49
21
|
"type": "array",
|
|
50
22
|
"minItems": 1,
|
|
@@ -53,34 +25,6 @@
|
|
|
53
25
|
"type": "object",
|
|
54
26
|
"additionalProperties": false,
|
|
55
27
|
"properties": {
|
|
56
|
-
"master": {
|
|
57
|
-
"type": [
|
|
58
|
-
"string",
|
|
59
|
-
"number"
|
|
60
|
-
],
|
|
61
|
-
"description": "本 endpoint 的框架 master(Slack user id);覆盖顶层 master",
|
|
62
|
-
"x-i18n": {
|
|
63
|
-
"en": {
|
|
64
|
-
"description": "Framework masters for this endpoint (Slack user id), overriding the top-level master list."
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
},
|
|
68
|
-
"trusted": {
|
|
69
|
-
"type": "array",
|
|
70
|
-
"items": {
|
|
71
|
-
"type": [
|
|
72
|
-
"string",
|
|
73
|
-
"number"
|
|
74
|
-
],
|
|
75
|
-
"description": "Trusted Slack user id"
|
|
76
|
-
},
|
|
77
|
-
"description": "本 endpoint 的 trusted 列表",
|
|
78
|
-
"x-i18n": {
|
|
79
|
-
"en": {
|
|
80
|
-
"description": "Trusted users for this endpoint."
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
},
|
|
84
28
|
"token": {
|
|
85
29
|
"type": "string",
|
|
86
30
|
"description": "Bot User OAuth Token (xoxb-...)"
|
package/src/console-spec.ts
CHANGED
|
@@ -7,17 +7,125 @@ export const slackConsoleSpec = {
|
|
|
7
7
|
adapterKey: 'slack',
|
|
8
8
|
adapterDisplayName: 'Slack',
|
|
9
9
|
fields: [
|
|
10
|
-
{
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
10
|
+
{
|
|
11
|
+
key: 'token',
|
|
12
|
+
required: true,
|
|
13
|
+
env: true,
|
|
14
|
+
label: 'Bot User OAuth Token',
|
|
15
|
+
labelI18n: { en: 'Bot User OAuth Token' },
|
|
16
|
+
group: '基础认证',
|
|
17
|
+
groupI18n: { en: 'Basic Auth' },
|
|
18
|
+
placeholder: 'xoxb-...',
|
|
19
|
+
description: 'Bot User OAuth Token(xoxb-...)',
|
|
20
|
+
descriptionI18n: { en: 'Bot User OAuth Token (xoxb-...)' },
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
key: 'appToken',
|
|
24
|
+
env: true,
|
|
25
|
+
label: 'App-Level Token (Socket Mode)',
|
|
26
|
+
labelI18n: { en: 'App-Level Token' },
|
|
27
|
+
group: '基础认证',
|
|
28
|
+
groupI18n: { en: 'Basic Auth' },
|
|
29
|
+
placeholder: 'xapp-...',
|
|
30
|
+
description: 'App-Level Token(xapp-...,Socket Mode)',
|
|
31
|
+
descriptionI18n: { en: 'App-level token (xapp-..., Socket Mode)' },
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
key: 'signingSecret',
|
|
35
|
+
env: true,
|
|
36
|
+
label: 'Signing Secret (签名密钥)',
|
|
37
|
+
labelI18n: { en: 'Signing Secret' },
|
|
38
|
+
group: '基础认证',
|
|
39
|
+
groupI18n: { en: 'Basic Auth' },
|
|
40
|
+
placeholder: '32 位十六进制签名密钥',
|
|
41
|
+
placeholderI18n: { en: 'Slack app signing secret' },
|
|
42
|
+
description: '签名密钥(HTTP Events,socketMode: false 必填)',
|
|
43
|
+
descriptionI18n: { en: 'Signing secret (required for HTTP Events)' },
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
key: 'socketMode',
|
|
47
|
+
type: 'boolean',
|
|
48
|
+
label: '启用 Socket Mode',
|
|
49
|
+
labelI18n: { en: 'Enable Socket Mode' },
|
|
50
|
+
group: '连接与回调',
|
|
51
|
+
groupI18n: { en: 'Connection & Webhook' },
|
|
52
|
+
description: '开启后使用 WebSocket 长连接;关闭则使用 HTTP Events 回调',
|
|
53
|
+
descriptionI18n: { en: 'Socket Mode; false selects HTTP Events' },
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
key: 'webhookPath',
|
|
57
|
+
label: 'HTTP Events 本地路由',
|
|
58
|
+
labelI18n: { en: 'HTTP Events Local Path' },
|
|
59
|
+
group: '连接与回调',
|
|
60
|
+
groupI18n: { en: 'Connection & Webhook' },
|
|
61
|
+
placeholder: '/slack/events',
|
|
62
|
+
description: 'HTTP Events 模式下监听的本地路由',
|
|
63
|
+
descriptionI18n: { en: 'HTTP Events route' },
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
key: 'clientPingTimeout',
|
|
67
|
+
type: 'number',
|
|
68
|
+
label: '心跳超时时间 (ms)',
|
|
69
|
+
labelI18n: { en: 'Client Ping Timeout (ms)' },
|
|
70
|
+
group: '连接与回调',
|
|
71
|
+
groupI18n: { en: 'Connection & Webhook' },
|
|
72
|
+
placeholder: '5000',
|
|
73
|
+
description: 'Socket Mode 客户端心跳超时毫秒数',
|
|
74
|
+
descriptionI18n: { en: 'SDK client ping timeout in milliseconds' },
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
key: 'streaming',
|
|
78
|
+
type: 'boolean',
|
|
79
|
+
label: '流式回复',
|
|
80
|
+
labelI18n: { en: 'Streaming Reply' },
|
|
81
|
+
group: '流式体验与代理',
|
|
82
|
+
groupI18n: { en: 'Streaming & Proxy' },
|
|
83
|
+
description: '生成时逐步显示回复',
|
|
84
|
+
descriptionI18n: { en: 'Show replies progressively while generating' },
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
key: 'streamingIntervalMs',
|
|
88
|
+
type: 'number',
|
|
89
|
+
label: '流式刷新间隔 (ms)',
|
|
90
|
+
labelI18n: { en: 'Streaming Interval (ms)' },
|
|
91
|
+
group: '流式体验与代理',
|
|
92
|
+
groupI18n: { en: 'Streaming & Proxy' },
|
|
93
|
+
placeholder: '500',
|
|
94
|
+
description: '回复更新间隔(毫秒)',
|
|
95
|
+
descriptionI18n: { en: 'Reply update interval in milliseconds' },
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
key: 'webApiProxy.port',
|
|
99
|
+
type: 'number',
|
|
100
|
+
label: 'Web API 代理端口',
|
|
101
|
+
labelI18n: { en: 'Web API Proxy Port' },
|
|
102
|
+
group: '流式体验与代理',
|
|
103
|
+
groupI18n: { en: 'Streaming & Proxy' },
|
|
104
|
+
placeholder: '例如 7890',
|
|
105
|
+
description: '本地回环 Web API 代理端口(可选)',
|
|
106
|
+
descriptionI18n: { en: 'Configured Web API loopback proxy port' },
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
key: 'streamProxy.port',
|
|
110
|
+
type: 'number',
|
|
111
|
+
label: 'Socket Mode 代理端口',
|
|
112
|
+
labelI18n: { en: 'Socket Mode Proxy Port' },
|
|
113
|
+
group: '流式体验与代理',
|
|
114
|
+
groupI18n: { en: 'Streaming & Proxy' },
|
|
115
|
+
placeholder: '例如 7890',
|
|
116
|
+
description: '本地回环 Socket Mode 代理端口(可选)',
|
|
117
|
+
descriptionI18n: { en: 'Configured Socket Mode loopback proxy port' },
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
key: 'streamProxy.serverName',
|
|
121
|
+
label: 'Socket Mode TLS 主机名',
|
|
122
|
+
labelI18n: { en: 'Socket Mode TLS Server Name' },
|
|
123
|
+
group: '流式体验与代理',
|
|
124
|
+
groupI18n: { en: 'Streaming & Proxy' },
|
|
125
|
+
placeholder: 'wss-primary.slack.com',
|
|
126
|
+
description: '代理校验的 Socket Mode TLS 主机名(可选)',
|
|
127
|
+
descriptionI18n: { en: 'Expected Socket Mode TLS hostname' },
|
|
128
|
+
},
|
|
21
129
|
] as readonly AdapterConsoleField[],
|
|
22
130
|
docsUrl: 'https://zhin.js.org/adapters/slack',
|
|
23
131
|
readPermission: 'slack:read',
|
package/src/index.ts
CHANGED
|
@@ -6,7 +6,6 @@ export {
|
|
|
6
6
|
headerValue,
|
|
7
7
|
keyboardToBlockKitBlocks,
|
|
8
8
|
normalizeWebhookPath,
|
|
9
|
-
readTextBody,
|
|
10
9
|
resolveSlackChannelType,
|
|
11
10
|
resolveSlackConfig,
|
|
12
11
|
slackInboundConversation,
|
|
@@ -41,12 +40,7 @@ export {
|
|
|
41
40
|
type SlackUserInfo,
|
|
42
41
|
} from './client.js';
|
|
43
42
|
|
|
44
|
-
export {
|
|
45
|
-
checkSlackPlatformPermit,
|
|
46
|
-
normalizeSlackSenderForPermit,
|
|
47
|
-
platformPermit,
|
|
48
|
-
slackGroupPermitResolver,
|
|
49
|
-
} from './platform-permit.js';
|
|
43
|
+
export { platformPermit } from './platform-permit.js';
|
|
50
44
|
|
|
51
45
|
export { normalizeSlackReactionName } from './slack-reaction.js';
|
|
52
46
|
export { markdownToMrkdwn, mrkdwnToPlainFallback, splitMrkdwnText } from './markdown-to-mrkdwn.js';
|
package/src/platform-permit.ts
CHANGED
|
@@ -1,59 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Slack platform permit
|
|
2
|
+
* Slack platform permit helper(ADR 0056 §5)。
|
|
3
|
+
*
|
|
4
|
+
* 平台身份由适配器上报到 `sender.platform`,permit 由内置评估器(`@zhin.js/permission`)判断;
|
|
5
|
+
* 适配器不再导出自有的 permit 解析器或检查函数。
|
|
3
6
|
*/
|
|
4
|
-
import type { PermissionSubject } from '@zhin.js/permission';
|
|
5
|
-
|
|
6
|
-
const ADAPTER = 'slack';
|
|
7
7
|
|
|
8
8
|
export function platformPermit(perm: string): string {
|
|
9
|
-
return `platform
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const FACTORY_PERM_MAP: Record<string, string> = {
|
|
13
|
-
scene_admin: 'channel_manager',
|
|
14
|
-
scene_owner: 'workspace_owner',
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export function slackGroupPermitResolver(logicalPerm: string): string {
|
|
18
|
-
return platformPermit(FACTORY_PERM_MAP[logicalPerm] ?? logicalPerm);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function normalizeSlackSenderForPermit(input: {
|
|
22
|
-
isWorkspaceOwner?: boolean;
|
|
23
|
-
isWorkspaceAdmin?: boolean;
|
|
24
|
-
isChannelManager?: boolean;
|
|
25
|
-
}): { role?: string; permissions?: string[] } {
|
|
26
|
-
const permissions: string[] = [];
|
|
27
|
-
if (input.isWorkspaceOwner) {
|
|
28
|
-
permissions.push('workspace_owner', 'workspace_admin', 'channel_manager');
|
|
29
|
-
return { role: 'owner', permissions };
|
|
30
|
-
}
|
|
31
|
-
if (input.isWorkspaceAdmin) {
|
|
32
|
-
permissions.push('workspace_admin', 'channel_manager');
|
|
33
|
-
return { role: 'admin', permissions };
|
|
34
|
-
}
|
|
35
|
-
if (input.isChannelManager) {
|
|
36
|
-
permissions.push('channel_manager');
|
|
37
|
-
return { role: 'channel_admin', permissions };
|
|
38
|
-
}
|
|
39
|
-
return { role: 'member', permissions };
|
|
9
|
+
return `platform-permission:${perm}`;
|
|
40
10
|
}
|
|
41
|
-
|
|
42
|
-
export function checkSlackPlatformPermit(perm: string, subject: PermissionSubject): boolean {
|
|
43
|
-
const role = subject.sender?.role?.[0];
|
|
44
|
-
const permissions = subject.sender?.permissions ?? [];
|
|
45
|
-
const has = (t: string) => permissions.includes(t);
|
|
46
|
-
|
|
47
|
-
switch (perm) {
|
|
48
|
-
case 'workspace_owner':
|
|
49
|
-
return role === 'owner' || has('workspace_owner');
|
|
50
|
-
case 'workspace_admin':
|
|
51
|
-
return role === 'owner' || role === 'admin' || has('workspace_admin') || has('workspace_owner');
|
|
52
|
-
case 'channel_manager':
|
|
53
|
-
return role === 'owner' || role === 'admin' || role === 'channel_admin'
|
|
54
|
-
|| has('channel_manager') || has('workspace_admin') || has('workspace_owner');
|
|
55
|
-
default:
|
|
56
|
-
return false;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
package/src/protocol.ts
CHANGED
|
@@ -4,7 +4,6 @@ import type { EndpointProfileConfiguration } from 'zhin.js/adapter';
|
|
|
4
4
|
* Canonicalization is owned by gateway/core before endpoint.send.
|
|
5
5
|
*/
|
|
6
6
|
import { createHmac, timingSafeEqual } from 'node:crypto';
|
|
7
|
-
import type { IncomingMessage } from 'node:http';
|
|
8
7
|
import { isMediaRef, type MediaRef, type ConversationKind, type ConversationRef } from '@zhin.js/im-contract';
|
|
9
8
|
import { formatCompact, getLogger } from '@zhin.js/logger';
|
|
10
9
|
import { mrkdwnToMarkdown } from './mrkdwn-to-markdown.js';
|
|
@@ -417,30 +416,10 @@ export function verifySlackSignature(
|
|
|
417
416
|
return timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
|
|
418
417
|
}
|
|
419
418
|
|
|
420
|
-
export async function readTextBody(
|
|
421
|
-
request: IncomingMessage,
|
|
422
|
-
options: { readonly limit?: number } = {},
|
|
423
|
-
): Promise<string> {
|
|
424
|
-
const limit = options.limit ?? 1_048_576;
|
|
425
|
-
const chunks: Buffer[] = [];
|
|
426
|
-
let size = 0;
|
|
427
|
-
for await (const chunk of request) {
|
|
428
|
-
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
429
|
-
size += buffer.length;
|
|
430
|
-
if (size > limit) {
|
|
431
|
-
request.destroy();
|
|
432
|
-
throw new Error(`Request body exceeds ${limit} bytes`);
|
|
433
|
-
}
|
|
434
|
-
chunks.push(buffer);
|
|
435
|
-
}
|
|
436
|
-
return Buffer.concat(chunks).toString('utf8');
|
|
437
|
-
}
|
|
438
419
|
|
|
439
420
|
export function headerValue(
|
|
440
|
-
headers:
|
|
421
|
+
headers: Headers,
|
|
441
422
|
name: string,
|
|
442
423
|
): string {
|
|
443
|
-
|
|
444
|
-
if (Array.isArray(raw)) return raw[0] ?? '';
|
|
445
|
-
return raw ?? '';
|
|
424
|
+
return headers.get(name) ?? '';
|
|
446
425
|
}
|
package/src/slack-message-ref.ts
CHANGED
package/src/webhook.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Slack webhook HTTP: signature → parse → admit.
|
|
3
3
|
*/
|
|
4
|
-
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
5
4
|
import type { HttpHost, HttpRouteRegistration } from '@zhin.js/host-http';
|
|
5
|
+
import { readTextBody } from '@zhin.js/host-http-contract';
|
|
6
6
|
import { formatCompact, getLogger } from '@zhin.js/logger';
|
|
7
7
|
import {
|
|
8
8
|
headerValue,
|
|
9
|
-
readTextBody,
|
|
10
9
|
verifySlackSignature,
|
|
11
10
|
type ResolvedSlackConfig,
|
|
12
11
|
type SlackInteractionPayload,
|
|
@@ -29,26 +28,21 @@ export function registerSlackWebhookRoutes(
|
|
|
29
28
|
): HttpRouteRegistration[] {
|
|
30
29
|
const path = handler.config.webhookPath;
|
|
31
30
|
return [
|
|
32
|
-
http.route('POST', path,
|
|
33
|
-
await handleSlackWebhookRequest(request, response, handler);
|
|
34
|
-
}, { summary: 'Slack Events API / Interactivity', tags: ['slack'] }),
|
|
31
|
+
http.route('POST', path, (request) => handleSlackWebhookRequest(request, handler), { summary: 'Slack Events API / Interactivity', tags: ['slack'] }),
|
|
35
32
|
];
|
|
36
33
|
}
|
|
37
34
|
|
|
38
35
|
export async function handleSlackWebhookRequest(
|
|
39
|
-
request:
|
|
40
|
-
response: ServerResponse,
|
|
36
|
+
request: Request,
|
|
41
37
|
handler: SlackWebhookHandler,
|
|
42
|
-
): Promise<
|
|
38
|
+
): Promise<Response> {
|
|
43
39
|
try {
|
|
44
40
|
const rawBody = await readTextBody(request);
|
|
45
41
|
const timestamp = headerValue(request.headers, 'x-slack-request-timestamp');
|
|
46
42
|
const signature = headerValue(request.headers, 'x-slack-signature');
|
|
47
43
|
|
|
48
44
|
if (!verifySlackSignature(handler.config.signingSecret, rawBody, timestamp, signature)) {
|
|
49
|
-
|
|
50
|
-
response.end('Invalid signature');
|
|
51
|
-
return;
|
|
45
|
+
return new Response('Invalid signature', { status: 401, headers: { 'Content-Type': 'text/plain' } });
|
|
52
46
|
}
|
|
53
47
|
|
|
54
48
|
const contentType = headerValue(request.headers, 'content-type');
|
|
@@ -57,17 +51,13 @@ export async function handleSlackWebhookRequest(
|
|
|
57
51
|
const payloadStr = params.get('payload');
|
|
58
52
|
if (payloadStr) {
|
|
59
53
|
const payload = JSON.parse(payloadStr) as SlackInteractionPayload;
|
|
60
|
-
response.writeHead(200);
|
|
61
|
-
response.end('');
|
|
62
54
|
handler.admitInteraction(payload);
|
|
63
|
-
return;
|
|
55
|
+
return new Response('', { status: 200 });
|
|
64
56
|
}
|
|
65
57
|
const body = Object.fromEntries(params) as unknown as SlackSlashCommand;
|
|
66
58
|
if (body.command) {
|
|
67
|
-
response.writeHead(200);
|
|
68
|
-
response.end('');
|
|
69
59
|
handler.admitSlashCommand(body);
|
|
70
|
-
return;
|
|
60
|
+
return new Response('', { status: 200 });
|
|
71
61
|
}
|
|
72
62
|
}
|
|
73
63
|
|
|
@@ -75,32 +65,22 @@ export async function handleSlackWebhookRequest(
|
|
|
75
65
|
try {
|
|
76
66
|
envelope = JSON.parse(rawBody) as Record<string, unknown>;
|
|
77
67
|
} catch {
|
|
78
|
-
|
|
79
|
-
response.end('');
|
|
80
|
-
return;
|
|
68
|
+
return new Response('', { status: 200 });
|
|
81
69
|
}
|
|
82
70
|
|
|
83
71
|
if (envelope.type === 'url_verification') {
|
|
84
72
|
const challenge = (envelope as unknown as SlackUrlVerification).challenge;
|
|
85
|
-
|
|
86
|
-
response.end(JSON.stringify({ challenge }));
|
|
87
|
-
return;
|
|
73
|
+
return new Response(JSON.stringify({ challenge }), { status: 200, headers: { 'Content-Type': 'application/json' } });
|
|
88
74
|
}
|
|
89
75
|
|
|
90
76
|
if (envelope.type === 'event_callback' && envelope.event) {
|
|
91
|
-
response.writeHead(200);
|
|
92
|
-
response.end('');
|
|
93
77
|
handler.handleEnvelope(envelope);
|
|
94
|
-
return;
|
|
78
|
+
return new Response('', { status: 200 });
|
|
95
79
|
}
|
|
96
80
|
|
|
97
|
-
|
|
98
|
-
response.end('');
|
|
81
|
+
return new Response('', { status: 200 });
|
|
99
82
|
} catch (error) {
|
|
100
83
|
logger.error('Slack webhook error:', error);
|
|
101
|
-
|
|
102
|
-
response.writeHead(200);
|
|
103
|
-
response.end('');
|
|
104
|
-
}
|
|
84
|
+
return new Response('', { status: 200 });
|
|
105
85
|
}
|
|
106
86
|
}
|