@zhin.js/adapter-slack 7.0.8 → 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 CHANGED
@@ -1,5 +1,25 @@
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
+
14
+ ## 8.0.0
15
+
16
+ ### Patch Changes
17
+
18
+ - Updated dependencies [12325e1]
19
+ - Updated dependencies [d19e976]
20
+ - @zhin.js/agent@1.2.0
21
+ - zhin.js@7.0.0
22
+
3
23
  ## 7.0.8
4
24
 
5
25
  ### Patch Changes
package/README.md CHANGED
@@ -138,3 +138,9 @@ HTTP 模式下 Runtime Host(`http`)须已 listen;Slack App 的 Event Subsc
138
138
  ## 许可证
139
139
 
140
140
  MIT
141
+
142
+ ## 发送失败与回执
143
+
144
+ 成功发送返回平台 `ts` 组成的消息引用,不以本地时间替代缺失回执。
145
+ 文件上传失败会让本次发送失败;分段消息中某段缺少有效回执时,停止后续分段。
146
+ 前面已成功的文件或分段不会自动回滚;发送失败可能表示部分已送达,请先核对目标会话再重试。
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
- if (result.ts)
142
- this.trackMessageChannel(result.ts, channel);
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) {
@@ -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, logger);
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.ts ?? '' };
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.ts ?? '';
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
- async function uploadFile(client, channel, file, threadTs, logger) {
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
- return;
87
- try {
88
- let buffer = file.buffer;
89
- if (!buffer && file.path) {
90
- const { readFile } = await import('node:fs/promises');
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
- catch (e) {
106
- logger?.error('File upload failed:', e);
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": "7.0.8",
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.1",
38
- "@zhin.js/core": "1.5.15",
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.1",
47
- "@zhin.js/agent": "1.1.22",
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.15",
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": "6.0.15"
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.1.22",
74
- "zhin.js": "6.0.15"
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
- if (result.ts) this.trackMessageChannel(result.ts, channel);
237
- return formatSlackMessageRef(channel, result.ts || String(Date.now()));
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. */
@@ -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, logger);
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.ts ?? '' };
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.ts ?? '';
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) return;
144
- try {
145
- let buffer = file.buffer;
146
- if (!buffer && file.path) {
147
- const { readFile } = await import('node:fs/promises');
148
- buffer = await readFile(file.path);
149
- }
150
- if (!buffer && file.url) {
151
- const res = await fetch(file.url);
152
- if (!res.ok) throw new Error(`fetch ${file.url}: ${res.status}`);
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 };