@pocketping/sdk-node 1.3.0 → 1.4.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,6 +1212,35 @@ 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
+ }
1215
1244
  if (update.message) {
1216
1245
  const msg = update.message;
1217
1246
  if (msg.text?.startsWith("/")) {
@@ -1267,6 +1296,7 @@ var WebhookHandler = class {
1267
1296
  return;
1268
1297
  }
1269
1298
  const operatorName = msg.from?.first_name ?? "Operator";
1299
+ const replyToBridgeMessageId = msg.reply_to_message?.message_id ?? null;
1270
1300
  const attachments = [];
1271
1301
  if (media) {
1272
1302
  const data = await this.downloadTelegramFile(media.fileId);
@@ -1285,7 +1315,9 @@ var WebhookHandler = class {
1285
1315
  text,
1286
1316
  operatorName,
1287
1317
  "telegram",
1288
- attachments
1318
+ attachments,
1319
+ replyToBridgeMessageId,
1320
+ msg.message_id
1289
1321
  );
1290
1322
  }
1291
1323
  this.writeOK(res);
@@ -1316,7 +1348,56 @@ var WebhookHandler = class {
1316
1348
  }
1317
1349
  if (payload.type === "event_callback" && payload.event) {
1318
1350
  const event = payload.event;
1319
- const hasContent = event.type === "message" && event.thread_ts && !event.bot_id && !event.subtype;
1351
+ const isAllowedBot = (botId) => !!botId && (this.config.allowedBotIds?.includes(botId) ?? false);
1352
+ if (event.subtype === "message_changed") {
1353
+ if (!this.config.onOperatorMessageEdit) {
1354
+ this.writeOK(res);
1355
+ return;
1356
+ }
1357
+ const botId = event.message?.bot_id ?? event.previous_message?.bot_id ?? event.bot_id;
1358
+ if (botId && !isAllowedBot(botId)) {
1359
+ this.writeOK(res);
1360
+ return;
1361
+ }
1362
+ const threadTs = event.message?.thread_ts ?? event.previous_message?.thread_ts;
1363
+ const messageTs = event.message?.ts ?? event.previous_message?.ts;
1364
+ const text = event.message?.text ?? "";
1365
+ if (threadTs && messageTs) {
1366
+ await this.config.onOperatorMessageEdit(
1367
+ threadTs,
1368
+ messageTs,
1369
+ text,
1370
+ "slack",
1371
+ (/* @__PURE__ */ new Date()).toISOString()
1372
+ );
1373
+ }
1374
+ this.writeOK(res);
1375
+ return;
1376
+ }
1377
+ if (event.subtype === "message_deleted") {
1378
+ if (!this.config.onOperatorMessageDelete) {
1379
+ this.writeOK(res);
1380
+ return;
1381
+ }
1382
+ const botId = event.previous_message?.bot_id ?? event.bot_id;
1383
+ if (botId && !isAllowedBot(botId)) {
1384
+ this.writeOK(res);
1385
+ return;
1386
+ }
1387
+ const threadTs = event.previous_message?.thread_ts;
1388
+ const messageTs = event.deleted_ts ?? event.previous_message?.ts;
1389
+ if (threadTs && messageTs) {
1390
+ await this.config.onOperatorMessageDelete(
1391
+ threadTs,
1392
+ messageTs,
1393
+ "slack",
1394
+ (/* @__PURE__ */ new Date()).toISOString()
1395
+ );
1396
+ }
1397
+ this.writeOK(res);
1398
+ return;
1399
+ }
1400
+ const hasContent = event.type === "message" && event.thread_ts && (!event.bot_id || isAllowedBot(event.bot_id)) && !event.subtype;
1320
1401
  const hasFiles = event.files && event.files.length > 0;
1321
1402
  if (hasContent && (event.text || hasFiles)) {
1322
1403
  const threadTs = event.thread_ts;
@@ -1346,7 +1427,9 @@ var WebhookHandler = class {
1346
1427
  text,
1347
1428
  operatorName,
1348
1429
  "slack",
1349
- attachments
1430
+ attachments,
1431
+ null,
1432
+ event.ts ?? null
1350
1433
  );
1351
1434
  }
1352
1435
  }
@@ -1384,7 +1467,8 @@ var WebhookHandler = class {
1384
1467
  content,
1385
1468
  operatorName,
1386
1469
  "discord",
1387
- []
1470
+ [],
1471
+ null
1388
1472
  );
1389
1473
  res.setHeader("Content-Type", "application/json");
1390
1474
  res.end(
@@ -1496,7 +1580,8 @@ var TelegramBridge = class {
1496
1580
  /**
1497
1581
  * Initialize the bridge (optional setup)
1498
1582
  */
1499
- async init(_pocketping) {
1583
+ async init(pocketping) {
1584
+ this.pocketping = pocketping;
1500
1585
  try {
1501
1586
  const response = await fetch(`${this.baseUrl}/getMe`);
1502
1587
  const data = await response.json();
@@ -1525,8 +1610,18 @@ var TelegramBridge = class {
1525
1610
  */
1526
1611
  async onVisitorMessage(message, session) {
1527
1612
  const text = this.formatVisitorMessage(session.visitorId, message.content);
1613
+ let replyToMessageId;
1614
+ if (message.replyTo) {
1615
+ const storage = this.pocketping?.getStorage();
1616
+ if (storage?.getBridgeMessageIds) {
1617
+ const ids = await storage.getBridgeMessageIds(message.replyTo);
1618
+ if (ids?.telegramMessageId) {
1619
+ replyToMessageId = ids.telegramMessageId;
1620
+ }
1621
+ }
1622
+ }
1528
1623
  try {
1529
- const messageId = await this.sendMessage(text);
1624
+ const messageId = await this.sendMessage(text, replyToMessageId);
1530
1625
  return { messageId };
1531
1626
  } catch (error) {
1532
1627
  console.error("[TelegramBridge] Failed to send visitor message:", error);
@@ -1663,7 +1758,7 @@ ${dataStr}
1663
1758
  /**
1664
1759
  * Send a message to the Telegram chat
1665
1760
  */
1666
- async sendMessage(text) {
1761
+ async sendMessage(text, replyToMessageId) {
1667
1762
  const response = await fetch(`${this.baseUrl}/sendMessage`, {
1668
1763
  method: "POST",
1669
1764
  headers: { "Content-Type": "application/json" },
@@ -1671,7 +1766,8 @@ ${dataStr}
1671
1766
  chat_id: this.chatId,
1672
1767
  text,
1673
1768
  parse_mode: this.parseMode,
1674
- disable_notification: this.disableNotification
1769
+ disable_notification: this.disableNotification,
1770
+ ...replyToMessageId ? { reply_to_message_id: replyToMessageId } : {}
1675
1771
  })
1676
1772
  });
1677
1773
  const data = await response.json();
@@ -1768,7 +1864,8 @@ var DiscordBridge = class _DiscordBridge {
1768
1864
  /**
1769
1865
  * Initialize the bridge (optional setup)
1770
1866
  */
1771
- async init(_pocketping) {
1867
+ async init(pocketping) {
1868
+ this.pocketping = pocketping;
1772
1869
  if (this.mode === "bot" && this.botToken) {
1773
1870
  try {
1774
1871
  const response = await fetch("https://discord.com/api/v10/users/@me", {
@@ -1818,8 +1915,18 @@ var DiscordBridge = class _DiscordBridge {
1818
1915
  // Green
1819
1916
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
1820
1917
  };
1918
+ let replyToMessageId;
1919
+ if (message.replyTo) {
1920
+ const storage = this.pocketping?.getStorage();
1921
+ if (storage?.getBridgeMessageIds) {
1922
+ const ids = await storage.getBridgeMessageIds(message.replyTo);
1923
+ if (ids?.discordMessageId) {
1924
+ replyToMessageId = ids.discordMessageId;
1925
+ }
1926
+ }
1927
+ }
1821
1928
  try {
1822
- const messageId = await this.sendEmbed(embed);
1929
+ const messageId = await this.sendEmbed(embed, replyToMessageId);
1823
1930
  return { messageId };
1824
1931
  } catch (error) {
1825
1932
  console.error("[DiscordBridge] Failed to send visitor message:", error);
@@ -2009,7 +2116,7 @@ ${JSON.stringify(event.data, null, 2)}
2009
2116
  /**
2010
2117
  * Send an embed to Discord
2011
2118
  */
2012
- async sendEmbed(embed) {
2119
+ async sendEmbed(embed, replyToMessageId) {
2013
2120
  const body = {
2014
2121
  embeds: [embed]
2015
2122
  };
@@ -2020,6 +2127,9 @@ ${JSON.stringify(event.data, null, 2)}
2020
2127
  body.avatar_url = this.avatarUrl;
2021
2128
  }
2022
2129
  if (this.mode === "webhook" && this.webhookUrl) {
2130
+ if (replyToMessageId) {
2131
+ body.message_reference = { message_id: replyToMessageId };
2132
+ }
2023
2133
  const response = await fetch(`${this.webhookUrl}?wait=true`, {
2024
2134
  method: "POST",
2025
2135
  headers: { "Content-Type": "application/json" },
@@ -2032,6 +2142,9 @@ ${JSON.stringify(event.data, null, 2)}
2032
2142
  const data = await response.json();
2033
2143
  return data.id;
2034
2144
  } else if (this.mode === "bot" && this.channelId) {
2145
+ if (replyToMessageId) {
2146
+ body.message_reference = { message_id: replyToMessageId };
2147
+ }
2035
2148
  const response = await fetch(
2036
2149
  `https://discord.com/api/v10/channels/${this.channelId}/messages`,
2037
2150
  {
@@ -2094,7 +2207,8 @@ var SlackBridge = class _SlackBridge {
2094
2207
  /**
2095
2208
  * Initialize the bridge (optional setup)
2096
2209
  */
2097
- async init(_pocketping) {
2210
+ async init(pocketping) {
2211
+ this.pocketping = pocketping;
2098
2212
  if (this.mode === "bot" && this.botToken) {
2099
2213
  try {
2100
2214
  const response = await fetch("https://slack.com/api/auth.test", {
@@ -2154,7 +2268,24 @@ ${url}`
2154
2268
  * Returns the Slack message timestamp for edit/delete sync.
2155
2269
  */
2156
2270
  async onVisitorMessage(message, session) {
2157
- const blocks = [
2271
+ const blocks = [];
2272
+ if (message.replyTo && this.pocketping?.getStorage().getMessage) {
2273
+ const replyTarget = await this.pocketping.getStorage().getMessage(message.replyTo);
2274
+ if (replyTarget) {
2275
+ const senderLabel = replyTarget.sender === "visitor" ? "Visitor" : replyTarget.sender === "operator" ? "Support" : "AI";
2276
+ const rawPreview = replyTarget.deletedAt ? "Message deleted" : replyTarget.content || "Message";
2277
+ const preview = rawPreview.length > 140 ? `${rawPreview.slice(0, 140)}...` : rawPreview;
2278
+ const quoted = `> *${this.escapeSlack(senderLabel)}* \u2014 ${this.escapeSlack(preview)}`;
2279
+ blocks.push({
2280
+ type: "section",
2281
+ text: {
2282
+ type: "mrkdwn",
2283
+ text: quoted
2284
+ }
2285
+ });
2286
+ }
2287
+ }
2288
+ blocks.push(
2158
2289
  {
2159
2290
  type: "section",
2160
2291
  text: {
@@ -2172,7 +2303,7 @@ ${this.escapeSlack(message.content)}`
2172
2303
  }
2173
2304
  ]
2174
2305
  }
2175
- ];
2306
+ );
2176
2307
  try {
2177
2308
  const messageId = await this.sendBlocks(blocks);
2178
2309
  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,6 +1181,35 @@ 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
+ }
1184
1213
  if (update.message) {
1185
1214
  const msg = update.message;
1186
1215
  if (msg.text?.startsWith("/")) {
@@ -1236,6 +1265,7 @@ var WebhookHandler = class {
1236
1265
  return;
1237
1266
  }
1238
1267
  const operatorName = msg.from?.first_name ?? "Operator";
1268
+ const replyToBridgeMessageId = msg.reply_to_message?.message_id ?? null;
1239
1269
  const attachments = [];
1240
1270
  if (media) {
1241
1271
  const data = await this.downloadTelegramFile(media.fileId);
@@ -1254,7 +1284,9 @@ var WebhookHandler = class {
1254
1284
  text,
1255
1285
  operatorName,
1256
1286
  "telegram",
1257
- attachments
1287
+ attachments,
1288
+ replyToBridgeMessageId,
1289
+ msg.message_id
1258
1290
  );
1259
1291
  }
1260
1292
  this.writeOK(res);
@@ -1285,7 +1317,56 @@ var WebhookHandler = class {
1285
1317
  }
1286
1318
  if (payload.type === "event_callback" && payload.event) {
1287
1319
  const event = payload.event;
1288
- const hasContent = event.type === "message" && event.thread_ts && !event.bot_id && !event.subtype;
1320
+ const isAllowedBot = (botId) => !!botId && (this.config.allowedBotIds?.includes(botId) ?? false);
1321
+ if (event.subtype === "message_changed") {
1322
+ if (!this.config.onOperatorMessageEdit) {
1323
+ this.writeOK(res);
1324
+ return;
1325
+ }
1326
+ const botId = event.message?.bot_id ?? event.previous_message?.bot_id ?? event.bot_id;
1327
+ if (botId && !isAllowedBot(botId)) {
1328
+ this.writeOK(res);
1329
+ return;
1330
+ }
1331
+ const threadTs = event.message?.thread_ts ?? event.previous_message?.thread_ts;
1332
+ const messageTs = event.message?.ts ?? event.previous_message?.ts;
1333
+ const text = event.message?.text ?? "";
1334
+ if (threadTs && messageTs) {
1335
+ await this.config.onOperatorMessageEdit(
1336
+ threadTs,
1337
+ messageTs,
1338
+ text,
1339
+ "slack",
1340
+ (/* @__PURE__ */ new Date()).toISOString()
1341
+ );
1342
+ }
1343
+ this.writeOK(res);
1344
+ return;
1345
+ }
1346
+ if (event.subtype === "message_deleted") {
1347
+ if (!this.config.onOperatorMessageDelete) {
1348
+ this.writeOK(res);
1349
+ return;
1350
+ }
1351
+ const botId = event.previous_message?.bot_id ?? event.bot_id;
1352
+ if (botId && !isAllowedBot(botId)) {
1353
+ this.writeOK(res);
1354
+ return;
1355
+ }
1356
+ const threadTs = event.previous_message?.thread_ts;
1357
+ const messageTs = event.deleted_ts ?? event.previous_message?.ts;
1358
+ if (threadTs && messageTs) {
1359
+ await this.config.onOperatorMessageDelete(
1360
+ threadTs,
1361
+ messageTs,
1362
+ "slack",
1363
+ (/* @__PURE__ */ new Date()).toISOString()
1364
+ );
1365
+ }
1366
+ this.writeOK(res);
1367
+ return;
1368
+ }
1369
+ const hasContent = event.type === "message" && event.thread_ts && (!event.bot_id || isAllowedBot(event.bot_id)) && !event.subtype;
1289
1370
  const hasFiles = event.files && event.files.length > 0;
1290
1371
  if (hasContent && (event.text || hasFiles)) {
1291
1372
  const threadTs = event.thread_ts;
@@ -1315,7 +1396,9 @@ var WebhookHandler = class {
1315
1396
  text,
1316
1397
  operatorName,
1317
1398
  "slack",
1318
- attachments
1399
+ attachments,
1400
+ null,
1401
+ event.ts ?? null
1319
1402
  );
1320
1403
  }
1321
1404
  }
@@ -1353,7 +1436,8 @@ var WebhookHandler = class {
1353
1436
  content,
1354
1437
  operatorName,
1355
1438
  "discord",
1356
- []
1439
+ [],
1440
+ null
1357
1441
  );
1358
1442
  res.setHeader("Content-Type", "application/json");
1359
1443
  res.end(
@@ -1465,7 +1549,8 @@ var TelegramBridge = class {
1465
1549
  /**
1466
1550
  * Initialize the bridge (optional setup)
1467
1551
  */
1468
- async init(_pocketping) {
1552
+ async init(pocketping) {
1553
+ this.pocketping = pocketping;
1469
1554
  try {
1470
1555
  const response = await fetch(`${this.baseUrl}/getMe`);
1471
1556
  const data = await response.json();
@@ -1494,8 +1579,18 @@ var TelegramBridge = class {
1494
1579
  */
1495
1580
  async onVisitorMessage(message, session) {
1496
1581
  const text = this.formatVisitorMessage(session.visitorId, message.content);
1582
+ let replyToMessageId;
1583
+ if (message.replyTo) {
1584
+ const storage = this.pocketping?.getStorage();
1585
+ if (storage?.getBridgeMessageIds) {
1586
+ const ids = await storage.getBridgeMessageIds(message.replyTo);
1587
+ if (ids?.telegramMessageId) {
1588
+ replyToMessageId = ids.telegramMessageId;
1589
+ }
1590
+ }
1591
+ }
1497
1592
  try {
1498
- const messageId = await this.sendMessage(text);
1593
+ const messageId = await this.sendMessage(text, replyToMessageId);
1499
1594
  return { messageId };
1500
1595
  } catch (error) {
1501
1596
  console.error("[TelegramBridge] Failed to send visitor message:", error);
@@ -1632,7 +1727,7 @@ ${dataStr}
1632
1727
  /**
1633
1728
  * Send a message to the Telegram chat
1634
1729
  */
1635
- async sendMessage(text) {
1730
+ async sendMessage(text, replyToMessageId) {
1636
1731
  const response = await fetch(`${this.baseUrl}/sendMessage`, {
1637
1732
  method: "POST",
1638
1733
  headers: { "Content-Type": "application/json" },
@@ -1640,7 +1735,8 @@ ${dataStr}
1640
1735
  chat_id: this.chatId,
1641
1736
  text,
1642
1737
  parse_mode: this.parseMode,
1643
- disable_notification: this.disableNotification
1738
+ disable_notification: this.disableNotification,
1739
+ ...replyToMessageId ? { reply_to_message_id: replyToMessageId } : {}
1644
1740
  })
1645
1741
  });
1646
1742
  const data = await response.json();
@@ -1737,7 +1833,8 @@ var DiscordBridge = class _DiscordBridge {
1737
1833
  /**
1738
1834
  * Initialize the bridge (optional setup)
1739
1835
  */
1740
- async init(_pocketping) {
1836
+ async init(pocketping) {
1837
+ this.pocketping = pocketping;
1741
1838
  if (this.mode === "bot" && this.botToken) {
1742
1839
  try {
1743
1840
  const response = await fetch("https://discord.com/api/v10/users/@me", {
@@ -1787,8 +1884,18 @@ var DiscordBridge = class _DiscordBridge {
1787
1884
  // Green
1788
1885
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
1789
1886
  };
1887
+ let replyToMessageId;
1888
+ if (message.replyTo) {
1889
+ const storage = this.pocketping?.getStorage();
1890
+ if (storage?.getBridgeMessageIds) {
1891
+ const ids = await storage.getBridgeMessageIds(message.replyTo);
1892
+ if (ids?.discordMessageId) {
1893
+ replyToMessageId = ids.discordMessageId;
1894
+ }
1895
+ }
1896
+ }
1790
1897
  try {
1791
- const messageId = await this.sendEmbed(embed);
1898
+ const messageId = await this.sendEmbed(embed, replyToMessageId);
1792
1899
  return { messageId };
1793
1900
  } catch (error) {
1794
1901
  console.error("[DiscordBridge] Failed to send visitor message:", error);
@@ -1978,7 +2085,7 @@ ${JSON.stringify(event.data, null, 2)}
1978
2085
  /**
1979
2086
  * Send an embed to Discord
1980
2087
  */
1981
- async sendEmbed(embed) {
2088
+ async sendEmbed(embed, replyToMessageId) {
1982
2089
  const body = {
1983
2090
  embeds: [embed]
1984
2091
  };
@@ -1989,6 +2096,9 @@ ${JSON.stringify(event.data, null, 2)}
1989
2096
  body.avatar_url = this.avatarUrl;
1990
2097
  }
1991
2098
  if (this.mode === "webhook" && this.webhookUrl) {
2099
+ if (replyToMessageId) {
2100
+ body.message_reference = { message_id: replyToMessageId };
2101
+ }
1992
2102
  const response = await fetch(`${this.webhookUrl}?wait=true`, {
1993
2103
  method: "POST",
1994
2104
  headers: { "Content-Type": "application/json" },
@@ -2001,6 +2111,9 @@ ${JSON.stringify(event.data, null, 2)}
2001
2111
  const data = await response.json();
2002
2112
  return data.id;
2003
2113
  } else if (this.mode === "bot" && this.channelId) {
2114
+ if (replyToMessageId) {
2115
+ body.message_reference = { message_id: replyToMessageId };
2116
+ }
2004
2117
  const response = await fetch(
2005
2118
  `https://discord.com/api/v10/channels/${this.channelId}/messages`,
2006
2119
  {
@@ -2063,7 +2176,8 @@ var SlackBridge = class _SlackBridge {
2063
2176
  /**
2064
2177
  * Initialize the bridge (optional setup)
2065
2178
  */
2066
- async init(_pocketping) {
2179
+ async init(pocketping) {
2180
+ this.pocketping = pocketping;
2067
2181
  if (this.mode === "bot" && this.botToken) {
2068
2182
  try {
2069
2183
  const response = await fetch("https://slack.com/api/auth.test", {
@@ -2123,7 +2237,24 @@ ${url}`
2123
2237
  * Returns the Slack message timestamp for edit/delete sync.
2124
2238
  */
2125
2239
  async onVisitorMessage(message, session) {
2126
- const blocks = [
2240
+ const blocks = [];
2241
+ if (message.replyTo && this.pocketping?.getStorage().getMessage) {
2242
+ const replyTarget = await this.pocketping.getStorage().getMessage(message.replyTo);
2243
+ if (replyTarget) {
2244
+ const senderLabel = replyTarget.sender === "visitor" ? "Visitor" : replyTarget.sender === "operator" ? "Support" : "AI";
2245
+ const rawPreview = replyTarget.deletedAt ? "Message deleted" : replyTarget.content || "Message";
2246
+ const preview = rawPreview.length > 140 ? `${rawPreview.slice(0, 140)}...` : rawPreview;
2247
+ const quoted = `> *${this.escapeSlack(senderLabel)}* \u2014 ${this.escapeSlack(preview)}`;
2248
+ blocks.push({
2249
+ type: "section",
2250
+ text: {
2251
+ type: "mrkdwn",
2252
+ text: quoted
2253
+ }
2254
+ });
2255
+ }
2256
+ }
2257
+ blocks.push(
2127
2258
  {
2128
2259
  type: "section",
2129
2260
  text: {
@@ -2141,7 +2272,7 @@ ${this.escapeSlack(message.content)}`
2141
2272
  }
2142
2273
  ]
2143
2274
  }
2144
- ];
2275
+ );
2145
2276
  try {
2146
2277
  const messageId = await this.sendBlocks(blocks);
2147
2278
  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.4.0",
4
4
  "type": "module",
5
5
  "description": "Node.js SDK for implementing PocketPing protocol",
6
6
  "main": "dist/index.cjs",