@pocketping/sdk-node 1.3.0 → 1.5.0

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 CHANGED
@@ -209,6 +209,12 @@ pp.addBridge(DiscordBridge.withBot({ ... }));
209
209
  pp.addBridge(SlackBridge.withWebhook({ ... }));
210
210
  ```
211
211
 
212
+ ### Reply Behavior
213
+
214
+ - **Telegram:** uses native replies when `replyTo` is set and the Telegram message ID is available.
215
+ - **Discord:** uses native replies via `message_reference` when the Discord message ID is available.
216
+ - **Slack:** renders a quoted block (left bar) in the thread.
217
+
212
218
  ## Architecture Options
213
219
 
214
220
  ### 1. Embedded Mode with Built-in Bridges (Simple)
@@ -255,6 +261,7 @@ const webhookHandler = new WebhookHandler({
255
261
  telegramBotToken: process.env.TELEGRAM_BOT_TOKEN,
256
262
  slackBotToken: process.env.SLACK_BOT_TOKEN,
257
263
  discordBotToken: process.env.DISCORD_BOT_TOKEN,
264
+ allowedBotIds: process.env.BRIDGE_TEST_BOT_IDS?.split(',').map((id) => id.trim()).filter(Boolean),
258
265
  onOperatorMessage: async (sessionId, content, operatorName, source, attachments) => {
259
266
  console.log(`Message from ${operatorName} via ${source}: ${content}`);
260
267
 
@@ -267,6 +274,12 @@ const webhookHandler = new WebhookHandler({
267
274
  // att.data contains the file bytes
268
275
  }
269
276
  },
277
+ onOperatorMessageEdit: async (sessionId, bridgeMessageId, content, source) => {
278
+ console.log(`Edited message ${bridgeMessageId} via ${source}: ${content}`);
279
+ },
280
+ onOperatorMessageDelete: async (sessionId, bridgeMessageId, source) => {
281
+ console.log(`Deleted message ${bridgeMessageId} via ${source}`);
282
+ },
270
283
  });
271
284
 
272
285
  // Mount webhook routes
package/dist/index.cjs CHANGED
@@ -1212,8 +1212,67 @@ var WebhookHandler = class {
1212
1212
  try {
1213
1213
  const body = await this.parseBody(req);
1214
1214
  const update = body;
1215
+ if (update.edited_message) {
1216
+ const msg = update.edited_message;
1217
+ if (msg.text?.startsWith("/")) {
1218
+ this.writeOK(res);
1219
+ return;
1220
+ }
1221
+ const text = msg.text ?? msg.caption ?? "";
1222
+ if (!text) {
1223
+ this.writeOK(res);
1224
+ return;
1225
+ }
1226
+ const topicId = msg.message_thread_id;
1227
+ if (!topicId) {
1228
+ this.writeOK(res);
1229
+ return;
1230
+ }
1231
+ if (this.config.onOperatorMessageEdit) {
1232
+ const editedAt = msg.edit_date ? new Date(msg.edit_date * 1e3).toISOString() : (/* @__PURE__ */ new Date()).toISOString();
1233
+ await this.config.onOperatorMessageEdit(
1234
+ String(topicId),
1235
+ msg.message_id,
1236
+ text,
1237
+ "telegram",
1238
+ editedAt
1239
+ );
1240
+ }
1241
+ this.writeOK(res);
1242
+ return;
1243
+ }
1244
+ if (update.message_reaction) {
1245
+ const reaction = update.message_reaction;
1246
+ const emoji = reaction.new_reaction?.[0]?.emoji;
1247
+ const topicId = reaction.message_thread_id;
1248
+ if (emoji && emoji.includes("\u{1F5D1}") && topicId && this.config.onOperatorMessageDelete) {
1249
+ const deletedAt = reaction.date ? new Date(reaction.date * 1e3).toISOString() : (/* @__PURE__ */ new Date()).toISOString();
1250
+ await this.config.onOperatorMessageDelete(
1251
+ String(topicId),
1252
+ reaction.message_id,
1253
+ "telegram",
1254
+ deletedAt
1255
+ );
1256
+ }
1257
+ this.writeOK(res);
1258
+ return;
1259
+ }
1215
1260
  if (update.message) {
1216
1261
  const msg = update.message;
1262
+ if (msg.text && /^\/delete(@\w+)?(\s|$)/.test(msg.text)) {
1263
+ const topicId2 = msg.message_thread_id;
1264
+ const replyToId = msg.reply_to_message?.message_id;
1265
+ if (topicId2 && replyToId && this.config.onOperatorMessageDelete) {
1266
+ await this.config.onOperatorMessageDelete(
1267
+ String(topicId2),
1268
+ replyToId,
1269
+ "telegram",
1270
+ (/* @__PURE__ */ new Date()).toISOString()
1271
+ );
1272
+ }
1273
+ this.writeOK(res);
1274
+ return;
1275
+ }
1217
1276
  if (msg.text?.startsWith("/")) {
1218
1277
  this.writeOK(res);
1219
1278
  return;
@@ -1267,6 +1326,7 @@ var WebhookHandler = class {
1267
1326
  return;
1268
1327
  }
1269
1328
  const operatorName = msg.from?.first_name ?? "Operator";
1329
+ const replyToBridgeMessageId = msg.reply_to_message?.message_id ?? null;
1270
1330
  const attachments = [];
1271
1331
  if (media) {
1272
1332
  const data = await this.downloadTelegramFile(media.fileId);
@@ -1285,7 +1345,9 @@ var WebhookHandler = class {
1285
1345
  text,
1286
1346
  operatorName,
1287
1347
  "telegram",
1288
- attachments
1348
+ attachments,
1349
+ replyToBridgeMessageId,
1350
+ msg.message_id
1289
1351
  );
1290
1352
  }
1291
1353
  this.writeOK(res);
@@ -1316,7 +1378,56 @@ var WebhookHandler = class {
1316
1378
  }
1317
1379
  if (payload.type === "event_callback" && payload.event) {
1318
1380
  const event = payload.event;
1319
- const hasContent = event.type === "message" && event.thread_ts && !event.bot_id && !event.subtype;
1381
+ const isAllowedBot = (botId) => !!botId && (this.config.allowedBotIds?.includes(botId) ?? false);
1382
+ if (event.subtype === "message_changed") {
1383
+ if (!this.config.onOperatorMessageEdit) {
1384
+ this.writeOK(res);
1385
+ return;
1386
+ }
1387
+ const botId = event.message?.bot_id ?? event.previous_message?.bot_id ?? event.bot_id;
1388
+ if (botId && !isAllowedBot(botId)) {
1389
+ this.writeOK(res);
1390
+ return;
1391
+ }
1392
+ const threadTs = event.message?.thread_ts ?? event.previous_message?.thread_ts;
1393
+ const messageTs = event.message?.ts ?? event.previous_message?.ts;
1394
+ const text = event.message?.text ?? "";
1395
+ if (threadTs && messageTs) {
1396
+ await this.config.onOperatorMessageEdit(
1397
+ threadTs,
1398
+ messageTs,
1399
+ text,
1400
+ "slack",
1401
+ (/* @__PURE__ */ new Date()).toISOString()
1402
+ );
1403
+ }
1404
+ this.writeOK(res);
1405
+ return;
1406
+ }
1407
+ if (event.subtype === "message_deleted") {
1408
+ if (!this.config.onOperatorMessageDelete) {
1409
+ this.writeOK(res);
1410
+ return;
1411
+ }
1412
+ const botId = event.previous_message?.bot_id ?? event.bot_id;
1413
+ if (botId && !isAllowedBot(botId)) {
1414
+ this.writeOK(res);
1415
+ return;
1416
+ }
1417
+ const threadTs = event.previous_message?.thread_ts;
1418
+ const messageTs = event.deleted_ts ?? event.previous_message?.ts;
1419
+ if (threadTs && messageTs) {
1420
+ await this.config.onOperatorMessageDelete(
1421
+ threadTs,
1422
+ messageTs,
1423
+ "slack",
1424
+ (/* @__PURE__ */ new Date()).toISOString()
1425
+ );
1426
+ }
1427
+ this.writeOK(res);
1428
+ return;
1429
+ }
1430
+ const hasContent = event.type === "message" && event.thread_ts && (!event.bot_id || isAllowedBot(event.bot_id)) && !event.subtype;
1320
1431
  const hasFiles = event.files && event.files.length > 0;
1321
1432
  if (hasContent && (event.text || hasFiles)) {
1322
1433
  const threadTs = event.thread_ts;
@@ -1346,7 +1457,9 @@ var WebhookHandler = class {
1346
1457
  text,
1347
1458
  operatorName,
1348
1459
  "slack",
1349
- attachments
1460
+ attachments,
1461
+ null,
1462
+ event.ts ?? null
1350
1463
  );
1351
1464
  }
1352
1465
  }
@@ -1384,7 +1497,8 @@ var WebhookHandler = class {
1384
1497
  content,
1385
1498
  operatorName,
1386
1499
  "discord",
1387
- []
1500
+ [],
1501
+ null
1388
1502
  );
1389
1503
  res.setHeader("Content-Type", "application/json");
1390
1504
  res.end(
@@ -1496,7 +1610,8 @@ var TelegramBridge = class {
1496
1610
  /**
1497
1611
  * Initialize the bridge (optional setup)
1498
1612
  */
1499
- async init(_pocketping) {
1613
+ async init(pocketping) {
1614
+ this.pocketping = pocketping;
1500
1615
  try {
1501
1616
  const response = await fetch(`${this.baseUrl}/getMe`);
1502
1617
  const data = await response.json();
@@ -1525,8 +1640,18 @@ var TelegramBridge = class {
1525
1640
  */
1526
1641
  async onVisitorMessage(message, session) {
1527
1642
  const text = this.formatVisitorMessage(session.visitorId, message.content);
1643
+ let replyToMessageId;
1644
+ if (message.replyTo) {
1645
+ const storage = this.pocketping?.getStorage();
1646
+ if (storage?.getBridgeMessageIds) {
1647
+ const ids = await storage.getBridgeMessageIds(message.replyTo);
1648
+ if (ids?.telegramMessageId) {
1649
+ replyToMessageId = ids.telegramMessageId;
1650
+ }
1651
+ }
1652
+ }
1528
1653
  try {
1529
- const messageId = await this.sendMessage(text);
1654
+ const messageId = await this.sendMessage(text, replyToMessageId);
1530
1655
  return { messageId };
1531
1656
  } catch (error) {
1532
1657
  console.error("[TelegramBridge] Failed to send visitor message:", error);
@@ -1663,7 +1788,7 @@ ${dataStr}
1663
1788
  /**
1664
1789
  * Send a message to the Telegram chat
1665
1790
  */
1666
- async sendMessage(text) {
1791
+ async sendMessage(text, replyToMessageId) {
1667
1792
  const response = await fetch(`${this.baseUrl}/sendMessage`, {
1668
1793
  method: "POST",
1669
1794
  headers: { "Content-Type": "application/json" },
@@ -1671,7 +1796,8 @@ ${dataStr}
1671
1796
  chat_id: this.chatId,
1672
1797
  text,
1673
1798
  parse_mode: this.parseMode,
1674
- disable_notification: this.disableNotification
1799
+ disable_notification: this.disableNotification,
1800
+ ...replyToMessageId ? { reply_to_message_id: replyToMessageId } : {}
1675
1801
  })
1676
1802
  });
1677
1803
  const data = await response.json();
@@ -1768,7 +1894,8 @@ var DiscordBridge = class _DiscordBridge {
1768
1894
  /**
1769
1895
  * Initialize the bridge (optional setup)
1770
1896
  */
1771
- async init(_pocketping) {
1897
+ async init(pocketping) {
1898
+ this.pocketping = pocketping;
1772
1899
  if (this.mode === "bot" && this.botToken) {
1773
1900
  try {
1774
1901
  const response = await fetch("https://discord.com/api/v10/users/@me", {
@@ -1818,8 +1945,18 @@ var DiscordBridge = class _DiscordBridge {
1818
1945
  // Green
1819
1946
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
1820
1947
  };
1948
+ let replyToMessageId;
1949
+ if (message.replyTo) {
1950
+ const storage = this.pocketping?.getStorage();
1951
+ if (storage?.getBridgeMessageIds) {
1952
+ const ids = await storage.getBridgeMessageIds(message.replyTo);
1953
+ if (ids?.discordMessageId) {
1954
+ replyToMessageId = ids.discordMessageId;
1955
+ }
1956
+ }
1957
+ }
1821
1958
  try {
1822
- const messageId = await this.sendEmbed(embed);
1959
+ const messageId = await this.sendEmbed(embed, replyToMessageId);
1823
1960
  return { messageId };
1824
1961
  } catch (error) {
1825
1962
  console.error("[DiscordBridge] Failed to send visitor message:", error);
@@ -2009,7 +2146,7 @@ ${JSON.stringify(event.data, null, 2)}
2009
2146
  /**
2010
2147
  * Send an embed to Discord
2011
2148
  */
2012
- async sendEmbed(embed) {
2149
+ async sendEmbed(embed, replyToMessageId) {
2013
2150
  const body = {
2014
2151
  embeds: [embed]
2015
2152
  };
@@ -2020,6 +2157,9 @@ ${JSON.stringify(event.data, null, 2)}
2020
2157
  body.avatar_url = this.avatarUrl;
2021
2158
  }
2022
2159
  if (this.mode === "webhook" && this.webhookUrl) {
2160
+ if (replyToMessageId) {
2161
+ body.message_reference = { message_id: replyToMessageId };
2162
+ }
2023
2163
  const response = await fetch(`${this.webhookUrl}?wait=true`, {
2024
2164
  method: "POST",
2025
2165
  headers: { "Content-Type": "application/json" },
@@ -2032,6 +2172,9 @@ ${JSON.stringify(event.data, null, 2)}
2032
2172
  const data = await response.json();
2033
2173
  return data.id;
2034
2174
  } else if (this.mode === "bot" && this.channelId) {
2175
+ if (replyToMessageId) {
2176
+ body.message_reference = { message_id: replyToMessageId };
2177
+ }
2035
2178
  const response = await fetch(
2036
2179
  `https://discord.com/api/v10/channels/${this.channelId}/messages`,
