@massalabs/gossip-sdk 0.0.2-dev.20260128094509 → 0.0.2-dev.20260128111120

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.
Files changed (142) hide show
  1. package/dist/api/messageProtocol/index.d.ts +19 -0
  2. package/dist/api/messageProtocol/index.js +26 -0
  3. package/dist/api/messageProtocol/mock.d.ts +12 -0
  4. package/{src/api/messageProtocol/mock.ts → dist/api/messageProtocol/mock.js} +2 -3
  5. package/dist/api/messageProtocol/rest.d.ts +22 -0
  6. package/dist/api/messageProtocol/rest.js +161 -0
  7. package/dist/api/messageProtocol/types.d.ts +61 -0
  8. package/dist/api/messageProtocol/types.js +6 -0
  9. package/dist/assets/generated/wasm/README.md +281 -0
  10. package/dist/assets/generated/wasm/gossip_wasm.d.ts +498 -0
  11. package/dist/assets/generated/wasm/gossip_wasm.js +1399 -0
  12. package/dist/assets/generated/wasm/gossip_wasm_bg.wasm +0 -0
  13. package/dist/assets/generated/wasm/gossip_wasm_bg.wasm.d.ts +68 -0
  14. package/dist/assets/generated/wasm/package.json +15 -0
  15. package/dist/config/protocol.d.ts +36 -0
  16. package/dist/config/protocol.js +77 -0
  17. package/dist/config/sdk.d.ts +82 -0
  18. package/dist/config/sdk.js +55 -0
  19. package/{src/contacts.ts → dist/contacts.d.ts} +10 -94
  20. package/dist/contacts.js +166 -0
  21. package/dist/core/SdkEventEmitter.d.ts +36 -0
  22. package/dist/core/SdkEventEmitter.js +59 -0
  23. package/dist/core/SdkPolling.d.ts +35 -0
  24. package/dist/core/SdkPolling.js +100 -0
  25. package/{src/core/index.ts → dist/core/index.d.ts} +0 -2
  26. package/dist/core/index.js +5 -0
  27. package/dist/crypto/bip39.d.ts +34 -0
  28. package/dist/crypto/bip39.js +62 -0
  29. package/dist/crypto/encryption.d.ts +37 -0
  30. package/dist/crypto/encryption.js +46 -0
  31. package/dist/db.d.ts +190 -0
  32. package/dist/db.js +311 -0
  33. package/dist/gossipSdk.d.ts +274 -0
  34. package/dist/gossipSdk.js +690 -0
  35. package/dist/index.d.ts +73 -0
  36. package/dist/index.js +77 -0
  37. package/dist/services/announcement.d.ts +43 -0
  38. package/dist/services/announcement.js +491 -0
  39. package/dist/services/auth.d.ts +37 -0
  40. package/dist/services/auth.js +76 -0
  41. package/dist/services/discussion.d.ts +63 -0
  42. package/dist/services/discussion.js +297 -0
  43. package/dist/services/message.d.ts +74 -0
  44. package/dist/services/message.js +826 -0
  45. package/dist/services/refresh.d.ts +41 -0
  46. package/dist/services/refresh.js +205 -0
  47. package/{src/sw.ts → dist/sw.d.ts} +1 -8
  48. package/dist/sw.js +10 -0
  49. package/dist/types/events.d.ts +80 -0
  50. package/dist/types/events.js +7 -0
  51. package/dist/types.d.ts +32 -0
  52. package/dist/types.js +7 -0
  53. package/dist/utils/base64.d.ts +10 -0
  54. package/dist/utils/base64.js +30 -0
  55. package/dist/utils/contacts.d.ts +42 -0
  56. package/dist/utils/contacts.js +113 -0
  57. package/dist/utils/discussions.d.ts +24 -0
  58. package/dist/utils/discussions.js +38 -0
  59. package/dist/utils/logs.d.ts +19 -0
  60. package/dist/utils/logs.js +89 -0
  61. package/dist/utils/messageSerialization.d.ts +64 -0
  62. package/dist/utils/messageSerialization.js +184 -0
  63. package/dist/utils/queue.d.ts +50 -0
  64. package/dist/utils/queue.js +110 -0
  65. package/dist/utils/type.d.ts +10 -0
  66. package/dist/utils/type.js +4 -0
  67. package/dist/utils/userId.d.ts +40 -0
  68. package/dist/utils/userId.js +90 -0
  69. package/dist/utils/validation.d.ts +50 -0
  70. package/dist/utils/validation.js +112 -0
  71. package/dist/utils.d.ts +30 -0
  72. package/{src/utils.ts → dist/utils.js} +9 -19
  73. package/dist/wasm/encryption.d.ts +56 -0
  74. package/{src/wasm/encryption.ts → dist/wasm/encryption.js} +22 -51
  75. package/dist/wasm/index.d.ts +10 -0
  76. package/{src/wasm/index.ts → dist/wasm/index.js} +1 -8
  77. package/dist/wasm/loader.d.ts +21 -0
  78. package/dist/wasm/loader.js +103 -0
  79. package/dist/wasm/session.d.ts +85 -0
  80. package/dist/wasm/session.js +226 -0
  81. package/dist/wasm/userKeys.d.ts +17 -0
  82. package/{src/wasm/userKeys.ts → dist/wasm/userKeys.js} +6 -13
  83. package/package.json +5 -1
  84. package/src/api/messageProtocol/index.ts +0 -53
  85. package/src/api/messageProtocol/rest.ts +0 -209
  86. package/src/api/messageProtocol/types.ts +0 -70
  87. package/src/config/protocol.ts +0 -97
  88. package/src/config/sdk.ts +0 -131
  89. package/src/core/SdkEventEmitter.ts +0 -91
  90. package/src/core/SdkPolling.ts +0 -134
  91. package/src/crypto/bip39.ts +0 -84
  92. package/src/crypto/encryption.ts +0 -77
  93. package/src/db.ts +0 -465
  94. package/src/gossipSdk.ts +0 -994
  95. package/src/index.ts +0 -211
  96. package/src/services/announcement.ts +0 -653
  97. package/src/services/auth.ts +0 -95
  98. package/src/services/discussion.ts +0 -380
  99. package/src/services/message.ts +0 -1055
  100. package/src/services/refresh.ts +0 -234
  101. package/src/types/events.ts +0 -108
  102. package/src/types.ts +0 -70
  103. package/src/utils/base64.ts +0 -39
  104. package/src/utils/contacts.ts +0 -161
  105. package/src/utils/discussions.ts +0 -55
  106. package/src/utils/logs.ts +0 -86
  107. package/src/utils/messageSerialization.ts +0 -257
  108. package/src/utils/queue.ts +0 -106
  109. package/src/utils/type.ts +0 -7
  110. package/src/utils/userId.ts +0 -114
  111. package/src/utils/validation.ts +0 -144
  112. package/src/wasm/loader.ts +0 -123
  113. package/src/wasm/session.ts +0 -276
  114. package/test/config/protocol.spec.ts +0 -31
  115. package/test/config/sdk.spec.ts +0 -163
  116. package/test/db/helpers.spec.ts +0 -142
  117. package/test/db/operations.spec.ts +0 -128
  118. package/test/db/states.spec.ts +0 -535
  119. package/test/integration/discussion-flow.spec.ts +0 -422
  120. package/test/integration/messaging-flow.spec.ts +0 -708
  121. package/test/integration/sdk-lifecycle.spec.ts +0 -325
  122. package/test/mocks/index.ts +0 -9
  123. package/test/mocks/mockMessageProtocol.ts +0 -100
  124. package/test/services/auth.spec.ts +0 -311
  125. package/test/services/discussion.spec.ts +0 -279
  126. package/test/services/message-deduplication.spec.ts +0 -299
  127. package/test/services/message-startup.spec.ts +0 -331
  128. package/test/services/message.spec.ts +0 -817
  129. package/test/services/refresh.spec.ts +0 -199
  130. package/test/services/session-status.spec.ts +0 -349
  131. package/test/session/wasm.spec.ts +0 -227
  132. package/test/setup.ts +0 -52
  133. package/test/utils/contacts.spec.ts +0 -156
  134. package/test/utils/discussions.spec.ts +0 -66
  135. package/test/utils/queue.spec.ts +0 -52
  136. package/test/utils/serialization.spec.ts +0 -120
  137. package/test/utils/userId.spec.ts +0 -120
  138. package/test/utils/validation.spec.ts +0 -223
  139. package/test/utils.ts +0 -212
  140. package/tsconfig.json +0 -26
  141. package/tsconfig.tsbuildinfo +0 -1
  142. package/vitest.config.ts +0 -28
