@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,708 +0,0 @@
1
- /**
2
- * Messaging e2e-style tests
3
- *
4
- * Uses real WASM SessionModule with real crypto.
5
- * MockMessageProtocol provides in-memory message storage (no network).
6
- */
7
-
8
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
9
- import { AnnouncementService } from '../../src/services/announcement';
10
- import { DiscussionService } from '../../src/services/discussion';
11
- import { MessageService } from '../../src/services/message';
12
- import {
13
- db,
14
- DiscussionStatus,
15
- DiscussionDirection,
16
- MessageStatus,
17
- MessageDirection,
18
- MessageType,
19
- } from '../../src/db';
20
- import { SessionStatus } from '../../src/assets/generated/wasm/gossip_wasm';
21
- import { MockMessageProtocol } from '../mocks';
22
- import {
23
- createTestSession,
24
- cleanupTestSession,
25
- TestSessionData,
26
- } from '../utils';
27
- import type { GossipSdkEvents } from '../../src/types/events';
28
-
29
- // ============================================================================
30
- // Session renewal resends unacknowledged messages
31
- // ============================================================================
32
-
33
- describe('Session renewal resends unacknowledged messages', () => {
34
- let alice: TestSessionData;
35
- let bob: TestSessionData;
36
- let mockProtocol: MockMessageProtocol;
37
- let events: GossipSdkEvents;
38
- let messageService: MessageService;
39
- let discussionService: DiscussionService;
40
- let announcementService: AnnouncementService;
41
-
42
- beforeEach(async () => {
43
- if (!db.isOpen()) {
44
- await db.open();
45
- }
46
- await Promise.all(db.tables.map(table => table.clear()));
47
-
48
- // Create real WASM sessions
49
- alice = await createTestSession(`alice-renew-${Date.now()}`);
50
- bob = await createTestSession(`bob-renew-${Date.now()}`);
51
-
52
- mockProtocol = new MockMessageProtocol();
53
- events = {};
54
-
55
- // Add Bob as Alice's contact
56
- await db.contacts.add({
57
- ownerUserId: alice.session.userIdEncoded,
58
- userId: bob.session.userIdEncoded,
59
- name: 'Bob',
60
- publicKeys: bob.session.ourPk.to_bytes(),
61
- isOnline: false,
62
- lastSeen: new Date(),
63
- createdAt: new Date(),
64
- });
65
-
66
- // Set up services
67
- announcementService = new AnnouncementService(
68
- db,
69
- mockProtocol,
70
- alice.session,
71
- events
72
- );
73
- discussionService = new DiscussionService(
74
- db,
75
- announcementService,
76
- alice.session
77
- );
78
- messageService = new MessageService(
79
- db,
80
- mockProtocol,
81
- alice.session,
82
- discussionService,
83
- events
84
- );
85
- });
86
-
87
- afterEach(() => {
88
- cleanupTestSession(alice);
89
- cleanupTestSession(bob);
90
- });
91
-
92
- /**
93
- * Helper to reset unacknowledged messages to WAITING_SESSION
94
- * (simulates what happens when session needs renewal)
95
- */
96
- async function simulateRenewReset(
97
- ownerUserId: string,
98
- contactUserId: string
99
- ): Promise<number> {
100
- return await db.messages
101
- .where('[ownerUserId+contactUserId]')
102
- .equals([ownerUserId, contactUserId])
103
- .and(
104
- message =>
105
- message.direction === MessageDirection.OUTGOING &&
106
- (message.status === MessageStatus.SENDING ||
107
- message.status === MessageStatus.FAILED ||
108
- message.status === MessageStatus.SENT)
109
- )
110
- .modify({
111
- status: MessageStatus.WAITING_SESSION,
112
- encryptedMessage: undefined,
113
- seeker: undefined,
114
- });
115
- }
116
-
117
- describe('Manual renewal flow', () => {
118
- it('should reset SENT messages to WAITING_SESSION and resend when session becomes active', async () => {
119
- // Establish a real session between Alice and Bob
120
- const aliceAnnouncement = await alice.session.establishOutgoingSession(
121
- bob.session.ourPk
122
- );
123
- await bob.session.feedIncomingAnnouncement(aliceAnnouncement);
124
- const bobAnnouncement = await bob.session.establishOutgoingSession(
125
- alice.session.ourPk
126
- );
127
- await alice.session.feedIncomingAnnouncement(bobAnnouncement);
128
-
129
- // Verify session is active
130
- expect(alice.session.peerSessionStatus(bob.session.userId)).toBe(
131
- SessionStatus.Active
132
- );
133
-
134
- // Create active discussion
135
- await db.discussions.add({
136
- ownerUserId: alice.session.userIdEncoded,
137
- contactUserId: bob.session.userIdEncoded,
138
- direction: DiscussionDirection.INITIATED,
139
- status: DiscussionStatus.ACTIVE,
140
- unreadCount: 0,
141
- createdAt: new Date(),
142
- updatedAt: new Date(),
143
- });
144
-
145
- // Add a SENT message (simulates message sent but not acknowledged)
146
- const sentMessageId = await db.messages.add({
147
- ownerUserId: alice.session.userIdEncoded,
148
- contactUserId: bob.session.userIdEncoded,
149
- content: 'Hello Bob! This was sent but not delivered.',
150
- type: MessageType.TEXT,
151
- direction: MessageDirection.OUTGOING,
152
- status: MessageStatus.SENT,
153
- timestamp: new Date(),
154
- seeker: new Uint8Array(32).fill(1),
155
- encryptedMessage: new Uint8Array(64).fill(2),
156
- });
157
-
158
- // Add a DELIVERED message (should not be reset)
159
- const deliveredMessageId = await db.messages.add({
160
- ownerUserId: alice.session.userIdEncoded,
161
- contactUserId: bob.session.userIdEncoded,
162
- content: 'This was delivered already.',
163
- type: MessageType.TEXT,
164
- direction: MessageDirection.OUTGOING,
165
- status: MessageStatus.DELIVERED,
166
- timestamp: new Date(),
167
- seeker: new Uint8Array(32).fill(3),
168
- encryptedMessage: new Uint8Array(64).fill(4),
169
- });
170
-
171
- // Verify initial state
172
- let sentMessage = await db.messages.get(sentMessageId);
173
- expect(sentMessage?.status).toBe(MessageStatus.SENT);
174
- expect(sentMessage?.seeker).toBeDefined();
175
-
176
- // Simulate renewal reset
177
- const resetCount = await simulateRenewReset(
178
- alice.session.userIdEncoded,
179
- bob.session.userIdEncoded
180
- );
181
- expect(resetCount).toBe(1);
182
-
183
- // Verify reset state
184
- sentMessage = await db.messages.get(sentMessageId);
185
- expect(sentMessage?.status).toBe(MessageStatus.WAITING_SESSION);
186
- expect(sentMessage?.seeker).toBeUndefined();
187
-
188
- // DELIVERED should be unchanged
189
- const deliveredMessage = await db.messages.get(deliveredMessageId);
190
- expect(deliveredMessage?.status).toBe(MessageStatus.DELIVERED);
191
- expect(deliveredMessage?.seeker).toBeDefined();
192
-
193
- // Process waiting messages (session is still active)
194
- const sentCount = await messageService.processWaitingMessages(
195
- bob.session.userIdEncoded
196
- );
197
-
198
- expect(sentCount).toBe(1);
199
- sentMessage = await db.messages.get(sentMessageId);
200
- expect(sentMessage?.status).toBe(MessageStatus.SENT);
201
- });
202
-
203
- it('should reset multiple unacknowledged messages (SENT, SENDING, FAILED) on renew', async () => {
204
- // Establish session
205
- const aliceAnnouncement = await alice.session.establishOutgoingSession(
206
- bob.session.ourPk
207
- );
208
- await bob.session.feedIncomingAnnouncement(aliceAnnouncement);
209
- const bobAnnouncement = await bob.session.establishOutgoingSession(
210
- alice.session.ourPk
211
- );
212
- await alice.session.feedIncomingAnnouncement(bobAnnouncement);
213
-
214
- await db.discussions.add({
215
- ownerUserId: alice.session.userIdEncoded,
216
- contactUserId: bob.session.userIdEncoded,
217
- direction: DiscussionDirection.INITIATED,
218
- status: DiscussionStatus.ACTIVE,
219
- unreadCount: 0,
220
- createdAt: new Date(),
221
- updatedAt: new Date(),
222
- });
223
-
224
- // Add messages with different statuses
225
- const sentId = await db.messages.add({
226
- ownerUserId: alice.session.userIdEncoded,
227
- contactUserId: bob.session.userIdEncoded,
228
- content: 'SENT message',
229
- type: MessageType.TEXT,
230
- direction: MessageDirection.OUTGOING,
231
- status: MessageStatus.SENT,
232
- timestamp: new Date(Date.now() - 3000),
233
- seeker: new Uint8Array(32).fill(1),
234
- encryptedMessage: new Uint8Array(64).fill(1),
235
- });
236
-
237
- const sendingId = await db.messages.add({
238
- ownerUserId: alice.session.userIdEncoded,
239
- contactUserId: bob.session.userIdEncoded,
240
- content: 'SENDING message (interrupted)',
241
- type: MessageType.TEXT,
242
- direction: MessageDirection.OUTGOING,
243
- status: MessageStatus.SENDING,
244
- timestamp: new Date(Date.now() - 2000),
245
- seeker: new Uint8Array(32).fill(2),
246
- encryptedMessage: new Uint8Array(64).fill(2),
247
- });
248
-
249
- const failedId = await db.messages.add({
250
- ownerUserId: alice.session.userIdEncoded,
251
- contactUserId: bob.session.userIdEncoded,
252
- content: 'FAILED message',
253
- type: MessageType.TEXT,
254
- direction: MessageDirection.OUTGOING,
255
- status: MessageStatus.FAILED,
256
- timestamp: new Date(Date.now() - 1000),
257
- seeker: new Uint8Array(32).fill(3),
258
- encryptedMessage: new Uint8Array(64).fill(3),
259
- });
260
-
261
- const deliveredId = await db.messages.add({
262
- ownerUserId: alice.session.userIdEncoded,
263
- contactUserId: bob.session.userIdEncoded,
264
- content: 'DELIVERED message',
265
- type: MessageType.TEXT,
266
- direction: MessageDirection.OUTGOING,
267
- status: MessageStatus.DELIVERED,
268
- timestamp: new Date(Date.now() - 5000),
269
- seeker: new Uint8Array(32).fill(4),
270
- encryptedMessage: new Uint8Array(64).fill(4),
271
- });
272
-
273
- // Simulate renewal
274
- const resetCount = await simulateRenewReset(
275
- alice.session.userIdEncoded,
276
- bob.session.userIdEncoded
277
- );
278
- expect(resetCount).toBe(3); // SENT, SENDING, FAILED
279
-
280
- // Verify reset
281
- expect((await db.messages.get(sentId))?.status).toBe(
282
- MessageStatus.WAITING_SESSION
283
- );
284
- expect((await db.messages.get(sendingId))?.status).toBe(
285
- MessageStatus.WAITING_SESSION
286
- );
287
- expect((await db.messages.get(failedId))?.status).toBe(
288
- MessageStatus.WAITING_SESSION
289
- );
290
- expect((await db.messages.get(deliveredId))?.status).toBe(
291
- MessageStatus.DELIVERED
292
- );
293
-
294
- // Process waiting messages
295
- const sentCount = await messageService.processWaitingMessages(
296
- bob.session.userIdEncoded
297
- );
298
- expect(sentCount).toBe(3);
299
- });
300
- });
301
-
302
- describe('Edge cases', () => {
303
- it('should preserve message order when resending multiple messages', async () => {
304
- // Establish session
305
- const aliceAnnouncement = await alice.session.establishOutgoingSession(
306
- bob.session.ourPk
307
- );
308
- await bob.session.feedIncomingAnnouncement(aliceAnnouncement);
309
- const bobAnnouncement = await bob.session.establishOutgoingSession(
310
- alice.session.ourPk
311
- );
312
- await alice.session.feedIncomingAnnouncement(bobAnnouncement);
313
-
314
- await db.discussions.add({
315
- ownerUserId: alice.session.userIdEncoded,
316
- contactUserId: bob.session.userIdEncoded,
317
- direction: DiscussionDirection.INITIATED,
318
- status: DiscussionStatus.ACTIVE,
319
- unreadCount: 0,
320
- createdAt: new Date(),
321
- updatedAt: new Date(),
322
- });
323
-
324
- // Add 3 messages in order
325
- const msg1Id = await db.messages.add({
326
- ownerUserId: alice.session.userIdEncoded,
327
- contactUserId: bob.session.userIdEncoded,
328
- content: 'Message 1',
329
- type: MessageType.TEXT,
330
- direction: MessageDirection.OUTGOING,
331
- status: MessageStatus.SENT,
332
- timestamp: new Date(Date.now() - 3000),
333
- seeker: new Uint8Array(32).fill(1),
334
- encryptedMessage: new Uint8Array(64).fill(1),
335
- });
336
-
337
- const msg2Id = await db.messages.add({
338
- ownerUserId: alice.session.userIdEncoded,
339
- contactUserId: bob.session.userIdEncoded,
340
- content: 'Message 2',
341
- type: MessageType.TEXT,
342
- direction: MessageDirection.OUTGOING,
343
- status: MessageStatus.SENT,
344
- timestamp: new Date(Date.now() - 2000),
345
- seeker: new Uint8Array(32).fill(2),
346
- encryptedMessage: new Uint8Array(64).fill(2),
347
- });
348
-
349
- const msg3Id = await db.messages.add({
350
- ownerUserId: alice.session.userIdEncoded,
351
- contactUserId: bob.session.userIdEncoded,
352
- content: 'Message 3',
353
- type: MessageType.TEXT,
354
- direction: MessageDirection.OUTGOING,
355
- status: MessageStatus.SENT,
356
- timestamp: new Date(Date.now() - 1000),
357
- seeker: new Uint8Array(32).fill(3),
358
- encryptedMessage: new Uint8Array(64).fill(3),
359
- });
360
-
361
- await simulateRenewReset(
362
- alice.session.userIdEncoded,
363
- bob.session.userIdEncoded
364
- );
365
-
366
- const sentCount = await messageService.processWaitingMessages(
367
- bob.session.userIdEncoded
368
- );
369
- expect(sentCount).toBe(3);
370
-
371
- // All should be SENT now
372
- expect((await db.messages.get(msg1Id))?.status).toBe(MessageStatus.SENT);
373
- expect((await db.messages.get(msg2Id))?.status).toBe(MessageStatus.SENT);
374
- expect((await db.messages.get(msg3Id))?.status).toBe(MessageStatus.SENT);
375
- });
376
-
377
- it('should not resend incoming messages on renewal', async () => {
378
- // Establish session
379
- const aliceAnnouncement = await alice.session.establishOutgoingSession(
380
- bob.session.ourPk
381
- );
382
- await bob.session.feedIncomingAnnouncement(aliceAnnouncement);
383
- const bobAnnouncement = await bob.session.establishOutgoingSession(
384
- alice.session.ourPk
385
- );
386
- await alice.session.feedIncomingAnnouncement(bobAnnouncement);
387
-
388
- await db.discussions.add({
389
- ownerUserId: alice.session.userIdEncoded,
390
- contactUserId: bob.session.userIdEncoded,
391
- direction: DiscussionDirection.INITIATED,
392
- status: DiscussionStatus.ACTIVE,
393
- unreadCount: 0,
394
- createdAt: new Date(),
395
- updatedAt: new Date(),
396
- });
397
-
398
- const outgoingId = await db.messages.add({
399
- ownerUserId: alice.session.userIdEncoded,
400
- contactUserId: bob.session.userIdEncoded,
401
- content: 'Outgoing message',
402
- type: MessageType.TEXT,
403
- direction: MessageDirection.OUTGOING,
404
- status: MessageStatus.SENT,
405
- timestamp: new Date(),
406
- seeker: new Uint8Array(32).fill(1),
407
- encryptedMessage: new Uint8Array(64).fill(1),
408
- });
409
-
410
- const incomingId = await db.messages.add({
411
- ownerUserId: alice.session.userIdEncoded,
412
- contactUserId: bob.session.userIdEncoded,
413
- content: 'Incoming message from Bob',
414
- type: MessageType.TEXT,
415
- direction: MessageDirection.INCOMING,
416
- status: MessageStatus.DELIVERED,
417
- timestamp: new Date(),
418
- seeker: new Uint8Array(32).fill(2),
419
- encryptedMessage: new Uint8Array(64).fill(2),
420
- });
421
-
422
- const resetCount = await simulateRenewReset(
423
- alice.session.userIdEncoded,
424
- bob.session.userIdEncoded
425
- );
426
- expect(resetCount).toBe(1); // Only outgoing
427
-
428
- expect((await db.messages.get(outgoingId))?.status).toBe(
429
- MessageStatus.WAITING_SESSION
430
- );
431
- expect((await db.messages.get(incomingId))?.status).toBe(
432
- MessageStatus.DELIVERED
433
- );
434
- });
435
- });
436
- });
437
-
438
- // ============================================================================
439
- // WAITING_SESSION after accept
440
- // ============================================================================
441
-
442
- describe('WAITING_SESSION messages after peer acceptance', () => {
443
- let alice: TestSessionData;
444
- let bob: TestSessionData;
445
- let mockProtocol: MockMessageProtocol;
446
- let events: GossipSdkEvents;
447
- let messageService: MessageService;
448
- let discussionService: DiscussionService;
449
- let announcementService: AnnouncementService;
450
-
451
- function createUserProfile(userId: string) {
452
- return {
453
- userId,
454
- username: 'test',
455
- security: {
456
- encKeySalt: new Uint8Array(),
457
- authMethod: 'password' as const,
458
- mnemonicBackup: {
459
- encryptedMnemonic: new Uint8Array(),
460
- createdAt: new Date(),
461
- backedUp: false,
462
- },
463
- },
464
- session: new Uint8Array(),
465
- status: 'online' as const,
466
- lastSeen: new Date(),
467
- createdAt: new Date(),
468
- updatedAt: new Date(),
469
- };
470
- }
471
-
472
- beforeEach(async () => {
473
- if (!db.isOpen()) {
474
- await db.open();
475
- }
476
- await Promise.all(db.tables.map(table => table.clear()));
477
-
478
- alice = await createTestSession(`alice-waiting-${Date.now()}`);
479
- bob = await createTestSession(`bob-waiting-${Date.now()}`);
480
-
481
- mockProtocol = new MockMessageProtocol();
482
- events = {};
483
-
484
- await db.contacts.add({
485
- ownerUserId: alice.session.userIdEncoded,
486
- userId: bob.session.userIdEncoded,
487
- name: 'Bob',
488
- publicKeys: bob.session.ourPk.to_bytes(),
489
- isOnline: false,
490
- lastSeen: new Date(),
491
- createdAt: new Date(),
492
- });
493
-
494
- await db.userProfile.put(createUserProfile(alice.session.userIdEncoded));
495
-
496
- announcementService = new AnnouncementService(
497
- db,
498
- mockProtocol,
499
- alice.session,
500
- events
501
- );
502
- discussionService = new DiscussionService(
503
- db,
504
- announcementService,
505
- alice.session
506
- );
507
- messageService = new MessageService(
508
- db,
509
- mockProtocol,
510
- alice.session,
511
- discussionService,
512
- events
513
- );
514
- });
515
-
516
- afterEach(() => {
517
- cleanupTestSession(alice);
518
- cleanupTestSession(bob);
519
- });
520
-
521
- it('messages queued before acceptance should send after session becomes Active', async () => {
522
- // Alice initiates session to Bob (SelfRequested state)
523
- await alice.session.establishOutgoingSession(bob.session.ourPk);
524
-
525
- expect(alice.session.peerSessionStatus(bob.session.userId)).toBe(
526
- SessionStatus.SelfRequested
527
- );
528
-
529
- // Create pending discussion
530
- await db.discussions.add({
531
- ownerUserId: alice.session.userIdEncoded,
532
- contactUserId: bob.session.userIdEncoded,
533
- direction: DiscussionDirection.INITIATED,
534
- status: DiscussionStatus.PENDING,
535
- unreadCount: 0,
536
- createdAt: new Date(),
537
- updatedAt: new Date(),
538
- });
539
-
540
- // Alice tries to send a message before Bob accepts
541
- const message = {
542
- ownerUserId: alice.session.userIdEncoded,
543
- contactUserId: bob.session.userIdEncoded,
544
- content: 'Hello Bob! (sent before you accepted)',
545
- type: MessageType.TEXT,
546
- direction: MessageDirection.OUTGOING,
547
- status: MessageStatus.SENDING,
548
- timestamp: new Date(),
549
- };
550
-
551
- const sendResult = await messageService.sendMessage(message);
552
-
553
- // Message should be queued as WAITING_SESSION
554
- expect(sendResult.success).toBe(true);
555
- expect(sendResult.message?.status).toBe(MessageStatus.WAITING_SESSION);
556
-
557
- const queuedMessageId = sendResult.message!.id!;
558
- let dbMessage = await db.messages.get(queuedMessageId);
559
- expect(dbMessage?.status).toBe(MessageStatus.WAITING_SESSION);
560
-
561
- // Now Bob accepts - establish the full handshake
562
- const aliceAnnouncement = await alice.session.establishOutgoingSession(
563
- bob.session.ourPk
564
- );
565
- await bob.session.feedIncomingAnnouncement(aliceAnnouncement);
566
- const bobAnnouncement = await bob.session.establishOutgoingSession(
567
- alice.session.ourPk
568
- );
569
- await alice.session.feedIncomingAnnouncement(bobAnnouncement);
570
-
571
- // Session should now be active
572
- expect(alice.session.peerSessionStatus(bob.session.userId)).toBe(
573
- SessionStatus.Active
574
- );
575
-
576
- // Update discussion to active
577
- const discussion = await db.getDiscussionByOwnerAndContact(
578
- alice.session.userIdEncoded,
579
- bob.session.userIdEncoded
580
- );
581
- await db.discussions.update(discussion!.id!, {
582
- status: DiscussionStatus.ACTIVE,
583
- updatedAt: new Date(),
584
- });
585
-
586
- // Process waiting messages
587
- const sentCount = await messageService.processWaitingMessages(
588
- bob.session.userIdEncoded
589
- );
590
-
591
- expect(sentCount).toBe(1);
592
- dbMessage = await db.messages.get(queuedMessageId);
593
- expect(dbMessage?.status).toBe(MessageStatus.SENT);
594
- });
595
-
596
- it('processWaitingMessages correctly sends messages when called manually', async () => {
597
- // Establish full session
598
- const aliceAnnouncement = await alice.session.establishOutgoingSession(
599
- bob.session.ourPk
600
- );
601
- await bob.session.feedIncomingAnnouncement(aliceAnnouncement);
602
- const bobAnnouncement = await bob.session.establishOutgoingSession(
603
- alice.session.ourPk
604
- );
605
- await alice.session.feedIncomingAnnouncement(bobAnnouncement);
606
-
607
- await db.discussions.add({
608
- ownerUserId: alice.session.userIdEncoded,
609
- contactUserId: bob.session.userIdEncoded,
610
- direction: DiscussionDirection.INITIATED,
611
- status: DiscussionStatus.ACTIVE,
612
- unreadCount: 0,
613
- createdAt: new Date(),
614
- updatedAt: new Date(),
615
- });
616
-
617
- // Add a WAITING_SESSION message directly
618
- const messageId = await db.messages.add({
619
- ownerUserId: alice.session.userIdEncoded,
620
- contactUserId: bob.session.userIdEncoded,
621
- content: 'Stuck message',
622
- type: MessageType.TEXT,
623
- direction: MessageDirection.OUTGOING,
624
- status: MessageStatus.WAITING_SESSION,
625
- timestamp: new Date(),
626
- });
627
-
628
- let dbMessage = await db.messages.get(messageId);
629
- expect(dbMessage?.status).toBe(MessageStatus.WAITING_SESSION);
630
-
631
- const sentCount = await messageService.processWaitingMessages(
632
- bob.session.userIdEncoded
633
- );
634
-
635
- expect(sentCount).toBe(1);
636
- dbMessage = await db.messages.get(messageId);
637
- expect(dbMessage?.status).toBe(MessageStatus.SENT);
638
- });
639
-
640
- it('full flow: Alice initiates, sends message, Bob accepts, message delivered', async () => {
641
- // Create pending discussion
642
- const aliceBobContact = {
643
- ownerUserId: alice.session.userIdEncoded,
644
- userId: bob.session.userIdEncoded,
645
- name: 'Bob',
646
- publicKeys: bob.session.ourPk.to_bytes(),
647
- isOnline: false,
648
- lastSeen: new Date(),
649
- createdAt: new Date(),
650
- };
651
-
652
- // Alice initiates discussion
653
- const { discussionId } =
654
- await discussionService.initialize(aliceBobContact);
655
-
656
- const discussion = await db.discussions.get(discussionId);
657
- expect(discussion?.status).toBe(DiscussionStatus.PENDING);
658
-
659
- // Alice sends a message (will be queued)
660
- const sendResult = await messageService.sendMessage({
661
- ownerUserId: alice.session.userIdEncoded,
662
- contactUserId: bob.session.userIdEncoded,
663
- content: 'Hello Bob!',
664
- type: MessageType.TEXT,
665
- direction: MessageDirection.OUTGOING,
666
- status: MessageStatus.SENDING,
667
- timestamp: new Date(),
668
- });
669
-
670
- expect(sendResult.success).toBe(true);
671
- expect(sendResult.message?.status).toBe(MessageStatus.WAITING_SESSION);
672
-
673
- // Bob receives Alice's announcement and accepts
674
- const storedAnnouncements = mockProtocol.getStoredAnnouncements();
675
- expect(storedAnnouncements.length).toBe(1);
676
-
677
- // Bob processes the announcement
678
- await bob.session.feedIncomingAnnouncement(storedAnnouncements[0].data);
679
-
680
- // Bob sends acceptance
681
- const bobAnnouncement = await bob.session.establishOutgoingSession(
682
- alice.session.ourPk
683
- );
684
-
685
- // Alice receives Bob's acceptance
686
- await alice.session.feedIncomingAnnouncement(bobAnnouncement);
687
-
688
- // Session should now be active
689
- expect(alice.session.peerSessionStatus(bob.session.userId)).toBe(
690
- SessionStatus.Active
691
- );
692
-
693
- // Update discussion status
694
- await db.discussions.update(discussionId, {
695
- status: DiscussionStatus.ACTIVE,
696
- });
697
-
698
- // Process waiting messages
699
- const sentCount = await messageService.processWaitingMessages(
700
- bob.session.userIdEncoded
701
- );
702
-
703
- expect(sentCount).toBe(1);
704
-
705
- const finalMessage = await db.messages.get(sendResult.message!.id!);
706
- expect(finalMessage?.status).toBe(MessageStatus.SENT);
707
- });
708
- });