@chat-adapter/slack 4.13.0 → 4.13.2
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/README.md +9 -195
- package/dist/index.d.ts +39 -39
- package/dist/index.js +72 -42
- package/dist/index.js.map +1 -1
- package/package.json +7 -8
package/dist/index.js
CHANGED
|
@@ -80,8 +80,11 @@ function convertChildToBlocks(child) {
|
|
|
80
80
|
return [];
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
|
+
function markdownToMrkdwn(text) {
|
|
84
|
+
return text.replace(/\*\*(.+?)\*\*/g, "*$1*");
|
|
85
|
+
}
|
|
83
86
|
function convertTextToBlock(element) {
|
|
84
|
-
const text = convertEmoji(element.content);
|
|
87
|
+
const text = markdownToMrkdwn(convertEmoji(element.content));
|
|
85
88
|
let formattedText = text;
|
|
86
89
|
if (element.style === "bold") {
|
|
87
90
|
formattedText = `*${text}*`;
|
|
@@ -238,8 +241,8 @@ function convertFieldsToBlock(element) {
|
|
|
238
241
|
for (const field of element.children) {
|
|
239
242
|
fields.push({
|
|
240
243
|
type: "mrkdwn",
|
|
241
|
-
text: `*${convertEmoji(field.label)}*
|
|
242
|
-
${convertEmoji(field.value)}`
|
|
244
|
+
text: `*${markdownToMrkdwn(convertEmoji(field.label))}*
|
|
245
|
+
${markdownToMrkdwn(convertEmoji(field.value))}`
|
|
243
246
|
});
|
|
244
247
|
}
|
|
245
248
|
return {
|
|
@@ -260,6 +263,7 @@ import crypto from "crypto";
|
|
|
260
263
|
var ALGORITHM = "aes-256-gcm";
|
|
261
264
|
var IV_LENGTH = 12;
|
|
262
265
|
var AUTH_TAG_LENGTH = 16;
|
|
266
|
+
var HEX_KEY_PATTERN = /^[0-9a-fA-F]{64}$/;
|
|
263
267
|
function encryptToken(plaintext, key) {
|
|
264
268
|
const iv = crypto.randomBytes(IV_LENGTH);
|
|
265
269
|
const cipher = crypto.createCipheriv(ALGORITHM, key, iv, {
|
|
@@ -290,13 +294,15 @@ function decryptToken(encrypted, key) {
|
|
|
290
294
|
]).toString("utf8");
|
|
291
295
|
}
|
|
292
296
|
function isEncryptedTokenData(value) {
|
|
293
|
-
if (!value || typeof value !== "object")
|
|
297
|
+
if (!value || typeof value !== "object") {
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
294
300
|
const obj = value;
|
|
295
301
|
return typeof obj.iv === "string" && typeof obj.data === "string" && typeof obj.tag === "string";
|
|
296
302
|
}
|
|
297
303
|
function decodeKey(rawKey) {
|
|
298
304
|
const trimmed = rawKey.trim();
|
|
299
|
-
const isHex =
|
|
305
|
+
const isHex = HEX_KEY_PATTERN.test(trimmed);
|
|
300
306
|
const key = Buffer.from(trimmed, isHex ? "hex" : "base64");
|
|
301
307
|
if (key.length !== 32) {
|
|
302
308
|
throw new Error(
|
|
@@ -434,11 +440,15 @@ ${node.value}
|
|
|
434
440
|
|
|
435
441
|
// src/modals.ts
|
|
436
442
|
function encodeModalMetadata(meta) {
|
|
437
|
-
if (!meta.contextId
|
|
443
|
+
if (!(meta.contextId || meta.privateMetadata)) {
|
|
444
|
+
return void 0;
|
|
445
|
+
}
|
|
438
446
|
return JSON.stringify({ c: meta.contextId, m: meta.privateMetadata });
|
|
439
447
|
}
|
|
440
448
|
function decodeModalMetadata(raw) {
|
|
441
|
-
if (!raw)
|
|
449
|
+
if (!raw) {
|
|
450
|
+
return {};
|
|
451
|
+
}
|
|
442
452
|
try {
|
|
443
453
|
const parsed = JSON.parse(raw);
|
|
444
454
|
if (typeof parsed === "object" && parsed !== null && ("c" in parsed || "m" in parsed)) {
|
|
@@ -475,6 +485,10 @@ function modalChildToBlock(child) {
|
|
|
475
485
|
return convertTextToBlock(child);
|
|
476
486
|
case "fields":
|
|
477
487
|
return convertFieldsToBlock(child);
|
|
488
|
+
default:
|
|
489
|
+
throw new Error(
|
|
490
|
+
`Unknown modal child type: ${child.type}`
|
|
491
|
+
);
|
|
478
492
|
}
|
|
479
493
|
}
|
|
480
494
|
function textInputToBlock(input) {
|
|
@@ -592,7 +606,9 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
592
606
|
/** Bot user ID (e.g., U_BOT_123) used for mention detection */
|
|
593
607
|
get botUserId() {
|
|
594
608
|
const ctx = this.requestContext.getStore();
|
|
595
|
-
if (ctx?.botUserId)
|
|
609
|
+
if (ctx?.botUserId) {
|
|
610
|
+
return ctx.botUserId;
|
|
611
|
+
}
|
|
596
612
|
return this._botUserId || void 0;
|
|
597
613
|
}
|
|
598
614
|
constructor(config) {
|
|
@@ -614,8 +630,12 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
614
630
|
*/
|
|
615
631
|
getToken() {
|
|
616
632
|
const ctx = this.requestContext.getStore();
|
|
617
|
-
if (ctx?.token)
|
|
618
|
-
|
|
633
|
+
if (ctx?.token) {
|
|
634
|
+
return ctx.token;
|
|
635
|
+
}
|
|
636
|
+
if (this.defaultBotToken) {
|
|
637
|
+
return this.defaultBotToken;
|
|
638
|
+
}
|
|
619
639
|
throw new ChatError(
|
|
620
640
|
"No bot token available. In multi-workspace mode, ensure the webhook is being processed.",
|
|
621
641
|
"MISSING_BOT_TOKEN"
|
|
@@ -693,7 +713,9 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
693
713
|
const state = this.chat.getState();
|
|
694
714
|
const key = this.installationKey(teamId);
|
|
695
715
|
const stored = await state.get(key);
|
|
696
|
-
if (!stored)
|
|
716
|
+
if (!stored) {
|
|
717
|
+
return null;
|
|
718
|
+
}
|
|
697
719
|
if (this.encryptionKey && isEncryptedTokenData(stored.botToken)) {
|
|
698
720
|
return {
|
|
699
721
|
...stored,
|
|
@@ -711,7 +733,7 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
711
733
|
* exchanges it for tokens, and saves the installation.
|
|
712
734
|
*/
|
|
713
735
|
async handleOAuthCallback(request) {
|
|
714
|
-
if (!this.clientId
|
|
736
|
+
if (!(this.clientId && this.clientSecret)) {
|
|
715
737
|
throw new ChatError(
|
|
716
738
|
"clientId and clientSecret are required for OAuth. Pass them in createSlackAdapter().",
|
|
717
739
|
"MISSING_OAUTH_CONFIG"
|
|
@@ -732,7 +754,7 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
732
754
|
code,
|
|
733
755
|
redirect_uri: redirectUri
|
|
734
756
|
});
|
|
735
|
-
if (!result.ok
|
|
757
|
+
if (!(result.ok && result.access_token && result.team?.id)) {
|
|
736
758
|
throw new ChatError(
|
|
737
759
|
`Slack OAuth failed: ${result.error || "missing access_token or team.id"}`,
|
|
738
760
|
"OAUTH_FAILED"
|
|
@@ -800,7 +822,9 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
800
822
|
try {
|
|
801
823
|
const params = new URLSearchParams(body);
|
|
802
824
|
const payloadStr = params.get("payload");
|
|
803
|
-
if (!payloadStr)
|
|
825
|
+
if (!payloadStr) {
|
|
826
|
+
return null;
|
|
827
|
+
}
|
|
804
828
|
const payload = JSON.parse(payloadStr);
|
|
805
829
|
return payload.team?.id || payload.team_id || null;
|
|
806
830
|
} catch {
|
|
@@ -891,9 +915,7 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
891
915
|
return new Response("Invalid JSON", { status: 400 });
|
|
892
916
|
}
|
|
893
917
|
if (payload.type === "url_verification" && payload.challenge) {
|
|
894
|
-
return
|
|
895
|
-
headers: { "Content-Type": "application/json" }
|
|
896
|
-
});
|
|
918
|
+
return Response.json({ challenge: payload.challenge });
|
|
897
919
|
}
|
|
898
920
|
if (!this.defaultBotToken && payload.type === "event_callback") {
|
|
899
921
|
const teamId = payload.team_id;
|
|
@@ -918,7 +940,7 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
918
940
|
const event = payload.event;
|
|
919
941
|
if (event.type === "message" || event.type === "app_mention") {
|
|
920
942
|
const slackEvent = event;
|
|
921
|
-
if (!slackEvent.team
|
|
943
|
+
if (!(slackEvent.team || slackEvent.team_id) && payload.team_id) {
|
|
922
944
|
slackEvent.team_id = payload.team_id;
|
|
923
945
|
}
|
|
924
946
|
this.handleMessageEvent(slackEvent, options);
|
|
@@ -1020,7 +1042,7 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
1020
1042
|
const messageTs = payload.message?.ts || payload.container?.message_ts;
|
|
1021
1043
|
const threadTs = payload.message?.thread_ts || payload.container?.thread_ts || messageTs;
|
|
1022
1044
|
const isViewAction = payload.container?.type === "view";
|
|
1023
|
-
if (!isViewAction
|
|
1045
|
+
if (!(isViewAction || channel)) {
|
|
1024
1046
|
this.logger.warn("Missing channel in block_actions", { channel });
|
|
1025
1047
|
return;
|
|
1026
1048
|
}
|
|
@@ -1173,11 +1195,11 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
1173
1195
|
return modal;
|
|
1174
1196
|
}
|
|
1175
1197
|
verifySignature(body, timestamp, signature) {
|
|
1176
|
-
if (!timestamp
|
|
1198
|
+
if (!(timestamp && signature)) {
|
|
1177
1199
|
return false;
|
|
1178
1200
|
}
|
|
1179
1201
|
const now = Math.floor(Date.now() / 1e3);
|
|
1180
|
-
if (Math.abs(now - parseInt(timestamp, 10)) > 300) {
|
|
1202
|
+
if (Math.abs(now - Number.parseInt(timestamp, 10)) > 300) {
|
|
1181
1203
|
return false;
|
|
1182
1204
|
}
|
|
1183
1205
|
const sigBasestring = `v0:${timestamp}:${body}`;
|
|
@@ -1206,7 +1228,7 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
1206
1228
|
});
|
|
1207
1229
|
return;
|
|
1208
1230
|
}
|
|
1209
|
-
if (!event.channel
|
|
1231
|
+
if (!(event.channel && event.ts)) {
|
|
1210
1232
|
this.logger.debug("Ignoring event without channel or ts", {
|
|
1211
1233
|
channel: event.channel,
|
|
1212
1234
|
ts: event.ts
|
|
@@ -1440,11 +1462,15 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
1440
1462
|
userIds.add(match[1]);
|
|
1441
1463
|
match = mentionPattern.exec(text);
|
|
1442
1464
|
}
|
|
1443
|
-
if (userIds.size === 0)
|
|
1465
|
+
if (userIds.size === 0) {
|
|
1466
|
+
return text;
|
|
1467
|
+
}
|
|
1444
1468
|
if (skipSelfMention && this._botUserId) {
|
|
1445
1469
|
userIds.delete(this._botUserId);
|
|
1446
1470
|
}
|
|
1447
|
-
if (userIds.size === 0)
|
|
1471
|
+
if (userIds.size === 0) {
|
|
1472
|
+
return text;
|
|
1473
|
+
}
|
|
1448
1474
|
const lookups = await Promise.all(
|
|
1449
1475
|
[...userIds].map(async (uid) => {
|
|
1450
1476
|
const info = await this.lookupUser(uid);
|
|
@@ -1483,9 +1509,9 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
1483
1509
|
isMe
|
|
1484
1510
|
},
|
|
1485
1511
|
metadata: {
|
|
1486
|
-
dateSent: new Date(parseFloat(event.ts || "0") * 1e3),
|
|
1512
|
+
dateSent: new Date(Number.parseFloat(event.ts || "0") * 1e3),
|
|
1487
1513
|
edited: !!event.edited,
|
|
1488
|
-
editedAt: event.edited ? new Date(parseFloat(event.edited.ts) * 1e3) : void 0
|
|
1514
|
+
editedAt: event.edited ? new Date(Number.parseFloat(event.edited.ts) * 1e3) : void 0
|
|
1489
1515
|
},
|
|
1490
1516
|
attachments: (event.files || []).map(
|
|
1491
1517
|
(file) => this.createAttachment(file)
|
|
@@ -1540,7 +1566,7 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
1540
1566
|
await this.uploadFiles(files, channel, threadTs || void 0);
|
|
1541
1567
|
const hasText = typeof message === "string" || typeof message === "object" && message !== null && ("raw" in message || "markdown" in message || "ast" in message);
|
|
1542
1568
|
const card2 = extractCard(message);
|
|
1543
|
-
if (!hasText
|
|
1569
|
+
if (!(hasText || card2)) {
|
|
1544
1570
|
return {
|
|
1545
1571
|
id: `file-${Date.now()}`,
|
|
1546
1572
|
threadId,
|
|
@@ -1918,7 +1944,7 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
1918
1944
|
* Requires `recipientUserId` and `recipientTeamId` in options.
|
|
1919
1945
|
*/
|
|
1920
1946
|
async stream(threadId, textStream, options) {
|
|
1921
|
-
if (!options?.recipientUserId
|
|
1947
|
+
if (!(options?.recipientUserId && options?.recipientTeamId)) {
|
|
1922
1948
|
throw new ChatError(
|
|
1923
1949
|
"Slack streaming requires recipientUserId and recipientTeamId in options",
|
|
1924
1950
|
"MISSING_STREAM_OPTIONS"
|
|
@@ -1990,7 +2016,7 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
1990
2016
|
const limit = options.limit || 100;
|
|
1991
2017
|
try {
|
|
1992
2018
|
if (direction === "forward") {
|
|
1993
|
-
return this.fetchMessagesForward(
|
|
2019
|
+
return await this.fetchMessagesForward(
|
|
1994
2020
|
channel,
|
|
1995
2021
|
threadTs,
|
|
1996
2022
|
threadId,
|
|
@@ -1998,7 +2024,7 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
1998
2024
|
options.cursor
|
|
1999
2025
|
);
|
|
2000
2026
|
}
|
|
2001
|
-
return this.fetchMessagesBackward(
|
|
2027
|
+
return await this.fetchMessagesBackward(
|
|
2002
2028
|
channel,
|
|
2003
2029
|
threadTs,
|
|
2004
2030
|
threadId,
|
|
@@ -2138,7 +2164,9 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
2138
2164
|
);
|
|
2139
2165
|
const messages = result.messages || [];
|
|
2140
2166
|
const target = messages.find((msg) => msg.ts === messageId);
|
|
2141
|
-
if (!target)
|
|
2167
|
+
if (!target) {
|
|
2168
|
+
return null;
|
|
2169
|
+
}
|
|
2142
2170
|
return this.parseSlackMessage(target, threadId);
|
|
2143
2171
|
} catch (error) {
|
|
2144
2172
|
this.handleSlackError(error);
|
|
@@ -2200,9 +2228,9 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
2200
2228
|
isMe
|
|
2201
2229
|
},
|
|
2202
2230
|
metadata: {
|
|
2203
|
-
dateSent: new Date(parseFloat(event.ts || "0") * 1e3),
|
|
2231
|
+
dateSent: new Date(Number.parseFloat(event.ts || "0") * 1e3),
|
|
2204
2232
|
edited: !!event.edited,
|
|
2205
|
-
editedAt: event.edited ? new Date(parseFloat(event.edited.ts) * 1e3) : void 0
|
|
2233
|
+
editedAt: event.edited ? new Date(Number.parseFloat(event.edited.ts) * 1e3) : void 0
|
|
2206
2234
|
},
|
|
2207
2235
|
attachments: (event.files || []).map(
|
|
2208
2236
|
(file) => this.createAttachment(file)
|
|
@@ -2276,7 +2304,7 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
2276
2304
|
);
|
|
2277
2305
|
let nextCursor;
|
|
2278
2306
|
if (result.has_more && slackMessages.length > 0) {
|
|
2279
|
-
const newest = slackMessages
|
|
2307
|
+
const newest = slackMessages.at(-1);
|
|
2280
2308
|
if (newest?.ts) {
|
|
2281
2309
|
nextCursor = newest.ts;
|
|
2282
2310
|
}
|
|
@@ -2399,7 +2427,7 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
2399
2427
|
return {
|
|
2400
2428
|
id: channelId,
|
|
2401
2429
|
name: info?.name ? `#${info.name}` : void 0,
|
|
2402
|
-
isDM: info?.is_im || info?.is_mpim
|
|
2430
|
+
isDM: Boolean(info?.is_im || info?.is_mpim),
|
|
2403
2431
|
memberCount: info?.num_members,
|
|
2404
2432
|
metadata: {
|
|
2405
2433
|
purpose: info?.purpose?.value,
|
|
@@ -2422,7 +2450,7 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
2422
2450
|
);
|
|
2423
2451
|
}
|
|
2424
2452
|
const syntheticThreadId = `slack:${channel}:`;
|
|
2425
|
-
return this.postMessage(syntheticThreadId, message);
|
|
2453
|
+
return await this.postMessage(syntheticThreadId, message);
|
|
2426
2454
|
}
|
|
2427
2455
|
renderFormatted(content) {
|
|
2428
2456
|
return this.formatConverter.fromAst(content);
|
|
@@ -2453,10 +2481,8 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
2453
2481
|
}
|
|
2454
2482
|
handleSlackError(error) {
|
|
2455
2483
|
const slackError = error;
|
|
2456
|
-
if (slackError.code === "slack_webapi_platform_error") {
|
|
2457
|
-
|
|
2458
|
-
throw new AdapterRateLimitError("slack");
|
|
2459
|
-
}
|
|
2484
|
+
if (slackError.code === "slack_webapi_platform_error" && slackError.data?.error === "ratelimited") {
|
|
2485
|
+
throw new AdapterRateLimitError("slack");
|
|
2460
2486
|
}
|
|
2461
2487
|
throw error;
|
|
2462
2488
|
}
|
|
@@ -2473,9 +2499,13 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
2473
2499
|
* Returns null if the messageId is not an ephemeral encoding.
|
|
2474
2500
|
*/
|
|
2475
2501
|
decodeEphemeralMessageId(messageId) {
|
|
2476
|
-
if (!messageId.startsWith("ephemeral:"))
|
|
2502
|
+
if (!messageId.startsWith("ephemeral:")) {
|
|
2503
|
+
return null;
|
|
2504
|
+
}
|
|
2477
2505
|
const parts = messageId.split(":");
|
|
2478
|
-
if (parts.length < 3)
|
|
2506
|
+
if (parts.length < 3) {
|
|
2507
|
+
return null;
|
|
2508
|
+
}
|
|
2479
2509
|
const messageTs = parts[1];
|
|
2480
2510
|
const encodedData = parts.slice(2).join(":");
|
|
2481
2511
|
try {
|