@@ -0,0 +1,297 @@
1
+ /**
2
+ * Discussion Service
3
+ *
4
+ * Class-based service for initializing, accepting, and managing discussions.
5
+ */
6
+ import { DiscussionStatus, MessageDirection, MessageStatus, DiscussionDirection, } from '../db';
7
+ import { UserPublicKeys, SessionStatus, } from '../assets/generated/wasm/gossip_wasm';
8
+ import { EstablishSessionError } from './announcement';
9
+ import { sessionStatusToString } from '../wasm/session';
10
+ import { decodeUserId } from '../utils/userId';
11
+ import { Logger } from '../utils/logs';
12
+ const logger = new Logger('DiscussionService');
13
+ /**
14
+ * Service for managing discussions between users.
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * const discussionService = new DiscussionService(db, announcementService, session);
19
+ *
20
+ * // Initialize a new discussion
21
+ * const result = await discussionService.initialize(contact, 'Hello!');
22
+ *
23
+ * // Accept a discussion request
24
+ * await discussionService.accept(discussion);
25
+ *
26
+ * // Renew a broken discussion
27
+ * await discussionService.renew(contactUserId);
28
+ * ```
29
+ */
30
+ export class DiscussionService {
31
+ constructor(db, announcementService, session, events = {}) {
32
+ Object.defineProperty(this, "db", {
33
+ enumerable: true,
34
+ configurable: true,
35
+ writable: true,
36
+ value: void 0
37
+ });
38
+ Object.defineProperty(this, "announcementService", {
39
+ enumerable: true,
40
+ configurable: true,
41
+ writable: true,
42
+ value: void 0
43
+ });
44
+ Object.defineProperty(this, "session", {
45
+ enumerable: true,
46
+ configurable: true,
47
+ writable: true,
48
+ value: void 0
49
+ });
50
+ Object.defineProperty(this, "events", {
51
+ enumerable: true,
52
+ configurable: true,
53
+ writable: true,
54
+ value: void 0
55
+ });
56
+ this.db = db;
57
+ this.announcementService = announcementService;
58
+ this.session = session;
59
+ this.events = events;
60
+ }
61
+ /**
62
+ * Initialize a discussion with a contact using SessionManager
63
+ * @param contact - The contact to start a discussion with
64
+ * @param message - Optional message to include in the announcement
65
+ * @returns The discussion ID and the created announcement
66
+ */
67
+ async initialize(contact, message) {
68
+ const log = logger.forMethod('initialize');
69
+ try {
70
+ const userId = this.session.userIdEncoded;
71
+ // Encode message as UTF-8 if provided
72
+ const userData = message
73
+ ? new TextEncoder().encode(message)
74
+ : new Uint8Array(0);
75
+ log.info(`${userId} is establishing session with contact ${contact.name}`);
76
+ const result = await this.announcementService.establishSession(UserPublicKeys.from_bytes(contact.publicKeys), userData);
77
+ let status = DiscussionStatus.PENDING;
78
+ if (!result.success) {
79
+ log.error(`Failed to establish session with contact ${contact.name}, got error: ${result.error}`);
80
+ // if the error is due to the session manager failed to establish outgoing session, throw the error
81
+ if (result.error && result.error.includes(EstablishSessionError))
82
+ throw new Error(EstablishSessionError);
83
+ status = DiscussionStatus.SEND_FAILED;
84
+ }
85
+ else {
86
+ log.info(`session established with contact and announcement sent: ${result.announcement.length}... bytes`);
87
+ }
88
+ // Parse announcement message to extract only the actual message content.
89
+ // The message parameter may be JSON format: {"u":"username","m":"message"}
90
+ // We only want to store the "m" (message) field, not the full JSON.
91
+ let parsedAnnouncementMessage;
92
+ if (message) {
93
+ if (message.startsWith('{')) {
94
+ try {
95
+ const parsed = JSON.parse(message);
96
+ parsedAnnouncementMessage = parsed.m?.trim() || undefined;
97
+ }
98
+ catch {
99
+ // Invalid JSON, treat as plain text
100
+ parsedAnnouncementMessage = message;
101
+ }
102
+ }
103
+ else {
104
+ parsedAnnouncementMessage = message;
105
+ }
106
+ }
107
+ // Persist discussion immediately with the announcement for reliable retry
108
+ const discussionId = await this.db.discussions.add({
109
+ ownerUserId: userId,
110
+ contactUserId: contact.userId,
111
+ direction: DiscussionDirection.INITIATED,
112
+ status: status,
113
+ nextSeeker: undefined,
114
+ initiationAnnouncement: result.announcement,
115
+ announcementMessage: parsedAnnouncementMessage,
116
+ unreadCount: 0,
117
+ createdAt: new Date(),
118
+ updatedAt: new Date(),
119
+ });
120
+ log.info(`discussion created with id: ${discussionId}`);
121
+ // Emit status change event
122
+ const discussion = await this.db.discussions.get(discussionId);
123
+ if (discussion) {
124
+ this.events.onDiscussionStatusChanged?.(discussion);
125
+ }
126
+ return { discussionId, announcement: result.announcement };
127
+ }
128
+ catch (error) {
129
+ log.error(`Failed to initialize discussion, error: ${error}`);
130
+ throw new Error('Discussion initialization failed, error: ' + error);
131
+ }
132
+ }
133
+ /**
134
+ * Accept a discussion request from a contact using SessionManager
135
+ * @param discussion - The discussion to accept
136
+ */
137
+ async accept(discussion) {
138
+ const log = logger.forMethod('accept');
139
+ try {
140
+ const contact = await this.db.getContactByOwnerAndUserId(discussion.ownerUserId, discussion.contactUserId);
141
+ if (!contact)
142
+ throw new Error(`Contact ${discussion.contactUserId} not found for ownerUserId ${discussion.ownerUserId}`);
143
+ const result = await this.announcementService.establishSession(UserPublicKeys.from_bytes(contact.publicKeys));
144
+ let status = DiscussionStatus.ACTIVE;
145
+ if (!result.success) {
146
+ log.error(`Failed to establish session with contact ${contact.name}, got error: ${result.error}`);
147
+ // if the error is due to the session manager failed to establish outgoing session, throw the error
148
+ if (result.error && result.error.includes(EstablishSessionError))
149
+ throw new Error(EstablishSessionError);
150
+ status = DiscussionStatus.SEND_FAILED;
151
+ }
152
+ else {
153
+ log.info(`session established with contact and announcement sent: ${result.announcement.length}... bytes`);
154
+ }
155
+ // update discussion status
156
+ await this.db.discussions.update(discussion.id, {
157
+ status: status,
158
+ initiationAnnouncement: result.announcement,
159
+ updatedAt: new Date(),
160
+ });
161
+ log.info(`discussion updated in db with status: ${status}`);
162
+ // Emit status change event
163
+ const updatedDiscussion = await this.db.discussions.get(discussion.id);
164
+ if (updatedDiscussion) {
165
+ this.events.onDiscussionStatusChanged?.(updatedDiscussion);
166
+ }
167
+ return;
168
+ }
169
+ catch (error) {
170
+ log.error(`Failed to accept pending discussion, error: ${error}`);
171
+ throw new Error('Failed to accept pending discussion, error: ' + error);
172
+ }
173
+ }
174
+ /**
175
+ * Renew a discussion by resetting sent outgoing messages and sending a new announcement.
176
+ * @param contactUserId - The user ID of the contact whose discussion should be renewed.
177
+ */
178
+ async renew(contactUserId) {
179
+ const log = logger.forMethod('renew');
180
+ const ownerUserId = this.session.userIdEncoded;
181
+ const contact = await this.db.getContactByOwnerAndUserId(ownerUserId, contactUserId);
182
+ if (!contact)
183
+ throw new Error('Contact not found');
184
+ const existingDiscussion = await this.db.getDiscussionByOwnerAndContact(ownerUserId, contactUserId);
185
+ if (!existingDiscussion)
186
+ throw new Error('Discussion with contact ' + contact.name + ' not found');
187
+ log.info(`renewing discussion between ${ownerUserId} and ${contactUserId}`);
188
+ // reset session by creating and sending a new announcement
189
+ const result = await this.announcementService.establishSession(UserPublicKeys.from_bytes(contact.publicKeys));
190
+ // if the error is due to the session manager failed to establish outgoing session, throw the error
191
+ if (result.error && result.error.includes(EstablishSessionError))
192
+ throw new Error(EstablishSessionError);
193
+ // get the new session status
194
+ const sessionStatus = this.session.peerSessionStatus(decodeUserId(contactUserId));
195
+ log.info(`session status for discussion between ${ownerUserId} and ${contactUserId} after reinitiation is ${sessionStatusToString(sessionStatus)}`);
196
+ // Determine discussion status based on send result and session state:
197
+ // - SEND_FAILED: announcement couldn't be sent
198
+ // - ACTIVE: session fully established (peer responded)
199
+ // - RECONNECTING: true renewal, waiting for peer's response
200
+ // - PENDING: first contact retry, waiting for peer's response
201
+ let status;
202
+ if (!result.success) {
203
+ status = DiscussionStatus.SEND_FAILED;
204
+ }
205
+ else if (sessionStatus === SessionStatus.Active) {
206
+ // Session fully established (peer already responded)
207
+ status = DiscussionStatus.ACTIVE;
208
+ }
209
+ else if (existingDiscussion.status === DiscussionStatus.ACTIVE) {
210
+ // True renewal: had working session before, now recovering
211
+ status = DiscussionStatus.RECONNECTING;
212
+ }
213
+ else {
214
+ // First contact retry: never had working session
215
+ status = DiscussionStatus.PENDING;
216
+ }
217
+ await this.db.transaction('rw', [this.db.discussions, this.db.messages], async () => {
218
+ await this.db.discussions.update(existingDiscussion.id, {
219
+ status: status,
220
+ direction: DiscussionDirection.INITIATED,
221
+ initiationAnnouncement: result.announcement,
222
+ updatedAt: new Date(),
223
+ });
224
+ log.info(`discussion updated with status: ${status}`);
225
+ /* Reset outgoing messages that haven't been acknowledged by the peer.
226
+ * When session is renewed, messages encrypted with the old session
227
+ * may not be decryptable by the peer with the new session.
228
+ *
229
+ * Messages to reset (not acknowledged):
230
+ * - SENDING: Was in progress, needs re-encryption with new session
231
+ * - FAILED: Previous send failed, needs re-encryption
232
+ * - SENT: On network but not acknowledged - peer may not have received
233
+ *
234
+ * Messages to keep (acknowledged by peer):
235
+ * - DELIVERED: Peer confirmed receipt
236
+ * - READ: Peer read it
237
+ */
238
+ const messagesToReset = await this.db.messages
239
+ .where('[ownerUserId+contactUserId]')
240
+ .equals([ownerUserId, contactUserId])
241
+ .and(message => message.direction === MessageDirection.OUTGOING &&
242
+ (message.status === MessageStatus.SENDING ||
243
+ message.status === MessageStatus.FAILED ||
244
+ message.status === MessageStatus.SENT))
245
+ .modify({
246
+ status: MessageStatus.WAITING_SESSION,
247
+ encryptedMessage: undefined,
248
+ seeker: undefined,
249
+ });
250
+ log.info(`reset ${messagesToReset} messages to WAITING_SESSION`);
251
+ });
252
+ // Emit events after transaction completes
253
+ const updatedDiscussion = await this.db.discussions.get(existingDiscussion.id);
254
+ if (updatedDiscussion) {
255
+ this.events.onDiscussionStatusChanged?.(updatedDiscussion);
256
+ this.events.onSessionRenewed?.(updatedDiscussion);
257
+ }
258
+ }
259
+ /**
260
+ * Check if new messages can be sent to session manager for encryption.
261
+ * Returns false if the discussion is broken or if there are failed messages
262
+ * that have not been encrypted.
263
+ *
264
+ * @param ownerUserId - The owner user ID
265
+ * @param contactUserId - The contact user ID
266
+ * @returns true if discussion is in stable state for sending messages
267
+ */
268
+ async isStableState(ownerUserId, contactUserId) {
269
+ const log = logger.forMethod('isStableState');
270
+ const discussion = await this.db.getDiscussionByOwnerAndContact(ownerUserId, contactUserId);
271
+ if (!discussion)
272
+ throw new Error('Discussion not found');
273
+ if (discussion.status === DiscussionStatus.BROKEN) {
274
+ log.info(`Discussion with ownerUserId ${ownerUserId} and contactUserId ${contactUserId} is broken`);
275
+ return false;
276
+ }
277
+ const messages = await this.db.messages
278
+ .where('[ownerUserId+contactUserId+direction]')
279
+ .equals([
280
+ discussion.ownerUserId,
281
+ discussion.contactUserId,
282
+ MessageDirection.OUTGOING,
283
+ ])
284
+ .sortBy('id');
285
+ /* If the discussion has been broken, all non delivered messages have been marked as failed and
286
+ their encryptedMessage field has been deleted.
287
+ If there are some unencrypted unsent messages in the conversation, the discussion is not stable
288
+ i.e. we should not encrypt any new message via session manager before these messages are not resent */
289
+ if (messages.length > 0 &&
290
+ !messages[messages.length - 1].encryptedMessage &&
291
+ messages[messages.length - 1].status === MessageStatus.FAILED) {
292
+ log.info(`Discussion with ownerUserId ${ownerUserId} and contactUserId ${contactUserId} has no encryptedMessage failed messages`);
293
+ return false;
294
+ }
295
+ return true;
296
+ }
297
+ }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Message Reception Service
3
+ *
4
+ * Handles fetching encrypted messages from the protocol and decrypting them.
5
+ * This service works both in host app contexts and SDK/automation context.
6
+ */
7
+ import { type Message, type GossipDatabase } from '../db';
8
+ import { IMessageProtocol } from '../api/messageProtocol';
9
+ import { SessionModule } from '../wasm';
10
+ import { DiscussionService } from './discussion';
11
+ import { GossipSdkEvents } from '../types/events';
12
+ import { SdkConfig } from '../config/sdk';
13
+ export interface MessageResult {
14
+ success: boolean;
15
+ newMessagesCount: number;
16
+ error?: string;
17
+ }
18
+ export interface SendMessageResult {
19
+ success: boolean;
20
+ message?: Message;
21
+ error?: string;
22
+ }
23
+ export declare class MessageService {
24
+ private db;
25
+ private messageProtocol;
26
+ private session;
27
+ private discussionService;
28
+ private events;
29
+ private config;
30
+ constructor(db: GossipDatabase, messageProtocol: IMessageProtocol, session: SessionModule, discussionService: DiscussionService, events?: GossipSdkEvents, config?: SdkConfig);
31
+ fetchMessages(): Promise<MessageResult>;
32
+ private decryptMessages;
33
+ private storeDecryptedMessages;
34
+ findMessageBySeeker(seeker: Uint8Array, ownerUserId: string): Promise<Message | undefined>;
35
+ /**
36
+ * Check if a message is a duplicate based on content and timestamp.
37
+ *
38
+ * A message is considered duplicate if:
39
+ * - Same sender (contactUserId)
40
+ * - Same content
41
+ * - Incoming direction
42
+ * - Timestamp within deduplication window (default 30 seconds)
43
+ *
44
+ * This handles the edge case where:
45
+ * 1. Sender sends message successfully to network
46
+ * 2. Sender app crashes before updating DB status to SENT
47
+ * 3. On restart, message is reset to WAITING_SESSION and re-sent
48
+ * 4. Receiver gets the same message twice with different seekers
49
+ *
50
+ * @param ownerUserId - The owner's user ID
51
+ * @param contactUserId - The sender's user ID
52
+ * @param content - The message content
53
+ * @param timestamp - The message timestamp
54
+ * @returns true if a duplicate exists
55
+ */
56
+ private isDuplicateMessage;
57
+ private acknowledgeMessages;
58
+ sendMessage(message: Message): Promise<SendMessageResult>;
59
+ private serializeMessage;
60
+ resendMessages(messages: Map<string, Message[]>): Promise<void>;
61
+ /**
62
+ * Process messages that are waiting for an active session.
63
+ * Called when a session becomes Active to send queued messages.
64
+ * Per spec: when session becomes Active, encrypt and send WAITING_SESSION messages.
65
+ *
66
+ * @param contactUserId - The contact whose session became active
67
+ * @returns Number of messages successfully sent
68
+ */
69
+ processWaitingMessages(contactUserId: string): Promise<number>;
70
+ /**
71
+ * Get count of messages waiting for session with a specific contact.
72
+ */
73
+ getWaitingMessageCount(contactUserId: string): Promise<number>;
74
+ }