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

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 (148) 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 +638 -0
  11. package/dist/assets/generated/wasm/gossip_wasm.js +1557 -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 +164 -0
  14. package/dist/assets/generated/wasm/package.json +15 -0
  15. package/dist/assets/generated/wasm-node/README.md +281 -0
  16. package/dist/assets/generated/wasm-node/gossip_wasm.d.ts +443 -0
  17. package/dist/assets/generated/wasm-node/gossip_wasm.js +1488 -0
  18. package/dist/assets/generated/wasm-node/gossip_wasm_bg.wasm +0 -0
  19. package/dist/assets/generated/wasm-node/gossip_wasm_bg.wasm.d.ts +164 -0
  20. package/dist/assets/generated/wasm-node/package.json +11 -0
  21. package/dist/config/protocol.d.ts +36 -0
  22. package/dist/config/protocol.js +77 -0
  23. package/dist/config/sdk.d.ts +82 -0
  24. package/dist/config/sdk.js +55 -0
  25. package/{src/contacts.ts → dist/contacts.d.ts} +11 -95
  26. package/dist/contacts.js +166 -0
  27. package/dist/core/SdkEventEmitter.d.ts +36 -0
  28. package/dist/core/SdkEventEmitter.js +59 -0
  29. package/dist/core/SdkPolling.d.ts +35 -0
  30. package/dist/core/SdkPolling.js +100 -0
  31. package/{src/core/index.ts → dist/core/index.d.ts} +0 -2
  32. package/dist/core/index.js +5 -0
  33. package/dist/crypto/bip39.d.ts +34 -0
  34. package/dist/crypto/bip39.js +62 -0
  35. package/dist/crypto/encryption.d.ts +37 -0
  36. package/dist/crypto/encryption.js +46 -0
  37. package/dist/db.d.ts +190 -0
  38. package/dist/db.js +311 -0
  39. package/dist/gossipSdk.d.ts +274 -0
  40. package/dist/gossipSdk.js +690 -0
  41. package/dist/index.d.ts +59 -0
  42. package/dist/index.js +61 -0
  43. package/dist/services/announcement.d.ts +43 -0
  44. package/dist/services/announcement.js +491 -0
  45. package/dist/services/auth.d.ts +37 -0
  46. package/dist/services/auth.js +76 -0
  47. package/dist/services/discussion.d.ts +63 -0
  48. package/dist/services/discussion.js +297 -0
  49. package/dist/services/message.d.ts +74 -0
  50. package/dist/services/message.js +826 -0
  51. package/dist/services/refresh.d.ts +41 -0
  52. package/dist/services/refresh.js +205 -0
  53. package/{src/sw.ts → dist/sw.d.ts} +1 -8
  54. package/dist/sw.js +10 -0
  55. package/dist/types/events.d.ts +80 -0
  56. package/dist/types/events.js +7 -0
  57. package/dist/types.d.ts +32 -0
  58. package/dist/types.js +7 -0
  59. package/dist/utils/base64.d.ts +10 -0
  60. package/dist/utils/base64.js +30 -0
  61. package/dist/utils/contacts.d.ts +42 -0
  62. package/dist/utils/contacts.js +113 -0
  63. package/dist/utils/discussions.d.ts +24 -0
  64. package/dist/utils/discussions.js +38 -0
  65. package/dist/utils/logs.d.ts +19 -0
  66. package/dist/utils/logs.js +89 -0
  67. package/dist/utils/messageSerialization.d.ts +64 -0
  68. package/dist/utils/messageSerialization.js +184 -0
  69. package/dist/utils/queue.d.ts +50 -0
  70. package/dist/utils/queue.js +110 -0
  71. package/dist/utils/type.d.ts +10 -0
  72. package/dist/utils/type.js +4 -0
  73. package/dist/utils/userId.d.ts +40 -0
  74. package/dist/utils/userId.js +90 -0
  75. package/dist/utils/validation.d.ts +50 -0
  76. package/dist/utils/validation.js +112 -0
  77. package/dist/utils.d.ts +30 -0
  78. package/{src/utils.ts → dist/utils.js} +9 -19
  79. package/dist/wasm/encryption.d.ts +56 -0
  80. package/{src/wasm/encryption.ts → dist/wasm/encryption.js} +22 -51
  81. package/dist/wasm/index.d.ts +10 -0
  82. package/{src/wasm/index.ts → dist/wasm/index.js} +1 -8
  83. package/dist/wasm/loader.d.ts +22 -0
  84. package/dist/wasm/loader.js +78 -0
  85. package/dist/wasm/session.d.ts +85 -0
  86. package/dist/wasm/session.js +226 -0
  87. package/dist/wasm/userKeys.d.ts +17 -0
  88. package/{src/wasm/userKeys.ts → dist/wasm/userKeys.js} +6 -13
  89. package/package.json +15 -2
  90. package/src/api/messageProtocol/index.ts +0 -53
  91. package/src/api/messageProtocol/rest.ts +0 -209
  92. package/src/api/messageProtocol/types.ts +0 -70
  93. package/src/config/protocol.ts +0 -97
  94. package/src/config/sdk.ts +0 -131
  95. package/src/core/SdkEventEmitter.ts +0 -91
  96. package/src/core/SdkPolling.ts +0 -134
  97. package/src/crypto/bip39.ts +0 -84
  98. package/src/crypto/encryption.ts +0 -77
  99. package/src/db.ts +0 -465
  100. package/src/gossipSdk.ts +0 -994
  101. package/src/index.ts +0 -211
  102. package/src/services/announcement.ts +0 -653
  103. package/src/services/auth.ts +0 -95
  104. package/src/services/discussion.ts +0 -380
  105. package/src/services/message.ts +0 -1055
  106. package/src/services/refresh.ts +0 -234
  107. package/src/types/events.ts +0 -108
  108. package/src/types.ts +0 -70
  109. package/src/utils/base64.ts +0 -39
  110. package/src/utils/contacts.ts +0 -161
  111. package/src/utils/discussions.ts +0 -55
  112. package/src/utils/logs.ts +0 -86
  113. package/src/utils/messageSerialization.ts +0 -257
  114. package/src/utils/queue.ts +0 -106
  115. package/src/utils/type.ts +0 -7
  116. package/src/utils/userId.ts +0 -114
  117. package/src/utils/validation.ts +0 -144
  118. package/src/wasm/loader.ts +0 -123
  119. package/src/wasm/session.ts +0 -276
  120. package/test/config/protocol.spec.ts +0 -31
  121. package/test/config/sdk.spec.ts +0 -163
  122. package/test/db/helpers.spec.ts +0 -142
  123. package/test/db/operations.spec.ts +0 -128
  124. package/test/db/states.spec.ts +0 -535
  125. package/test/integration/discussion-flow.spec.ts +0 -422
  126. package/test/integration/messaging-flow.spec.ts +0 -708
  127. package/test/integration/sdk-lifecycle.spec.ts +0 -325
  128. package/test/mocks/index.ts +0 -9
  129. package/test/mocks/mockMessageProtocol.ts +0 -100
  130. package/test/services/auth.spec.ts +0 -311
  131. package/test/services/discussion.spec.ts +0 -279
  132. package/test/services/message-deduplication.spec.ts +0 -299
  133. package/test/services/message-startup.spec.ts +0 -331
  134. package/test/services/message.spec.ts +0 -817
  135. package/test/services/refresh.spec.ts +0 -199
  136. package/test/services/session-status.spec.ts +0 -349
  137. package/test/session/wasm.spec.ts +0 -227
  138. package/test/setup.ts +0 -52
  139. package/test/utils/contacts.spec.ts +0 -156
  140. package/test/utils/discussions.spec.ts +0 -66
  141. package/test/utils/queue.spec.ts +0 -52
  142. package/test/utils/serialization.spec.ts +0 -120
  143. package/test/utils/userId.spec.ts +0 -120
  144. package/test/utils/validation.spec.ts +0 -223
  145. package/test/utils.ts +0 -212
  146. package/tsconfig.json +0 -26
  147. package/tsconfig.tsbuildinfo +0 -1
  148. package/vitest.config.ts +0 -28
