@zhin.js/adapter-slack 8.0.0 → 8.0.1
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 +11 -0
- package/README.md +6 -0
- package/lib/endpoint.js +2 -3
- package/lib/slack-outbound.js +26 -24
- package/package.json +9 -9
- package/src/endpoint.ts +2 -2
- package/src/slack-outbound.ts +28 -25
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 8.0.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- f7ee28d: Require a platform message timestamp for each Slack message chunk instead of fabricating a successful receipt. Stop subsequent chunks when delivery cannot be confirmed, and propagate file upload failures instead of silently reporting partial delivery as success.
|
|
8
|
+
- Updated dependencies [f7ee28d]
|
|
9
|
+
- @zhin.js/adapter@1.2.2
|
|
10
|
+
- @zhin.js/core@1.5.16
|
|
11
|
+
- zhin.js@7.0.1
|
|
12
|
+
- @zhin.js/agent@1.2.1
|
|
13
|
+
|
|
3
14
|
## 8.0.0
|
|
4
15
|
|
|
5
16
|
### Patch Changes
|
package/README.md
CHANGED
package/lib/endpoint.js
CHANGED
|
@@ -138,9 +138,8 @@ export class SlackEndpoint extends Endpoint {
|
|
|
138
138
|
const channel = conversation.id;
|
|
139
139
|
const threadTs = conversation.threadId;
|
|
140
140
|
const result = await sendSlackContent(this.#client, payload, { channel, threadTs }, this.#logger);
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
return formatSlackMessageRef(channel, result.ts || String(Date.now()));
|
|
141
|
+
this.trackMessageChannel(result.ts, channel);
|
|
142
|
+
return formatSlackMessageRef(channel, result.ts);
|
|
144
143
|
}
|
|
145
144
|
/** Test / internal: admit a message event when open. */
|
|
146
145
|
admit(event) {
|
package/lib/slack-outbound.js
CHANGED
|
@@ -7,10 +7,11 @@ export async function sendSlackContent(client, content, opts, logger) {
|
|
|
7
7
|
const textDelivery = applyTextMrkdwnBlocks(wire.text, blocks);
|
|
8
8
|
for (const pf of wire.files) {
|
|
9
9
|
try {
|
|
10
|
-
await uploadFile(client, opts.channel, pf, opts.threadTs
|
|
10
|
+
await uploadFile(client, opts.channel, pf, opts.threadTs);
|
|
11
11
|
}
|
|
12
12
|
catch (e) {
|
|
13
13
|
logger.error('Failed to upload file:', e);
|
|
14
|
+
throw e;
|
|
14
15
|
}
|
|
15
16
|
}
|
|
16
17
|
return postSlackMessage(client, {
|
|
@@ -62,7 +63,7 @@ async function postSlackMessage(client, opts) {
|
|
|
62
63
|
...(threadTs ? { thread_ts: threadTs } : {}),
|
|
63
64
|
...(attachments.length > 0 ? { attachments } : {}),
|
|
64
65
|
});
|
|
65
|
-
return { ts: result
|
|
66
|
+
return { ts: requireMessageTimestamp(result) };
|
|
66
67
|
}
|
|
67
68
|
for (let offset = 0; offset < blocks.length; offset += SLACK_MAX_BLOCKS_PER_MESSAGE) {
|
|
68
69
|
const chunk = blocks.slice(offset, offset + SLACK_MAX_BLOCKS_PER_MESSAGE);
|
|
@@ -73,7 +74,7 @@ async function postSlackMessage(client, opts) {
|
|
|
73
74
|
...(threadTs ? { thread_ts: threadTs } : {}),
|
|
74
75
|
...(offset === 0 && attachments.length > 0 ? { attachments } : {}),
|
|
75
76
|
});
|
|
76
|
-
const ts = result
|
|
77
|
+
const ts = requireMessageTimestamp(result);
|
|
77
78
|
if (!firstTs)
|
|
78
79
|
firstTs = ts;
|
|
79
80
|
if (!threadTs)
|
|
@@ -81,29 +82,30 @@ async function postSlackMessage(client, opts) {
|
|
|
81
82
|
}
|
|
82
83
|
return { ts: firstTs };
|
|
83
84
|
}
|
|
84
|
-
|
|
85
|
+
function requireMessageTimestamp(result) {
|
|
86
|
+
if (typeof result?.ts !== 'string' || !result.ts.trim()) {
|
|
87
|
+
throw new Error('Slack chat.postMessage returned no valid ts; delivery is unconfirmed');
|
|
88
|
+
}
|
|
89
|
+
return result.ts;
|
|
90
|
+
}
|
|
91
|
+
async function uploadFile(client, channel, file, threadTs) {
|
|
85
92
|
if (!client.filesUploadV2)
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
buffer = await readFile(file.path);
|
|
92
|
-
}
|
|
93
|
-
if (!buffer && file.url) {
|
|
94
|
-
const res = await fetch(file.url);
|
|
95
|
-
if (!res.ok)
|
|
96
|
-
throw new Error(`fetch ${file.url}: ${res.status}`);
|
|
97
|
-
buffer = Buffer.from(await res.arrayBuffer());
|
|
98
|
-
}
|
|
99
|
-
if (!buffer)
|
|
100
|
-
return;
|
|
101
|
-
await client.filesUploadV2(threadTs
|
|
102
|
-
? { channel_id: channel, file: buffer, filename: file.name ?? 'file', thread_ts: threadTs }
|
|
103
|
-
: { channel_id: channel, file: buffer, filename: file.name ?? 'file' });
|
|
93
|
+
throw new Error('Slack client does not support file uploads');
|
|
94
|
+
let buffer = file.buffer;
|
|
95
|
+
if (!buffer && file.path) {
|
|
96
|
+
const { readFile } = await import('node:fs/promises');
|
|
97
|
+
buffer = await readFile(file.path);
|
|
104
98
|
}
|
|
105
|
-
|
|
106
|
-
|
|
99
|
+
if (!buffer && file.url) {
|
|
100
|
+
const res = await fetch(file.url);
|
|
101
|
+
if (!res.ok)
|
|
102
|
+
throw new Error(`fetch ${file.url}: ${res.status}`);
|
|
103
|
+
buffer = Buffer.from(await res.arrayBuffer());
|
|
107
104
|
}
|
|
105
|
+
if (!buffer)
|
|
106
|
+
throw new Error('Slack file contains no uploadable content');
|
|
107
|
+
await client.filesUploadV2(threadTs
|
|
108
|
+
? { channel_id: channel, file: buffer, filename: file.name ?? 'file', thread_ts: threadTs }
|
|
109
|
+
: { channel_id: channel, file: buffer, filename: file.name ?? 'file' });
|
|
108
110
|
}
|
|
109
111
|
export { keyboardToBlockKitBlocks };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/adapter-slack",
|
|
3
|
-
"version": "8.0.
|
|
3
|
+
"version": "8.0.1",
|
|
4
4
|
"description": "Zhin.js Slack adapter for Plugin Runtime (Socket Mode / HTTP Events)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@slack/socket-mode": "^2.0.7",
|
|
36
36
|
"@slack/web-api": "^7.19.0",
|
|
37
|
-
"@zhin.js/adapter": "1.2.
|
|
38
|
-
"@zhin.js/core": "1.5.
|
|
37
|
+
"@zhin.js/adapter": "1.2.2",
|
|
38
|
+
"@zhin.js/core": "1.5.16",
|
|
39
39
|
"@zhin.js/feature-kit": "1.0.13",
|
|
40
40
|
"@zhin.js/host-http": "1.0.17",
|
|
41
41
|
"@zhin.js/im-contract": "1.0.4",
|
|
@@ -43,13 +43,13 @@
|
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
|
45
45
|
"zod": "^4.0.0",
|
|
46
|
-
"@zhin.js/adapter": "1.2.
|
|
47
|
-
"@zhin.js/agent": "1.2.
|
|
46
|
+
"@zhin.js/adapter": "1.2.2",
|
|
47
|
+
"@zhin.js/agent": "1.2.1",
|
|
48
48
|
"@zhin.js/command": "1.0.16",
|
|
49
|
-
"@zhin.js/core": "1.5.
|
|
49
|
+
"@zhin.js/core": "1.5.16",
|
|
50
50
|
"@zhin.js/host-http": "1.0.17",
|
|
51
51
|
"@zhin.js/permission": "1.0.4",
|
|
52
|
-
"zhin.js": "7.0.
|
|
52
|
+
"zhin.js": "7.0.1"
|
|
53
53
|
},
|
|
54
54
|
"peerDependenciesMeta": {
|
|
55
55
|
"@zhin.js/agent": {
|
|
@@ -70,8 +70,8 @@
|
|
|
70
70
|
"typescript": "^6.0.3",
|
|
71
71
|
"vitest": "^4.1.10",
|
|
72
72
|
"zod": "^4.4.3",
|
|
73
|
-
"@zhin.js/agent": "1.2.
|
|
74
|
-
"zhin.js": "7.0.
|
|
73
|
+
"@zhin.js/agent": "1.2.1",
|
|
74
|
+
"zhin.js": "7.0.1"
|
|
75
75
|
},
|
|
76
76
|
"files": [
|
|
77
77
|
"adapters",
|
package/src/endpoint.ts
CHANGED
|
@@ -233,8 +233,8 @@ export class SlackEndpoint extends Endpoint<SlackWebClientLike> implements Slack
|
|
|
233
233
|
{ channel, threadTs },
|
|
234
234
|
this.#logger,
|
|
235
235
|
);
|
|
236
|
-
|
|
237
|
-
return formatSlackMessageRef(channel, result.ts
|
|
236
|
+
this.trackMessageChannel(result.ts, channel);
|
|
237
|
+
return formatSlackMessageRef(channel, result.ts);
|
|
238
238
|
}
|
|
239
239
|
|
|
240
240
|
/** Test / internal: admit a message event when open. */
|
package/src/slack-outbound.ts
CHANGED
|
@@ -36,9 +36,10 @@ export async function sendSlackContent(
|
|
|
36
36
|
|
|
37
37
|
for (const pf of wire.files) {
|
|
38
38
|
try {
|
|
39
|
-
await uploadFile(client, opts.channel, pf, opts.threadTs
|
|
39
|
+
await uploadFile(client, opts.channel, pf, opts.threadTs);
|
|
40
40
|
} catch (e) {
|
|
41
41
|
logger.error('Failed to upload file:', e);
|
|
42
|
+
throw e;
|
|
42
43
|
}
|
|
43
44
|
}
|
|
44
45
|
|
|
@@ -113,7 +114,7 @@ async function postSlackMessage(
|
|
|
113
114
|
...(threadTs ? { thread_ts: threadTs } : {}),
|
|
114
115
|
...(attachments.length > 0 ? { attachments } : {}),
|
|
115
116
|
});
|
|
116
|
-
return { ts: result
|
|
117
|
+
return { ts: requireMessageTimestamp(result) };
|
|
117
118
|
}
|
|
118
119
|
|
|
119
120
|
for (let offset = 0; offset < blocks.length; offset += SLACK_MAX_BLOCKS_PER_MESSAGE) {
|
|
@@ -125,7 +126,7 @@ async function postSlackMessage(
|
|
|
125
126
|
...(threadTs ? { thread_ts: threadTs } : {}),
|
|
126
127
|
...(offset === 0 && attachments.length > 0 ? { attachments } : {}),
|
|
127
128
|
});
|
|
128
|
-
const ts = result
|
|
129
|
+
const ts = requireMessageTimestamp(result);
|
|
129
130
|
if (!firstTs) firstTs = ts;
|
|
130
131
|
if (!threadTs) threadTs = ts;
|
|
131
132
|
}
|
|
@@ -133,35 +134,37 @@ async function postSlackMessage(
|
|
|
133
134
|
return { ts: firstTs };
|
|
134
135
|
}
|
|
135
136
|
|
|
137
|
+
function requireMessageTimestamp(result: { ts?: string } | null | undefined): string {
|
|
138
|
+
if (typeof result?.ts !== 'string' || !result.ts.trim()) {
|
|
139
|
+
throw new Error('Slack chat.postMessage returned no valid ts; delivery is unconfirmed');
|
|
140
|
+
}
|
|
141
|
+
return result.ts;
|
|
142
|
+
}
|
|
143
|
+
|
|
136
144
|
async function uploadFile(
|
|
137
145
|
client: SlackChatClient,
|
|
138
146
|
channel: string,
|
|
139
147
|
file: { buffer?: Buffer; url?: string; path?: string; name?: string },
|
|
140
148
|
threadTs?: string,
|
|
141
|
-
logger?: Logger,
|
|
142
149
|
): Promise<void> {
|
|
143
|
-
if (!client.filesUploadV2)
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
buffer = Buffer.from(await res.arrayBuffer());
|
|
154
|
-
}
|
|
155
|
-
if (!buffer) return;
|
|
156
|
-
|
|
157
|
-
await client.filesUploadV2(
|
|
158
|
-
threadTs
|
|
159
|
-
? { channel_id: channel, file: buffer, filename: file.name ?? 'file', thread_ts: threadTs }
|
|
160
|
-
: { channel_id: channel, file: buffer, filename: file.name ?? 'file' },
|
|
161
|
-
);
|
|
162
|
-
} catch (e) {
|
|
163
|
-
logger?.error('File upload failed:', e);
|
|
150
|
+
if (!client.filesUploadV2) throw new Error('Slack client does not support file uploads');
|
|
151
|
+
let buffer = file.buffer;
|
|
152
|
+
if (!buffer && file.path) {
|
|
153
|
+
const { readFile } = await import('node:fs/promises');
|
|
154
|
+
buffer = await readFile(file.path);
|
|
155
|
+
}
|
|
156
|
+
if (!buffer && file.url) {
|
|
157
|
+
const res = await fetch(file.url);
|
|
158
|
+
if (!res.ok) throw new Error(`fetch ${file.url}: ${res.status}`);
|
|
159
|
+
buffer = Buffer.from(await res.arrayBuffer());
|
|
164
160
|
}
|
|
161
|
+
if (!buffer) throw new Error('Slack file contains no uploadable content');
|
|
162
|
+
|
|
163
|
+
await client.filesUploadV2(
|
|
164
|
+
threadTs
|
|
165
|
+
? { channel_id: channel, file: buffer, filename: file.name ?? 'file', thread_ts: threadTs }
|
|
166
|
+
: { channel_id: channel, file: buffer, filename: file.name ?? 'file' },
|
|
167
|
+
);
|
|
165
168
|
}
|
|
166
169
|
|
|
167
170
|
export { keyboardToBlockKitBlocks };
|