2037
2180
  {
@@ -2094,7 +2237,8 @@ var SlackBridge = class _SlackBridge {
2094
2237
  /**
2095
2238
  * Initialize the bridge (optional setup)
2096
2239
  */
2097
- async init(_pocketping) {
2240
+ async init(pocketping) {
2241
+ this.pocketping = pocketping;
2098
2242
  if (this.mode === "bot" && this.botToken) {
2099
2243
  try {
2100
2244
  const response = await fetch("https://slack.com/api/auth.test", {
@@ -2154,7 +2298,24 @@ ${url}`
2154
2298
  * Returns the Slack message timestamp for edit/delete sync.
2155
2299
  */
2156
2300
  async onVisitorMessage(message, session) {
2157
- const blocks = [
2301
+ const blocks = [];
2302
+ if (message.replyTo && this.pocketping?.getStorage().getMessage) {
2303
+ const replyTarget = await this.pocketping.getStorage().getMessage(message.replyTo);
2304
+ if (replyTarget) {
2305
+ const senderLabel = replyTarget.sender === "visitor" ? "Visitor" : replyTarget.sender === "operator" ? "Support" : "AI";
2306
+ const rawPreview = replyTarget.deletedAt ? "Message deleted" : replyTarget.content || "Message";
2307
+ const preview = rawPreview.length > 140 ? `${rawPreview.slice(0, 140)}...` : rawPreview;
2308
+ const quoted = `> *${this.escapeSlack(senderLabel)}* \u2014 ${this.escapeSlack(preview)}`;
2309
+ blocks.push({
2310
+ type: "section",
2311
+ text: {
2312
+ type: "mrkdwn",
2313
+ text: quoted
2314
+ }
2315
+ });
2316
+ }
2317
+ }
2318
+ blocks.push(
2158
2319
  {
2159
2320
  type: "section",
2160
2321
  text: {
@@ -2172,7 +2333,7 @@ ${this.escapeSlack(message.content)}`
2172
2333
  }
2173
2334
  ]
2174
2335
  }
2175
- ];
2336
+ );
2176
2337
  try {
2177
2338
  const messageId = await this.sendBlocks(blocks);
2178
2339
  return { messageId };
package/dist/index.d.cts CHANGED
@@ -594,13 +594,18 @@ interface OperatorAttachment {
594
594
  bridgeFileId?: string;
595
595
  }
596
596
  /** Callback when operator sends a message from a bridge */
597
- type OperatorMessageCallback = (sessionId: string, content: string, operatorName: string, sourceBridge: 'telegram' | 'discord' | 'slack', attachments: OperatorAttachment[]) => void | Promise<void>;
597
+ type OperatorMessageCallback = (sessionId: string, content: string, operatorName: string, sourceBridge: 'telegram' | 'discord' | 'slack', attachments: OperatorAttachment[], replyToBridgeMessageId?: number | null, bridgeMessageId?: number | string | null) => void | Promise<void>;
598
+ type OperatorMessageEditCallback = (sessionId: string, bridgeMessageId: number | string, content: string, sourceBridge: 'telegram' | 'discord' | 'slack', editedAt?: string) => void | Promise<void>;
599
+ type OperatorMessageDeleteCallback = (sessionId: string, bridgeMessageId: number | string, sourceBridge: 'telegram' | 'discord' | 'slack', deletedAt?: string) => void | Promise<void>;
598
600
  /** Webhook handler configuration */
599
601
  interface WebhookConfig {
600
602
  telegramBotToken?: string;
601
603
  slackBotToken?: string;
602
604
  discordBotToken?: string;
605
+ allowedBotIds?: string[];
603
606
  onOperatorMessage: OperatorMessageCallback;
607
+ onOperatorMessageEdit?: OperatorMessageEditCallback;
608
+ onOperatorMessageDelete?: OperatorMessageDeleteCallback;
604
609
  }
605
610
  declare class WebhookHandler {
606
611
  private config;
@@ -655,6 +660,7 @@ interface TelegramBridgeOptions {
655
660
  */
656
661
  declare class TelegramBridge implements Bridge {
657
662
  readonly name = "telegram";
663
+ private pocketping?;
658
664
  private readonly botToken;
659
665
  private readonly chatId;
660
666
  private readonly parseMode;
@@ -664,7 +670,7 @@ declare class TelegramBridge implements Bridge {
664
670
  /**
665
671
  * Initialize the bridge (optional setup)
666
672
  */
667
- init(_pocketping: PocketPing): Promise<void>;
673
+ init(pocketping: PocketPing): Promise<void>;
668
674
  /**
669
675
  * Called when a new chat session is created
670
676
  */
@@ -770,6 +776,7 @@ interface DiscordBotOptions {
770
776
  */
771
777
  declare class DiscordBridge implements Bridge {
772
778
  readonly name = "discord";
779
+ private pocketping?;
773
780
  private readonly mode;
774
781
  private readonly webhookUrl?;
775
782
  private readonly botToken?;
@@ -788,7 +795,7 @@ declare class DiscordBridge implements Bridge {
788
795
  /**
789
796
  * Initialize the bridge (optional setup)
790
797
  */
791
- init(_pocketping: PocketPing): Promise<void>;
798
+ init(pocketping: PocketPing): Promise<void>;
792
799
  /**
793
800
  * Called when a new chat session is created
794
801
  */
@@ -878,6 +885,7 @@ interface SlackBotOptions {
878
885
  */
879
886
  declare class SlackBridge implements Bridge {
880
887
  readonly name = "slack";
888
+ private pocketping?;
881
889
  private readonly mode;
882
890
  private readonly webhookUrl?;
883
891
  private readonly botToken?;
@@ -897,7 +905,7 @@ declare class SlackBridge implements Bridge {
897
905
  /**
898
906
  * Initialize the bridge (optional setup)
899
907
  */
900
- init(_pocketping: PocketPing): Promise<void>;
908
+ init(pocketping: PocketPing): Promise<void>;
901
909
  /**
902
910
  * Called when a new chat session is created
903
911
  */
@@ -946,4 +954,4 @@ declare class SlackBridge implements Bridge {
946
954
  private escapeSlack;
947
955
  }
948
956
 
949
- export { type AIProvider, type Bridge, type BridgeMessageIds, type BridgeMessageResult, type ConnectRequest, type ConnectResponse, type CustomEvent, type CustomEventHandler, type DeleteMessageRequest, type DeleteMessageResponse, type DiscordBotOptions, DiscordBridge, type DiscordWebhookOptions, type EditMessageRequest, type EditMessageResponse, MemoryStorage, type Message, type OperatorAttachment, type OperatorMessageCallback, PocketPing, type PocketPingConfig, type PresenceResponse, type SendMessageRequest, type SendMessageResponse, type Session, type SlackBotOptions, SlackBridge, type SlackWebhookOptions, type Storage, TelegramBridge, type TelegramBridgeOptions, type TrackedElement, type TriggerOptions, type WebhookConfig, WebhookHandler, type WebhookPayload };
957
+ export { type AIProvider, type Bridge, type BridgeMessageIds, type BridgeMessageResult, type ConnectRequest, type ConnectResponse, type CustomEvent, type CustomEventHandler, type DeleteMessageRequest, type DeleteMessageResponse, type DiscordBotOptions, DiscordBridge, type DiscordWebhookOptions, type EditMessageRequest, type EditMessageResponse, MemoryStorage, type Message, type OperatorAttachment, type OperatorMessageCallback, type OperatorMessageDeleteCallback, type OperatorMessageEditCallback, PocketPing, type PocketPingConfig, type PresenceResponse, type SendMessageRequest, type SendMessageResponse, type Session, type SlackBotOptions, SlackBridge, type SlackWebhookOptions, type Storage, TelegramBridge, type TelegramBridgeOptions, type TrackedElement, type TriggerOptions, type WebhookConfig, WebhookHandler, type WebhookPayload };
package/dist/index.d.ts CHANGED
@@ -594,13 +594,18 @@ interface OperatorAttachment {
594
594
  bridgeFileId?: string;
595
595
  }
596
596
  /** Callback when operator sends a message from a bridge */
597
- type OperatorMessageCallback = (sessionId: string, content: string, operatorName: string, sourceBridge: 'telegram' | 'discord' | 'slack', attachments: OperatorAttachment[]) => void | Promise<void>;
597
+ type OperatorMessageCallback = (sessionId: string, content: string, operatorName: string, sourceBridge: 'telegram' | 'discord' | 'slack', attachments: OperatorAttachment[], replyToBridgeMessageId?: number | null, bridgeMessageId?: number | string | null) => void | Promise<void>;
598
+ type OperatorMessageEditCallback = (sessionId: string, bridgeMessageId: number | string, content: string, sourceBridge: 'telegram' | 'discord' | 'slack', editedAt?: string) => void | Promise<void>;
599
+ type OperatorMessageDeleteCallback = (sessionId: string, bridgeMessageId: number | string, sourceBridge: 'telegram' | 'discord' | 'slack', deletedAt?: string) => void | Promise<void>;
598
600
  /** Webhook handler configuration */
599
601
  interface WebhookConfig {
600
602
  telegramBotToken?: string;
601
603
  slackBotToken?: string;
602
604
  discordBotToken?: string;
605
+ allowedBotIds?: string[];
603
606
  onOperatorMessage: OperatorMessageCallback;
607
+ onOperatorMessageEdit?: OperatorMessageEditCallback;
608
+ onOperatorMessageDelete?: OperatorMessageDeleteCallback;
604
609
  }
605
610
  declare class WebhookHandler {
606
611
  private config;
@@ -655,6 +660,7 @@ interface TelegramBridgeOptions {
655
660
  */
656
661
  declare class TelegramBridge implements Bridge {
657
662
  readonly name = "telegram";
663
+ private pocketping?;
658
664
  private readonly botToken;
659
665
  private readonly chatId;
660
666
  private readonly parseMode;
@@ -664,7 +670,7 @@ declare class TelegramBridge implements Bridge {
664
670
  /**
665
671
  * Initialize the bridge (optional setup)
666
672
  */
667
- init(_pocketping: PocketPing): Promise<void>;
673
+ init(pocketping: PocketPing): Promise<void>;
668
674
  /**
669
675
  * Called when a new chat session is created
670
676
  */
@@ -770,6 +776,7 @@ interface DiscordBotOptions {
770
776
  */
771
777
  declare class DiscordBridge implements Bridge {
772
778
  readonly name = "discord";
779
+ private pocketping?;
773
780
  private readonly mode;
774
781
  private readonly webhookUrl?;
775
782
  private readonly botToken?;
@@ -788,7 +795,7 @@ declare class DiscordBridge implements Bridge {
788
795
  /**
789
796
  * Initialize the bridge (optional setup)
790
797
  */
791
- init(_pocketping: PocketPing): Promise<void>;
798
+ init(pocketping: PocketPing): Promise<void>;
792
799
  /**
793
800
  * Called when a new chat session is created
794
801
  */
@@ -878,6 +885,7 @@ interface SlackBotOptions {
878
885
  */
879
886
  declare class SlackBridge implements Bridge {
880
887
  readonly name = "slack";
888
+ private pocketping?;
881
889
  private readonly mode;
882
890
  private readonly webhookUrl?;
883
891
  private readonly botToken?;
@@ -897,7 +905,7 @@ declare class SlackBridge implements Bridge {
897
905
  /**
898
906
  * Initialize the bridge (optional setup)
899
907
  */
900
- init(_pocketping: PocketPing): Promise<void>;
908
+ init(pocketping: PocketPing): Promise<void>;
901
909
  /**
902
910
  * Called when a new chat session is created
903
911
  */
@@ -946,4 +954,4 @@ declare class SlackBridge implements Bridge {
946
954
  private escapeSlack;
947
955
  }
948
956
 
949
- export { type AIProvider, type Bridge, type BridgeMessageIds, type BridgeMessageResult, type ConnectRequest, type ConnectResponse, type CustomEvent, type CustomEventHandler, type DeleteMessageRequest, type DeleteMessageResponse, type DiscordBotOptions, DiscordBridge, type DiscordWebhookOptions, type EditMessageRequest, type EditMessageResponse, MemoryStorage, type Message, type OperatorAttachment, type OperatorMessageCallback, PocketPing, type PocketPingConfig, type PresenceResponse, type SendMessageRequest, type SendMessageResponse, type Session, type SlackBotOptions, SlackBridge, type SlackWebhookOptions, type Storage, TelegramBridge, type TelegramBridgeOptions, type TrackedElement, type TriggerOptions, type WebhookConfig, WebhookHandler, type WebhookPayload };
957
+ export { type AIProvider, type Bridge, type BridgeMessageIds, type BridgeMessageResult, type ConnectRequest, type ConnectResponse, type CustomEvent, type CustomEventHandler, type DeleteMessageRequest, type DeleteMessageResponse, type DiscordBotOptions, DiscordBridge, type DiscordWebhookOptions, type EditMessageRequest, type EditMessageResponse, MemoryStorage, type Message, type OperatorAttachment, type OperatorMessageCallback, type OperatorMessageDeleteCallback, type OperatorMessageEditCallback, PocketPing, type PocketPingConfig, type PresenceResponse, type SendMessageRequest, type SendMessageResponse, type Session, type SlackBotOptions, SlackBridge, type SlackWebhookOptions, type Storage, TelegramBridge, type TelegramBridgeOptions, type TrackedElement, type TriggerOptions, type WebhookConfig, WebhookHandler, type WebhookPayload };
package/dist/index.js CHANGED
@@ -1181,8 +1181,67 @@ var WebhookHandler = class {
1181
1181
  try {
1182
1182
  const body = await this.parseBody(req);
1183
1183
  const update = body;
1184
+ if (update.edited_message) {
1185
+ const msg = update.edited_message;
1186
+ if (msg.text?.startsWith("/")) {
1187
+ this.writeOK(res);
1188
+ return;
1189
+ }
1190
+ const text = msg.text ?? msg.caption ?? "";
1191
+ if (!text) {
1192
+ this.writeOK(res);
1193
+ return;
1194
+ }
1195
+ const topicId = msg.message_thread_id;
1196
+ if (!topicId) {
1197
+ this.writeOK(res);
1198
+ return;
1199
+ }
1200
+ if (this.config.onOperatorMessageEdit) {
1201
+ const editedAt = msg.edit_date ? new Date(msg.edit_date * 1e3).toISOString() : (/* @__PURE__ */ new Date()).toISOString();
1202
+ await this.config.onOperatorMessageEdit(
1203
+ String(topicId),
1204
+ msg.message_id,
1205
+ text,
1206
+ "telegram",
1207
+ editedAt
1208
+ );
1209
+ }
1210
+ this.writeOK(res);
1211
+ return;
1212
+ }
1213
+ if (update.message_reaction) {
1214
+ const reaction = update.message_reaction;
1215
+ const emoji = reaction.new_reaction?.[0]?.emoji;
1216
+ const topicId = reaction.message_thread_id;
1217
+ if (emoji && emoji.includes("\u{1F5D1}") && topicId && this.config.onOperatorMessageDelete) {
1218
+ const deletedAt = reaction.date ? new Date(reaction.date * 1e3).toISOString() : (/* @__PURE__ */ new Date()).toISOString();
1219
+ await this.config.onOperatorMessageDelete(
1220
+ String(topicId),
1221
+ reaction.message_id,
1222
+ "telegram",
1223
+ deletedAt
1224
+ );
1225
+ }
1226
+ this.writeOK(res);
1227
+ return;
1228
+ }
1184
1229
  if (update.message) {
1185
1230
  const msg = update.message;
1231
+ if (msg.text && /^\/delete(@\w+)?(\s|$)/.test(msg.text)) {
1232
+ const topicId2 = msg.message_thread_id;
1233
+ const replyToId = msg.reply_to_message?.message_id;
1234
+ if (topicId2 && replyToId && this.config.onOperatorMessageDelete) {
1235
+ await this.config.onOperatorMessageDelete(
1236
+ String(topicId2),
1237
+ replyToId,
1238
+ "telegram",
1239
+ (/* @__PURE__ */ new Date()).toISOString()
1240
+ );
1241
+ }
1242
+ this.writeOK(res);
1243
+ return;
1244
+ }
1186
1245
  if (msg.text?.startsWith("/")) {
1187
1246
  this.writeOK(res);
1188
1247
  return;
@@ -1236,6 +1295,7 @@ var WebhookHandler = class {
1236
1295
  return;
1237
1296
  }
1238
1297
  const operatorName = msg.from?.first_name ?? "Operator";
1298
+ const replyToBridgeMessageId = msg.reply_to_message?.message_id ?? null;
1239
1299
  const attachments = [];
1240
1300
  if (media) {
1241
1301
  const data = await this.downloadTelegramFile(media.fileId);
@@ -1254,7 +1314,9 @@ var WebhookHandler = class {
1254
1314
  text,
1255
1315
  operatorName,
1256
1316
  "telegram",
1257
- attachments
1317
+ attachments,
1318
+ replyToBridgeMessageId,
1319
+ msg.message_id
1258
1320
  );
1259
1321
  }
1260
1322
  this.writeOK(res);
@@ -1285,7 +1347,56 @@ var WebhookHandler = class {
1285
1347
  }
1286
1348
  if (payload.type === "event_callback" && payload.event) {
1287
1349
  const event = payload.event;
1288
- const hasContent = event.type === "message" && event.thread_ts && !event.bot_id && !event.subtype;
1350
+ const isAllowedBot = (botId) => !!botId && (this.config.allowedBotIds?.includes(botId) ?? false);
1351
+ if (event.subtype === "message_changed") {
1352
+ if (!this.config.onOperatorMessageEdit) {
1353
+ this.writeOK(res);
1354
+ return;
1355
+ }
1356
+ const botId = event.message?.bot_id ?? event.previous_message?.bot_id ?? event.bot_id;
1357
+ if (botId && !isAllowedBot(botId)) {
1358
+ this.writeOK(res);
1359
+ return;
1360
+ }
1361
+ const threadTs = event.message?.thread_ts ?? event.previous_message?.thread_ts;
1362
+ const messageTs = event.message?.ts ?? event.previous_message?.ts;
1363
+ const text = event.message?.text ?? "";
1364
+ if (threadTs && messageTs) {
1365
+ await this.config.onOperatorMessageEdit(
1366
+ threadTs,
1367
+ messageTs,
1368
+ text,
1369
+ "slack",
1370
+ (/* @__PURE__ */ new Date()).toISOString()
1371
+ );
1372
+ }
1373
+ this.writeOK(res);
1374
+ return;
1375
+ }
1376
+ if (event.subtype === "message_deleted") {
1377
+ if (!this.config.onOperatorMessageDelete) {
1378
+ this.writeOK(res);
1379
+ return;
1380
+ }
1381
+ const botId = event.previous_message?.bot_id ?? event.bot_id;
1382
+ if (botId && !isAllowedBot(botId)) {
1383
+ this.writeOK(res);
1384
+ return;
1385
+ }
1386
+ const threadTs = event.previous_message?.thread_ts;
1387
+ const messageTs = event.deleted_ts ?? event.previous_message?.ts;
1388
+ if (threadTs && messageTs) {
1389
+ await this.config.onOperatorMessageDelete(
1390
+ threadTs,
1391
+ messageTs,
1392
+ "slack",
1393
+ (/* @__PURE__ */ new Date()).toISOString()
1394
+ );
1395
+ }
1396
+ this.writeOK(res);
1397
+ return;
1398
+ }
1399
+ const hasContent = event.type === "message" && event.thread_ts && (!event.bot_id || isAllowedBot(event.bot_id)) && !event.subtype;
1289
1400
  const hasFiles = event.files && event.files.length > 0;
1290
1401
  if (hasContent && (event.text || hasFiles)) {
1291
1402
  const threadTs = event.thread_ts;
@@ -1315,7 +1426,9 @@ var WebhookHandler = class {
1315
1426
  text,
1316
1427
  operatorName,
1317
1428
  "slack",
1318
- attachments
1429
+ attachments,
1430
+ null,
1431
+ event.ts ?? null
1319
1432
  );
1320
1433
  }
1321
1434
  }
@@ -1353,7 +1466,8 @@ var WebhookHandler = class {
1353
1466
  content,
1354
1467
  operatorName,
1355
1468
  "discord",
1356
- []
1469
+ [],
1470
+ null
1357
1471
  );
1358
1472
  res.setHeader("Content-Type", "application/json");
1359
1473
  res.end(
@@ -1465,7 +1579,8 @@ var TelegramBridge = class {
1465
1579
  /**
1466
1580
  * Initialize the bridge (optional setup)
1467
1581
  */
1468
- async init(_pocketping) {
1582
+ async init(pocketping) {
1583
+ this.pocketping = pocketping;
1469
1584
  try {
1470
1585
  const response = await fetch(`${this.baseUrl}/getMe`);
1471
1586
  const data = await response.json();
@@ -1494,8 +1609,18 @@ var TelegramBridge = class {
1494
1609
  */
1495
1610
  async onVisitorMessage(message, session) {
1496
1611
  const text = this.formatVisitorMessage(session.visitorId, message.content);
1612
+ let replyToMessageId;
1613
+ if (message.replyTo) {
1614
+ const storage = this.pocketping?.getStorage();
1615
+ if (storage?.getBridgeMessageIds) {
1616
+ const ids = await storage.getBridgeMessageIds(message.replyTo);
1617
+ if (ids?.telegramMessageId) {
1618
+ replyToMessageId = ids.telegramMessageId;
1619
+ }
1620
+ }
1621
+ }
1497
1622
  try {
1498
- const messageId = await this.sendMessage(text);
1623
+ const messageId = await this.sendMessage(text, replyToMessageId);
1499
1624
  return { messageId };
1500
1625
  } catch (error) {
1501
1626
  console.error("[TelegramBridge] Failed to send visitor message:", error);
@@ -1632,7 +1757,7 @@ ${dataStr}
1632
1757
  /**
1633
1758
  * Send a message to the Telegram chat
1634
1759
  */
1635
- async sendMessage(text) {
1760
+ async sendMessage(text, replyToMessageId) {
1636
1761
  const response = await fetch(`${this.baseUrl}/sendMessage`, {
1637
1762
  method: "POST",
1638
1763
  headers: { "Content-Type": "application/json" },
@@ -1640,7 +1765,8 @@ ${dataStr}
1640
1765
  chat_id: this.chatId,
1641
1766
  text,
1642
1767
  parse_mode: this.parseMode,
1643
- disable_notification: this.disableNotification
1768
+ disable_notification: this.disableNotification,
1769
+ ...replyToMessageId ? { reply_to_message_id: replyToMessageId } : {}
1644
1770
  })
1645
1771
  });
1646
1772
  const data = await response.json();
@@ -1737,7 +1863,8 @@ var DiscordBridge = class _DiscordBridge {
1737
1863
  /**
1738
1864
  * Initialize the bridge (optional setup)
1739
1865
  */
1740
- async init(_pocketping) {
1866
+ async init(pocketping) {
1867
+ this.pocketping = pocketping;
1741
1868
  if (this.mode === "bot" && this.botToken) {
1742
1869
  try {
1743
1870
  const response = await fetch("https://discord.com/api/v10/users/@me", {
@@ -1787,8 +1914,18 @@ var DiscordBridge = class _DiscordBridge {
1787
1914
  // Green
1788
1915
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
1789
1916
  };
1917
+ let replyToMessageId;
1918
+ if (message.replyTo) {
1919
+ const storage = this.pocketping?.getStorage();
1920
+ if (storage?.getBridgeMessageIds) {
1921
+ const ids = await storage.getBridgeMessageIds(message.replyTo);
1922
+ if (ids?.discordMessageId) {
1923
+ replyToMessageId = ids.discordMessageId;
1924
+ }
1925
+ }
1926
+ }
1790
1927
  try {
1791
- const messageId = await this.sendEmbed(embed);
1928
+ const messageId = await this.sendEmbed(embed, replyToMessageId);
1792
1929
  return { messageId };
1793
1930
  } catch (error) {
1794
1931
  console.error("[DiscordBridge] Failed to send visitor message:", error);
@@ -1978,7 +2115,7 @@ ${JSON.stringify(event.data, null, 2)}
1978
2115
  /**
1979
2116
  * Send an embed to Discord
1980
2117
  */
1981
- async sendEmbed(embed) {
2118
+ async sendEmbed(embed, replyToMessageId) {
1982
2119
  const body = {
1983
2120
  embeds: [embed]
1984
2121
  };
@@ -1989,6 +2126,9 @@ ${JSON.stringify(event.data, null, 2)}
1989
2126
  body.avatar_url = this.avatarUrl;
1990
2127
  }
1991
2128
  if (this.mode === "webhook" && this.webhookUrl) {
2129
+ if (replyToMessageId) {
2130
+ body.message_reference = { message_id: replyToMessageId };
2131
+ }
1992
2132
  const response = await fetch(`${this.webhookUrl}?wait=true`, {
1993
2133
  method: "POST",
1994
2134
  headers: { "Content-Type": "application/json" },
@@ -2001,6 +2141,9 @@ ${JSON.stringify(event.data, null, 2)}
2001
2141
  const data = await response.json();
2002
2142
  return data.id;
2003
2143
  } else if (this.mode === "bot" && this.channelId) {
2144
+ if (replyToMessageId) {
2145
+ body.message_reference = { message_id: replyToMessageId };
2146
+ }
2004
2147
  const response = await fetch(
2005
2148
  `https://discord.com/api/v10/channels/${this.channelId}/messages`,
2006
2149
  {
@@ -2063,7 +2206,8 @@ var SlackBridge = class _SlackBridge {
2063
2206
  /**
2064
2207
  * Initialize the bridge (optional setup)
2065
2208
  */
2066
- async init(_pocketping) {
2209
+ async init(pocketping) {
2210
+ this.pocketping = pocketping;
2067
2211
  if (this.mode === "bot" && this.botToken) {
2068
2212
  try {
2069
2213
  const response = await fetch("https://slack.com/api/auth.test", {
@@ -2123,7 +2267,24 @@ ${url}`
2123
2267
  * Returns the Slack message timestamp for edit/delete sync.
2124
2268
  */
2125
2269
  async onVisitorMessage(message, session) {
2126
- const blocks = [
2270
+ const blocks = [];
2271
+ if (message.replyTo && this.pocketping?.getStorage().getMessage) {
2272
+ const replyTarget = await this.pocketping.getStorage().getMessage(message.replyTo);
2273
+ if (replyTarget) {
2274
+ const senderLabel = replyTarget.sender === "visitor" ? "Visitor" : replyTarget.sender === "operator" ? "Support" : "AI";
2275
+ const rawPreview = replyTarget.deletedAt ? "Message deleted" : replyTarget.content || "Message";
2276
+ const preview = rawPreview.length > 140 ? `${rawPreview.slice(0, 140)}...` : rawPreview;
2277
+ const quoted = `> *${this.escapeSlack(senderLabel)}* \u2014 ${this.escapeSlack(preview)}`;
2278
+ blocks.push({
2279
+ type: "section",
2280
+ text: {
2281
+ type: "mrkdwn",
2282
+ text: quoted
2283
+ }
2284
+ });
2285
+ }
2286
+ }
2287
+ blocks.push(
2127
2288
  {
2128
2289
  type: "section",
2129
2290
  text: {
@@ -2141,7 +2302,7 @@ ${this.escapeSlack(message.content)}`
2141
2302
  }
2142
2303
  ]
2143
2304
  }
2144
- ];
2305
+ );
2145
2306
  try {
2146
2307
  const messageId = await this.sendBlocks(blocks);
2147
2308
  return { messageId };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pocketping/sdk-node",
3
- "version": "1.3.0",
3
+ "version": "1.5.0",
4
4
  "type": "module",
5
5
  "description": "Node.js SDK for implementing PocketPing protocol",
6
6
  "main": "dist/index.cjs",