@pocketping/sdk-node 1.2.0 → 1.3.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/dist/index.d.cts CHANGED
@@ -1,5 +1,14 @@
1
1
  import { IncomingMessage, ServerResponse } from 'http';
2
2
 
3
+ /**
4
+ * Bridge message IDs for edit/delete sync.
5
+ * Stored when a message is sent to bridges.
6
+ */
7
+ interface BridgeMessageIds {
8
+ telegramMessageId?: number;
9
+ discordMessageId?: string;
10
+ slackMessageTs?: string;
11
+ }
3
12
  /**
4
13
  * Storage adapter interface.
5
14
  * Implement this interface to use any database with PocketPing.
@@ -13,9 +22,23 @@ interface Storage {
13
22
  saveMessage(message: Message): Promise<void>;
14
23
  getMessages(sessionId: string, after?: string, limit?: number): Promise<Message[]>;
15
24
  getMessage(messageId: string): Promise<Message | null>;
25
+ /** Update an existing message (for edit/delete) */
26
+ updateMessage?(message: Message): Promise<void>;
27
+ /** Save bridge message IDs for a message */
28
+ saveBridgeMessageIds?(messageId: string, bridgeIds: BridgeMessageIds): Promise<void>;
29
+ /** Get bridge message IDs for a message */
30
+ getBridgeMessageIds?(messageId: string): Promise<BridgeMessageIds | null>;
16
31
  cleanupOldSessions?(olderThan: Date): Promise<number>;
17
32
  }
18
33
 
34
+ /**
35
+ * Result from sending a message to a bridge.
36
+ * Contains the bridge-specific message ID for later edit/delete.
37
+ */
38
+ interface BridgeMessageResult {
39
+ /** Bridge-specific message ID */
40
+ messageId?: string | number;
41
+ }
19
42
  /**
20
43
  * Bridge interface for notification channels.
21
44
  * Implement this interface to add support for Telegram, Discord, Slack, etc.
@@ -27,14 +50,32 @@ interface Bridge {
27
50
  init?(pocketping: PocketPing): void | Promise<void>;
28
51
  /** Called when a new chat session is created */
29
52
  onNewSession?(session: Session): void | Promise<void>;
30
- /** Called when a visitor sends a message */
31
- onVisitorMessage?(message: Message, session: Session): void | Promise<void>;
53
+ /**
54
+ * Called when a visitor sends a message.
55
+ * Return the bridge message ID for edit/delete sync.
56
+ */
57
+ onVisitorMessage?(message: Message, session: Session): void | BridgeMessageResult | Promise<void | BridgeMessageResult>;
32
58
  /** Called when an operator sends a message (for cross-bridge sync) */
33
59
  onOperatorMessage?(message: Message, session: Session, sourceBridge?: string, operatorName?: string): void | Promise<void>;
34
60
  /** Called when visitor starts/stops typing */
35
61
  onTyping?(sessionId: string, isTyping: boolean): void | Promise<void>;
36
62
  /** Called when messages are marked as delivered/read */
37
63
  onMessageRead?(sessionId: string, messageIds: string[], status: MessageStatus, session: Session): void | Promise<void>;
64
+ /**
65
+ * Called when a visitor edits their message.
66
+ * @param messageId - The message ID in PocketPing
67
+ * @param newContent - The new message content
68
+ * @param bridgeMessageId - The bridge-specific message ID
69
+ * @returns true if edit succeeded, false otherwise
70
+ */
71
+ onMessageEdit?(messageId: string, newContent: string, bridgeMessageId: string | number): boolean | Promise<boolean>;
72
+ /**
73
+ * Called when a visitor deletes their message.
74
+ * @param messageId - The message ID in PocketPing
75
+ * @param bridgeMessageId - The bridge-specific message ID
76
+ * @returns true if delete succeeded, false otherwise
77
+ */
78
+ onMessageDelete?(messageId: string, bridgeMessageId: string | number): boolean | Promise<boolean>;
38
79
  /** Called when a custom event is triggered from the widget */