@@ -1,95 +0,0 @@
1
- /**
2
- * Auth Service
3
- *
4
- * Handles storing and retrieving public keys by userId hash via the auth API.
5
- */
6
-
7
- import { UserPublicKeys } from '../assets/generated/wasm/gossip_wasm';
8
- import { decodeUserId } from '../utils/userId';
9
- import { encodeToBase64, decodeFromBase64 } from '../utils/base64';
10
- import { IMessageProtocol } from '../api/messageProtocol/types';
11
- import { type GossipDatabase } from '../db';
12
-
13
- export type PublicKeyResult =
14
- | { publicKey: UserPublicKeys; error?: never }
15
- | { publicKey?: never; error: string };
16
-
17
- export class AuthService {
18
- constructor(
19
- private db: GossipDatabase,
20
- public messageProtocol: IMessageProtocol
21
- ) {}
22
-
23
- /**
24
- * Fetch public key by userId
25
- * @param userId - Bech32-encoded userId (e.g., "gossip1...")
26
- */
27
- async fetchPublicKeyByUserId(userId: string): Promise<PublicKeyResult> {
28
- try {
29
- const base64PublicKey = await this.messageProtocol.fetchPublicKeyByUserId(
30
- decodeUserId(userId)
31
- );
32
-
33
- return {
34
- publicKey: UserPublicKeys.from_bytes(decodeFromBase64(base64PublicKey)),
35
- };
36
- } catch (err) {
37
- return {
38
- error: getPublicKeyErrorMessage(err),
39
- };
40
- }
41
- }
42
-
43
- /**
44
- * Ensure public key is published (check first, then publish if needed)
45
- * @param publicKeys - UserPublicKeys instance
46
- * @param userId - Bech32-encoded userId (e.g., "gossip1...")
47
- */
48
- async ensurePublicKeyPublished(
49
- publicKeys: UserPublicKeys,
50
- userId: string
51
- ): Promise<void> {
52
- const profile = await this.db.userProfile.get(userId);
53
- if (!profile) throw new Error('User profile not found');
54
-
55
- const lastPush = profile.lastPublicKeyPush;
56
-
57
- if (lastPush && !moreThanOneWeekAgo(lastPush)) {
58
- return;
59
- }
60
-
61
- await this.messageProtocol.postPublicKey(
62
- encodeToBase64(publicKeys.to_bytes())
63
- );
64
-
65
- await this.db.userProfile.update(userId, { lastPublicKeyPush: new Date() });
66
- }
67
- }
68
-
69
- const ONE_WEEK_IN_MILLIS = 7 * 24 * 60 * 60 * 1000;
70
-
71
- function moreThanOneWeekAgo(date: Date): boolean {
72
- return Date.now() - date.getTime() >= ONE_WEEK_IN_MILLIS;
73
- }
74
-
75
- export const PUBLIC_KEY_NOT_FOUND_ERROR = 'Public key not found';
76
- export const PUBLIC_KEY_NOT_FOUND_MESSAGE =
77
- 'Contact public key not found. It may not be published yet.';
78
- export const FAILED_TO_FETCH_ERROR = 'Failed to fetch';
79
- export const FAILED_TO_FETCH_MESSAGE =
80
- 'Failed to retrieve contact public key. Check your internet connection or try again later.';
81
- export const FAILED_TO_RETRIEVE_CONTACT_PUBLIC_KEY_ERROR =
82
- 'Failed to retrieve contact public key';
83
-
84
- export function getPublicKeyErrorMessage(error: unknown): string {
85
- const errorMessage = error instanceof Error ? error.message : String(error);
86
- if (errorMessage.includes(PUBLIC_KEY_NOT_FOUND_ERROR)) {
87
- return PUBLIC_KEY_NOT_FOUND_MESSAGE;
88
- }
89
-
90
- if (errorMessage.includes(FAILED_TO_FETCH_ERROR)) {
91
- return FAILED_TO_FETCH_MESSAGE;
92
- }
93
-
94
- return `${FAILED_TO_RETRIEVE_CONTACT_PUBLIC_KEY_ERROR}. ${errorMessage}`;
95
- }
@@ -1,380 +0,0 @@
1
- /**
2
- * Discussion Service
3
- *
4
- * Class-based service for initializing, accepting, and managing discussions.
5
- */
6
-
7
- import {
8
- DiscussionStatus,
9
- type Discussion,
10
- type Contact,
11
- type GossipDatabase,
12
- MessageDirection,
13
- MessageStatus,
14
- DiscussionDirection,
15
- } from '../db';
16
- import {
17
- UserPublicKeys,
18
- SessionStatus,
19
- } from '../assets/generated/wasm/gossip_wasm';
20
- import { AnnouncementService, EstablishSessionError } from './announcement';
21
- import { SessionModule, sessionStatusToString } from '../wasm/session';
22
- import { decodeUserId } from '../utils/userId';
23
- import { Logger } from '../utils/logs';
24
- import { GossipSdkEvents } from '../types/events';
25
-
26
- const logger = new Logger('DiscussionService');
27
-
28
- /**
29
- * Service for managing discussions between users.
30
- *
31
- * @example
32
- * ```typescript
33
- * const discussionService = new DiscussionService(db, announcementService, session);
34
- *
35
- * // Initialize a new discussion
36
- * const result = await discussionService.initialize(contact, 'Hello!');
37
- *
38
- * // Accept a discussion request
39
- * await discussionService.accept(discussion);
40
- *
41
- * // Renew a broken discussion
42
- * await discussionService.renew(contactUserId);
43
- * ```
44
- */
45
- export class DiscussionService {
46
- private db: GossipDatabase;
47
- private announcementService: AnnouncementService;
48
- private session: SessionModule;
49
- private events: GossipSdkEvents;
50
-
51
- constructor(
52
- db: GossipDatabase,
53
- announcementService: AnnouncementService,
54
- session: SessionModule,
55
- events: GossipSdkEvents = {}
56
- ) {
57
- this.db = db;
58
- this.announcementService = announcementService;
59
- this.session = session;
60
- this.events = events;
61
- }
62
-
63
- /**
64
- * Initialize a discussion with a contact using SessionManager
65
- * @param contact - The contact to start a discussion with
66
- * @param message - Optional message to include in the announcement
67
- * @returns The discussion ID and the created announcement
68
- */
69
- async initialize(
70
- contact: Contact,
71
- message?: string
72
- ): Promise<{
73
- discussionId: number;
74
- announcement: Uint8Array;
75
- }> {
76
- const log = logger.forMethod('initialize');
77
- try {
78
- const userId = this.session.userIdEncoded;
79
- // Encode message as UTF-8 if provided
80
- const userData = message
81
- ? new TextEncoder().encode(message)
82
- : new Uint8Array(0);
83
-
84
- log.info(
85
- `${userId} is establishing session with contact ${contact.name}`
86
- );
87
- const result = await this.announcementService.establishSession(
88
- UserPublicKeys.from_bytes(contact.publicKeys),
89
- userData
90
- );
91
-
92
- let status: DiscussionStatus = DiscussionStatus.PENDING;
93
- if (!result.success) {
94
- log.error(
95
- `Failed to establish session with contact ${contact.name}, got error: ${result.error}`
96
- );
97
- // if the error is due to the session manager failed to establish outgoing session, throw the error
98
- if (result.error && result.error.includes(EstablishSessionError))
99
- throw new Error(EstablishSessionError);
100
-
101
- status = DiscussionStatus.SEND_FAILED;
102
- } else {
103
- log.info(
104
- `session established with contact and announcement sent: ${result.announcement.length}... bytes`
105
- );
106
- }
107
-
108
- // Parse announcement message to extract only the actual message content.
109
- // The message parameter may be JSON format: {"u":"username","m":"message"}
110
- // We only want to store the "m" (message) field, not the full JSON.
111
- let parsedAnnouncementMessage: string | undefined;
112
- if (message) {
113
- if (message.startsWith('{')) {
114
- try {
115
- const parsed = JSON.parse(message) as { u?: string; m?: string };
116
- parsedAnnouncementMessage = parsed.m?.trim() || undefined;
117
- } catch {
118
- // Invalid JSON, treat as plain text
119
- parsedAnnouncementMessage = message;
120
- }
121
- } else {
122
- parsedAnnouncementMessage = message;
123
- }
124
- }
125
-
126
- // Persist discussion immediately with the announcement for reliable retry
127
- const discussionId = await this.db.discussions.add({
128
- ownerUserId: userId,
129
- contactUserId: contact.userId,
130
- direction: DiscussionDirection.INITIATED,
131
- status: status,
132
- nextSeeker: undefined,
133
- initiationAnnouncement: result.announcement,
134
- announcementMessage: parsedAnnouncementMessage,
135
- unreadCount: 0,
136
- createdAt: new Date(),
137
- updatedAt: new Date(),
138
- });
139
-
140
- log.info(`discussion created with id: ${discussionId}`);
141
-
142
- // Emit status change event
143
- const discussion = await this.db.discussions.get(discussionId);
144
- if (discussion) {
145
- this.events.onDiscussionStatusChanged?.(discussion);
146
- }
147
-
148
- return { discussionId, announcement: result.announcement };
149
- } catch (error) {
150
- log.error(`Failed to initialize discussion, error: ${error}`);
151
- throw new Error('Discussion initialization failed, error: ' + error);
152
- }
153
- }
154
-
155
- /**
156
- * Accept a discussion request from a contact using SessionManager
157
- * @param discussion - The discussion to accept
158
- */
159
- async accept(discussion: Discussion): Promise<void> {
160
- const log = logger.forMethod('accept');
161
- try {
162
- const contact = await this.db.getContactByOwnerAndUserId(
163
- discussion.ownerUserId,
164
- discussion.contactUserId
165
- );
166
- if (!contact)
167
- throw new Error(
168
- `Contact ${discussion.contactUserId} not found for ownerUserId ${discussion.ownerUserId}`
169
- );
170
-
171
- const result = await this.announcementService.establishSession(
172
- UserPublicKeys.from_bytes(contact.publicKeys)
173
- );
174
-
175
- let status: DiscussionStatus = DiscussionStatus.ACTIVE;
176
- if (!result.success) {
177
- log.error(
178
- `Failed to establish session with contact ${contact.name}, got error: ${result.error}`
179
- );
180
-
181
- // if the error is due to the session manager failed to establish outgoing session, throw the error
182
- if (result.error && result.error.includes(EstablishSessionError))
183
- throw new Error(EstablishSessionError);
184
-
185
- status = DiscussionStatus.SEND_FAILED;
186
- } else {
187
- log.info(
188
- `session established with contact and announcement sent: ${result.announcement.length}... bytes`
189
- );
190
- }
191
-
192
- // update discussion status
193
- await this.db.discussions.update(discussion.id, {
194
- status: status,
195
- initiationAnnouncement: result.announcement,
196
- updatedAt: new Date(),
197
- });
198
- log.info(`discussion updated in db with status: ${status}`);
199
-
200
- // Emit status change event
201
- const updatedDiscussion = await this.db.discussions.get(discussion.id!);
202
- if (updatedDiscussion) {
203
- this.events.onDiscussionStatusChanged?.(updatedDiscussion);
204
- }
205
-
206
- return;
207
- } catch (error) {
208
- log.error(`Failed to accept pending discussion, error: ${error}`);
209
- throw new Error('Failed to accept pending discussion, error: ' + error);
210
- }
211
- }
212
-
213
- /**
214
- * Renew a discussion by resetting sent outgoing messages and sending a new announcement.
215
- * @param contactUserId - The user ID of the contact whose discussion should be renewed.
216
- */
217
- async renew(contactUserId: string): Promise<void> {
218
- const log = logger.forMethod('renew');
219
- const ownerUserId = this.session.userIdEncoded;
220
-
221
- const contact = await this.db.getContactByOwnerAndUserId(
222
- ownerUserId,
223
- contactUserId
224
- );
225
- if (!contact) throw new Error('Contact not found');
226
-
227
- const existingDiscussion = await this.db.getDiscussionByOwnerAndContact(
228
- ownerUserId,
229
- contactUserId
230
- );
231
-
232
- if (!existingDiscussion)
233
- throw new Error('Discussion with contact ' + contact.name + ' not found');
234
-
235
- log.info(`renewing discussion between ${ownerUserId} and ${contactUserId}`);
236
-
237
- // reset session by creating and sending a new announcement
238
- const result = await this.announcementService.establishSession(
239
- UserPublicKeys.from_bytes(contact.publicKeys)
240
- );
241
-
242
- // if the error is due to the session manager failed to establish outgoing session, throw the error
243
- if (result.error && result.error.includes(EstablishSessionError))
244
- throw new Error(EstablishSessionError);
245
-
246
- // get the new session status
247
- const sessionStatus = this.session.peerSessionStatus(
248
- decodeUserId(contactUserId)
249
- );
250
- log.info(
251
- `session status for discussion between ${ownerUserId} and ${contactUserId} after reinitiation is ${sessionStatusToString(sessionStatus)}`
252
- );
253
-
254
- // Determine discussion status based on send result and session state:
255
- // - SEND_FAILED: announcement couldn't be sent
256
- // - ACTIVE: session fully established (peer responded)
257
- // - RECONNECTING: true renewal, waiting for peer's response
258
- // - PENDING: first contact retry, waiting for peer's response
259
- let status: DiscussionStatus;
260
- if (!result.success) {
261
- status = DiscussionStatus.SEND_FAILED;
262
- } else if (sessionStatus === SessionStatus.Active) {
263
- // Session fully established (peer already responded)
264
- status = DiscussionStatus.ACTIVE;
265
- } else if (existingDiscussion.status === DiscussionStatus.ACTIVE) {
266
- // True renewal: had working session before, now recovering
267
- status = DiscussionStatus.RECONNECTING;
268
- } else {
269
- // First contact retry: never had working session
270
- status = DiscussionStatus.PENDING;
271
- }
272
-
273
- await this.db.transaction(
274
- 'rw',
275
- [this.db.discussions, this.db.messages],
276
- async () => {
277
- await this.db.discussions.update(existingDiscussion.id, {
278
- status: status,
279
- direction: DiscussionDirection.INITIATED,
280
- initiationAnnouncement: result.announcement,
281
- updatedAt: new Date(),
282
- });
283
-
284
- log.info(`discussion updated with status: ${status}`);
285
-
286
- /* Reset outgoing messages that haven't been acknowledged by the peer.
287
- * When session is renewed, messages encrypted with the old session
288
- * may not be decryptable by the peer with the new session.
289
- *
290
- * Messages to reset (not acknowledged):
291
- * - SENDING: Was in progress, needs re-encryption with new session
292
- * - FAILED: Previous send failed, needs re-encryption
293
- * - SENT: On network but not acknowledged - peer may not have received
294
- *
295
- * Messages to keep (acknowledged by peer):
296
- * - DELIVERED: Peer confirmed receipt
297
- * - READ: Peer read it
298
- */
299
- const messagesToReset = await this.db.messages
300
- .where('[ownerUserId+contactUserId]')
301
- .equals([ownerUserId, contactUserId])
302
- .and(
303
- message =>
304
- message.direction === MessageDirection.OUTGOING &&
305
- (message.status === MessageStatus.SENDING ||
306
- message.status === MessageStatus.FAILED ||
307
- message.status === MessageStatus.SENT)
308
- )
309
- .modify({
310
- status: MessageStatus.WAITING_SESSION,
311
- encryptedMessage: undefined,
312
- seeker: undefined,
313
- });
314
- log.info(`reset ${messagesToReset} messages to WAITING_SESSION`);
315
- }
316
- );
317
-
318
- // Emit events after transaction completes
319
- const updatedDiscussion = await this.db.discussions.get(
320
- existingDiscussion.id!
321
- );
322
- if (updatedDiscussion) {
323
- this.events.onDiscussionStatusChanged?.(updatedDiscussion);
324
- this.events.onSessionRenewed?.(updatedDiscussion);
325
- }
326
- }
327
-
328
- /**
329
- * Check if new messages can be sent to session manager for encryption.
330
- * Returns false if the discussion is broken or if there are failed messages
331
- * that have not been encrypted.
332
- *
333
- * @param ownerUserId - The owner user ID
334
- * @param contactUserId - The contact user ID
335
- * @returns true if discussion is in stable state for sending messages
336
- */
337
- async isStableState(
338
- ownerUserId: string,
339
- contactUserId: string
340
- ): Promise<boolean> {
341
- const log = logger.forMethod('isStableState');
342
- const discussion: Discussion | undefined =
343
- await this.db.getDiscussionByOwnerAndContact(ownerUserId, contactUserId);
344
-
345
- if (!discussion) throw new Error('Discussion not found');
346
-
347
- if (discussion.status === DiscussionStatus.BROKEN) {
348
- log.info(
349
- `Discussion with ownerUserId ${ownerUserId} and contactUserId ${contactUserId} is broken`
350
- );
351
- return false;
352
- }
353
-
354
- const messages = await this.db.messages
355
- .where('[ownerUserId+contactUserId+direction]')
356
- .equals([
357
- discussion.ownerUserId,
358
- discussion.contactUserId,
359
- MessageDirection.OUTGOING,
360
- ])
361
- .sortBy('id');
362
-
363
- /* If the discussion has been broken, all non delivered messages have been marked as failed and
364
- their encryptedMessage field has been deleted.
365
- If there are some unencrypted unsent messages in the conversation, the discussion is not stable
366
- i.e. we should not encrypt any new message via session manager before these messages are not resent */
367
- if (
368
- messages.length > 0 &&
369
- !messages[messages.length - 1].encryptedMessage &&
370
- messages[messages.length - 1].status === MessageStatus.FAILED
371
- ) {
372
- log.info(
373
- `Discussion with ownerUserId ${ownerUserId} and contactUserId ${contactUserId} has no encryptedMessage failed messages`
374
- );
375
- return false;
376
- }
377
-
378
- return true;
379
- }
380
- }