39
80
  onCustomEvent?(event: CustomEvent, session: Session): void | Promise<void>;
40
81
  /** Called when a user identifies themselves via PocketPing.identify() */
@@ -184,6 +225,29 @@ interface SessionMetadata {
184
225
  [key: string]: unknown;
185
226
  }
186
227
  type MessageStatus = 'sending' | 'sent' | 'delivered' | 'read';
228
+ type AttachmentStatus = 'pending' | 'uploading' | 'ready' | 'failed';
229
+ type UploadSource = 'widget' | 'telegram' | 'discord' | 'slack' | 'api';
230
+ /** File attachment in a message */
231
+ interface Attachment {
232
+ /** Unique attachment ID */
233
+ id: string;
234
+ /** Original filename */
235
+ filename: string;
236
+ /** MIME type (e.g., 'image/jpeg', 'application/pdf') */
237
+ mimeType: string;
238
+ /** File size in bytes */
239
+ size: number;
240
+ /** URL to access the file */
241
+ url: string;
242
+ /** Thumbnail URL (for images/videos) */
243
+ thumbnailUrl?: string;
244
+ /** Upload status */
245
+ status: AttachmentStatus;
246
+ /** Source of the upload */
247
+ uploadedFrom?: UploadSource;
248
+ /** External file ID (from Telegram/Discord/Slack) */
249
+ bridgeFileId?: string;
250
+ }
187
251
  interface Message {
188
252
  id: string;
189
253
  sessionId: string;
@@ -192,9 +256,38 @@ interface Message {
192
256
  timestamp: Date;
193
257
  replyTo?: string;
194
258
  metadata?: Record<string, unknown>;
259
+ /** File attachments in this message */
260
+ attachments?: Attachment[];
195
261
  status?: MessageStatus;
196
262
  deliveredAt?: Date;
197
263
  readAt?: Date;
264
+ /** Timestamp when message was edited */
265
+ editedAt?: Date;
266
+ /** Timestamp when message was soft-deleted */
267
+ deletedAt?: Date;
268
+ }
269
+ /** Request to edit a message */
270
+ interface EditMessageRequest {
271
+ sessionId: string;
272
+ messageId: string;
273
+ content: string;
274
+ }
275
+ /** Response after editing a message */
276
+ interface EditMessageResponse {
277
+ message: {
278
+ id: string;
279
+ content: string;
280
+ editedAt: string;
281
+ };
282
+ }
283
+ /** Request to delete a message */
284
+ interface DeleteMessageRequest {
285
+ sessionId: string;
286
+ messageId: string;
287
+ }
288
+ /** Response after deleting a message */
289
+ interface DeleteMessageResponse {
290
+ deleted: boolean;
198
291
  }
199
292
  interface ConnectRequest {
200
293
  visitorId: string;
@@ -235,6 +328,10 @@ interface SendMessageRequest {
235
328
  content: string;
236
329
  sender: 'visitor' | 'operator';
237
330
  replyTo?: string;
331
+ /** Attachment IDs to include with the message */
332
+ attachmentIds?: string[];
333
+ /** Inline attachments (for operator messages from bridges) */
334
+ attachments?: Attachment[];
238
335
  }
239
336
  interface SendMessageResponse {
240
337
  messageId: string;
@@ -351,6 +448,16 @@ declare class PocketPing {
351
448
  * Get a session by ID
352
449
  */
353
450
  getSession(sessionId: string): Promise<Session | null>;
451
+ /**
452
+ * Handle message edit from widget
453
+ * Only the message sender can edit their own messages
454
+ */
455
+ handleEditMessage(request: EditMessageRequest): Promise<EditMessageResponse>;
456
+ /**
457
+ * Handle message delete from widget
458
+ * Only the message sender can delete their own messages
459
+ */
460
+ handleDeleteMessage(request: DeleteMessageRequest): Promise<DeleteMessageResponse>;
354
461
  sendOperatorMessage(sessionId: string, content: string): Promise<Message>;
355
462
  setOperatorOnline(online: boolean): void;
356
463
  /**
@@ -419,6 +526,14 @@ declare class PocketPing {
419
526
  private notifyBridgesRead;
420
527
  private notifyBridgesEvent;
421
528
  private notifyBridgesIdentity;
529
+ /**
530
+ * Sync message edit to all bridges that support it
531
+ */
532
+ private syncEditToBridges;
533
+ /**
534
+ * Sync message delete to all bridges that support it
535
+ */
536
+ private syncDeleteToBridges;
422
537
  /**
423
538
  * Forward custom event to configured webhook URL (non-blocking)
424
539
  * Used for integrations with Zapier, Make, n8n, or custom backends
@@ -455,6 +570,7 @@ declare class MemoryStorage implements Storage {
455
570
  private sessions;
456
571
  private messages;
457
572
  private messageById;
573
+ private bridgeMessageIds;
458
574
  createSession(session: Session): Promise<void>;
459
575
  getSession(sessionId: string): Promise<Session | null>;
460
576
  getSessionByVisitorId(visitorId: string): Promise<Session | null>;
@@ -463,7 +579,371 @@ declare class MemoryStorage implements Storage {
463
579
  saveMessage(message: Message): Promise<void>;
464
580
  getMessages(sessionId: string, after?: string, limit?: number): Promise<Message[]>;
465
581
  getMessage(messageId: string): Promise<Message | null>;
582
+ updateMessage(message: Message): Promise<void>;
583
+ saveBridgeMessageIds(messageId: string, bridgeIds: BridgeMessageIds): Promise<void>;
584
+ getBridgeMessageIds(messageId: string): Promise<BridgeMessageIds | null>;
466
585
  cleanupOldSessions(olderThan: Date): Promise<number>;
467
586
  }
468
587
 
469
- export { type AIProvider, type Bridge, type ConnectRequest, type ConnectResponse, type CustomEvent, type CustomEventHandler, MemoryStorage, type Message, PocketPing, type PocketPingConfig, type PresenceResponse, type SendMessageRequest, type SendMessageResponse, type Session, type Storage, type TrackedElement, type TriggerOptions, type WebhookPayload };
588
+ /** Attachment from an operator message */
589
+ interface OperatorAttachment {
590
+ filename: string;
591
+ mimeType: string;
592
+ size: number;
593
+ data: Buffer;
594
+ bridgeFileId?: string;
595
+ }
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>;
598
+ /** Webhook handler configuration */
599
+ interface WebhookConfig {
600
+ telegramBotToken?: string;
601
+ slackBotToken?: string;
602
+ discordBotToken?: string;
603
+ onOperatorMessage: OperatorMessageCallback;
604
+ }
605
+ declare class WebhookHandler {
606
+ private config;
607
+ constructor(config: WebhookConfig);
608
+ /**
609
+ * Create an Express/Connect middleware for handling Telegram webhooks
610
+ */
611
+ handleTelegramWebhook(): (req: IncomingMessage & {
612
+ body?: unknown;
613
+ }, res: ServerResponse) => Promise<void>;
614
+ /**
615
+ * Create an Express/Connect middleware for handling Slack webhooks
616
+ */
617
+ handleSlackWebhook(): (req: IncomingMessage & {
618
+ body?: unknown;
619
+ }, res: ServerResponse) => Promise<void>;
620
+ /**
621
+ * Create an Express/Connect middleware for handling Discord webhooks
622
+ */
623
+ handleDiscordWebhook(): (req: IncomingMessage & {
624
+ body?: unknown;
625
+ }, res: ServerResponse) => Promise<void>;
626
+ private parseBody;
627
+ private writeOK;
628
+ private downloadTelegramFile;
629
+ private downloadSlackFile;
630
+ private getSlackUserName;
631
+ }
632
+
633
+ /**
634
+ * Options for TelegramBridge
635
+ */
636
+ interface TelegramBridgeOptions {
637
+ /** Parse mode for message formatting */
638
+ parseMode?: 'HTML' | 'Markdown';
639
+ /** Disable notification sound */
640
+ disableNotification?: boolean;
641
+ }
642
+ /**
643
+ * Telegram Bridge for PocketPing.
644
+ * Sends visitor messages to a Telegram chat using the Bot API.
645
+ *
646
+ * @example
647
+ * ```ts
648
+ * const telegram = new TelegramBridge(
649
+ * 'BOT_TOKEN',
650
+ * '-1001234567890',
651
+ * { parseMode: 'HTML' }
652
+ * );
653
+ * const pocketping = new PocketPing({ bridges: [telegram] });
654
+ * ```
655
+ */
656
+ declare class TelegramBridge implements Bridge {
657
+ readonly name = "telegram";
658
+ private readonly botToken;
659
+ private readonly chatId;
660
+ private readonly parseMode;
661
+ private readonly disableNotification;
662
+ private readonly baseUrl;
663
+ constructor(botToken: string, chatId: string | number, options?: TelegramBridgeOptions);
664
+ /**
665
+ * Initialize the bridge (optional setup)
666
+ */
667
+ init(_pocketping: PocketPing): Promise<void>;
668
+ /**
669
+ * Called when a new chat session is created
670
+ */
671
+ onNewSession(session: Session): Promise<void>;
672
+ /**
673
+ * Called when a visitor sends a message.
674
+ * Returns the Telegram message ID for edit/delete sync.
675
+ */
676
+ onVisitorMessage(message: Message, session: Session): Promise<BridgeMessageResult>;
677
+ /**
678
+ * Called when an operator sends a message (for cross-bridge sync)
679
+ */
680
+ onOperatorMessage(message: Message, _session: Session, sourceBridge?: string, operatorName?: string): Promise<void>;
681
+ /**
682
+ * Called when visitor starts/stops typing
683
+ */
684
+ onTyping(sessionId: string, isTyping: boolean): Promise<void>;
685
+ /**
686
+ * Called when a visitor edits their message.
687
+ * @returns true if edit succeeded, false otherwise
688
+ */
689
+ onMessageEdit(_messageId: string, newContent: string, bridgeMessageId: string | number): Promise<boolean>;
690
+ /**
691
+ * Called when a visitor deletes their message.
692
+ * @returns true if delete succeeded, false otherwise
693
+ */
694
+ onMessageDelete(_messageId: string, bridgeMessageId: string | number): Promise<boolean>;
695
+ /**
696
+ * Called when a custom event is triggered from the widget
697
+ */
698
+ onCustomEvent(event: {
699
+ name: string;
700
+ data?: Record<string, unknown>;
701
+ }, session: Session): Promise<void>;
702
+ /**
703
+ * Called when a user identifies themselves via PocketPing.identify()
704
+ */
705
+ onIdentityUpdate(session: Session): Promise<void>;
706
+ /**
707
+ * Send a message to the Telegram chat
708
+ */
709
+ private sendMessage;
710
+ /**
711
+ * Send a chat action (e.g., "typing")
712
+ */
713
+ private sendChatAction;
714
+ /**
715
+ * Format new session notification
716
+ */
717
+ private formatNewSession;
718
+ /**
719
+ * Format visitor message
720
+ */
721
+ private formatVisitorMessage;
722
+ /**
723
+ * Escape HTML special characters
724
+ */
725
+ private escapeHtml;
726
+ /**
727
+ * Escape Markdown special characters
728
+ */
729
+ private escapeMarkdown;
730
+ }
731
+
732
+ /**
733
+ * Options for Discord webhook mode
734
+ */
735
+ interface DiscordWebhookOptions {
736
+ /** Custom username for webhook messages */
737
+ username?: string;
738
+ /** Custom avatar URL for webhook messages */
739
+ avatarUrl?: string;
740
+ }
741
+ /**
742
+ * Options for Discord bot mode
743
+ */
744
+ interface DiscordBotOptions {
745
+ /** Custom username displayed in embeds */
746
+ username?: string;
747
+ /** Custom avatar URL for embeds */
748
+ avatarUrl?: string;
749
+ }
750
+ /**
751
+ * Discord Bridge for PocketPing.
752
+ * Sends visitor messages to a Discord channel using webhooks or bot API.
753
+ *
754
+ * @example Webhook mode
755
+ * ```ts
756
+ * const discord = DiscordBridge.webhook(
757
+ * 'https://discord.com/api/webhooks/123/abc',
758
+ * { username: 'PocketPing' }
759
+ * );
760
+ * ```
761
+ *
762
+ * @example Bot mode
763
+ * ```ts
764
+ * const discord = DiscordBridge.bot(
765
+ * 'BOT_TOKEN',
766
+ * 'CHANNEL_ID',
767
+ * { username: 'PocketPing' }
768
+ * );
769
+ * ```
770
+ */
771
+ declare class DiscordBridge implements Bridge {
772
+ readonly name = "discord";
773
+ private readonly mode;
774
+ private readonly webhookUrl?;
775
+ private readonly botToken?;
776
+ private readonly channelId?;
777
+ private readonly username?;
778
+ private readonly avatarUrl?;
779
+ private constructor();
780
+ /**
781
+ * Create a Discord bridge using a webhook URL
782
+ */
783
+ static webhook(webhookUrl: string, options?: DiscordWebhookOptions): DiscordBridge;
784
+ /**
785
+ * Create a Discord bridge using a bot token
786
+ */
787
+ static bot(botToken: string, channelId: string, options?: DiscordBotOptions): DiscordBridge;
788
+ /**
789
+ * Initialize the bridge (optional setup)
790
+ */
791
+ init(_pocketping: PocketPing): Promise<void>;
792
+ /**
793
+ * Called when a new chat session is created
794
+ */
795
+ onNewSession(session: Session): Promise<void>;
796
+ /**
797
+ * Called when a visitor sends a message.
798
+ * Returns the Discord message ID for edit/delete sync.
799
+ */
800
+ onVisitorMessage(message: Message, session: Session): Promise<BridgeMessageResult>;
801
+ /**
802
+ * Called when an operator sends a message (for cross-bridge sync)
803
+ */
804
+ onOperatorMessage(message: Message, _session: Session, sourceBridge?: string, operatorName?: string): Promise<void>;
805
+ /**
806
+ * Called when visitor starts/stops typing
807
+ */
808
+ onTyping(_sessionId: string, isTyping: boolean): Promise<void>;
809
+ /**
810
+ * Called when a visitor edits their message.
811
+ * @returns true if edit succeeded, false otherwise
812
+ */
813
+ onMessageEdit(_messageId: string, newContent: string, bridgeMessageId: string | number): Promise<boolean>;
814
+ /**
815
+ * Called when a visitor deletes their message.
816
+ * @returns true if delete succeeded, false otherwise
817
+ */
818
+ onMessageDelete(_messageId: string, bridgeMessageId: string | number): Promise<boolean>;
819
+ /**
820
+ * Called when a custom event is triggered from the widget
821
+ */
822
+ onCustomEvent(event: {
823
+ name: string;
824
+ data?: Record<string, unknown>;
825
+ }, session: Session): Promise<void>;
826
+ /**
827
+ * Called when a user identifies themselves via PocketPing.identify()
828
+ */
829
+ onIdentityUpdate(session: Session): Promise<void>;
830
+ /**
831
+ * Send an embed to Discord
832
+ */
833
+ private sendEmbed;
834
+ }
835
+
836
+ /**
837
+ * Options for Slack webhook mode
838
+ */
839
+ interface SlackWebhookOptions {
840
+ /** Custom username for webhook messages */
841
+ username?: string;
842
+ /** Custom emoji icon (e.g., ':robot_face:') */
843
+ iconEmoji?: string;
844
+ /** Custom icon URL (overrides iconEmoji) */
845
+ iconUrl?: string;
846
+ }
847
+ /**
848
+ * Options for Slack bot mode
849
+ */
850
+ interface SlackBotOptions {
851
+ /** Custom username for bot messages */
852
+ username?: string;
853
+ /** Custom emoji icon (e.g., ':robot_face:') */
854
+ iconEmoji?: string;
855
+ /** Custom icon URL (overrides iconEmoji) */
856
+ iconUrl?: string;
857
+ }
858
+ /**
859
+ * Slack Bridge for PocketPing.
860
+ * Sends visitor messages to a Slack channel using webhooks or bot API.
861
+ *
862
+ * @example Webhook mode
863
+ * ```ts
864
+ * const slack = SlackBridge.webhook(
865
+ * 'https://hooks.slack.com/services/T.../B.../xxx',
866
+ * { username: 'PocketPing', iconEmoji: ':speech_balloon:' }
867
+ * );
868
+ * ```
869
+ *
870
+ * @example Bot mode
871
+ * ```ts
872
+ * const slack = SlackBridge.bot(
873
+ * 'xoxb-YOUR-BOT-TOKEN',
874
+ * 'C1234567890',
875
+ * { username: 'PocketPing' }
876
+ * );
877
+ * ```
878
+ */
879
+ declare class SlackBridge implements Bridge {
880
+ readonly name = "slack";
881
+ private readonly mode;
882
+ private readonly webhookUrl?;
883
+ private readonly botToken?;
884
+ private readonly channelId?;
885
+ private readonly username?;
886
+ private readonly iconEmoji?;
887
+ private readonly iconUrl?;
888
+ private constructor();
889
+ /**
890
+ * Create a Slack bridge using a webhook URL
891
+ */
892
+ static webhook(webhookUrl: string, options?: SlackWebhookOptions): SlackBridge;
893
+ /**
894
+ * Create a Slack bridge using a bot token
895
+ */
896
+ static bot(botToken: string, channelId: string, options?: SlackBotOptions): SlackBridge;
897
+ /**
898
+ * Initialize the bridge (optional setup)
899
+ */
900
+ init(_pocketping: PocketPing): Promise<void>;
901
+ /**
902
+ * Called when a new chat session is created
903
+ */
904
+ onNewSession(session: Session): Promise<void>;
905
+ /**
906
+ * Called when a visitor sends a message.
907
+ * Returns the Slack message timestamp for edit/delete sync.
908
+ */
909
+ onVisitorMessage(message: Message, session: Session): Promise<BridgeMessageResult>;
910
+ /**
911
+ * Called when an operator sends a message (for cross-bridge sync)
912
+ */
913
+ onOperatorMessage(message: Message, _session: Session, sourceBridge?: string, operatorName?: string): Promise<void>;
914
+ /**
915
+ * Called when visitor starts/stops typing
916
+ */
917
+ onTyping(_sessionId: string, _isTyping: boolean): Promise<void>;
918
+ /**
919
+ * Called when a visitor edits their message.
920
+ * @returns true if edit succeeded, false otherwise
921
+ */
922
+ onMessageEdit(_messageId: string, newContent: string, bridgeMessageId: string | number): Promise<boolean>;
923
+ /**
924
+ * Called when a visitor deletes their message.
925
+ * @returns true if delete succeeded, false otherwise
926
+ */
927
+ onMessageDelete(_messageId: string, bridgeMessageId: string | number): Promise<boolean>;
928
+ /**
929
+ * Called when a custom event is triggered from the widget
930
+ */
931
+ onCustomEvent(event: {
932
+ name: string;
933
+ data?: Record<string, unknown>;
934
+ }, session: Session): Promise<void>;
935
+ /**
936
+ * Called when a user identifies themselves via PocketPing.identify()
937
+ */
938
+ onIdentityUpdate(session: Session): Promise<void>;
939
+ /**
940
+ * Send blocks to Slack
941
+ */
942
+ private sendBlocks;
943
+ /**
944
+ * Escape special characters for Slack mrkdwn
945
+ */
946
+ private escapeSlack;
947
+ }
948
+
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 };