@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,299 +0,0 @@
1
- /**
2
- * Message Deduplication tests
3
- */
4
-
5
- import { describe, it, expect, beforeEach } from 'vitest';
6
- import {
7
- GossipDatabase,
8
- MessageStatus,
9
- MessageDirection,
10
- MessageType,
11
- DiscussionStatus,
12
- DiscussionDirection,
13
- } from '../../src/db';
14
- import { encodeUserId } from '../../src/utils/userId';
15
- import { defaultSdkConfig, type SdkConfig } from '../../src/config/sdk';
16
-
17
- const DEDUP_OWNER_USER_ID = encodeUserId(new Uint8Array(32).fill(1));
18
- const DEDUP_CONTACT_USER_ID = encodeUserId(new Uint8Array(32).fill(2));
19
-
20
- describe('Message Deduplication', () => {
21
- let testDb: GossipDatabase;
22
-
23
- beforeEach(async () => {
24
- testDb = new GossipDatabase();
25
- if (!testDb.isOpen()) {
26
- await testDb.open();
27
- }
28
- await Promise.all(testDb.tables.map(table => table.clear()));
29
-
30
- await testDb.discussions.add({
31
- ownerUserId: DEDUP_OWNER_USER_ID,
32
- contactUserId: DEDUP_CONTACT_USER_ID,
33
- direction: DiscussionDirection.RECEIVED,
34
- status: DiscussionStatus.ACTIVE,
35
- unreadCount: 0,
36
- createdAt: new Date(),
37
- updatedAt: new Date(),
38
- });
39
- });
40
-
41
- describe('isDuplicateMessage (via storeDecryptedMessages)', () => {
42
- async function storeIncomingMessage(
43
- content: string,
44
- timestamp: Date,
45
- seeker: Uint8Array
46
- ): Promise<number | null> {
47
- const existing = await testDb.messages
48
- .where('[ownerUserId+contactUserId]')
49
- .equals([DEDUP_OWNER_USER_ID, DEDUP_CONTACT_USER_ID])
50
- .and(
51
- msg =>
52
- msg.direction === MessageDirection.INCOMING &&
53
- msg.content === content &&
54
- msg.timestamp >= new Date(timestamp.getTime() - 30000) &&
55
- msg.timestamp <= new Date(timestamp.getTime() + 30000)
56
- )
57
- .first();
58
-
59
- if (existing) {
60
- return null;
61
- }
62
-
63
- return await testDb.messages.add({
64
- ownerUserId: DEDUP_OWNER_USER_ID,
65
- contactUserId: DEDUP_CONTACT_USER_ID,
66
- content,
67
- type: MessageType.TEXT,
68
- direction: MessageDirection.INCOMING,
69
- status: MessageStatus.DELIVERED,
70
- timestamp,
71
- seeker,
72
- });
73
- }
74
-
75
- it('should store first message normally', async () => {
76
- const timestamp = new Date();
77
- const id = await storeIncomingMessage(
78
- 'Hello world',
79
- timestamp,
80
- new Uint8Array([1, 2, 3])
81
- );
82
-
83
- expect(id).not.toBeNull();
84
-
85
- const message = await testDb.messages.get(id!);
86
- expect(message?.content).toBe('Hello world');
87
- });
88
-
89
- it('should detect duplicate with same content and similar timestamp', async () => {
90
- const timestamp = new Date();
91
-
92
- const id1 = await storeIncomingMessage(
93
- 'Hello world',
94
- timestamp,
95
- new Uint8Array([1, 2, 3])
96
- );
97
- expect(id1).not.toBeNull();
98
-
99
- const timestamp2 = new Date(timestamp.getTime() + 5000);
100
- const id2 = await storeIncomingMessage(
101
- 'Hello world',
102
- timestamp2,
103
- new Uint8Array([4, 5, 6])
104
- );
105
-
106
- expect(id2).toBeNull();
107
- });
108
-
109
- it('should NOT detect duplicate if content differs', async () => {
110
- const timestamp = new Date();
111
-
112
- const id1 = await storeIncomingMessage(
113
- 'Hello world',
114
- timestamp,
115
- new Uint8Array([1, 2, 3])
116
- );
117
- expect(id1).not.toBeNull();
118
-
119
- const id2 = await storeIncomingMessage(
120
- 'Goodbye world',
121
- timestamp,
122
- new Uint8Array([4, 5, 6])
123
- );
124
-
125
- expect(id2).not.toBeNull();
126
- });
127
-
128
- it('should NOT detect duplicate if timestamp outside window', async () => {
129
- const timestamp = new Date();
130
-
131
- const id1 = await storeIncomingMessage(
132
- 'Hello world',
133
- timestamp,
134
- new Uint8Array([1, 2, 3])
135
- );
136
- expect(id1).not.toBeNull();
137
-
138
- const timestamp2 = new Date(timestamp.getTime() + 60000);
139
- const id2 = await storeIncomingMessage(
140
- 'Hello world',
141
- timestamp2,
142
- new Uint8Array([4, 5, 6])
143
- );
144
-
145
- expect(id2).not.toBeNull();
146
- });
147
-
148
- it('should NOT flag outgoing messages as duplicates of incoming', async () => {
149
- const timestamp = new Date();
150
-
151
- await testDb.messages.add({
152
- ownerUserId: DEDUP_OWNER_USER_ID,
153
- contactUserId: DEDUP_CONTACT_USER_ID,
154
- content: 'Hello world',
155
- type: MessageType.TEXT,
156
- direction: MessageDirection.INCOMING,
157
- status: MessageStatus.DELIVERED,
158
- timestamp,
159
- seeker: new Uint8Array([1, 2, 3]),
160
- });
161
-
162
- const outgoingId = await testDb.messages.add({
163
- ownerUserId: DEDUP_OWNER_USER_ID,
164
- contactUserId: DEDUP_CONTACT_USER_ID,
165
- content: 'Hello world',
166
- type: MessageType.TEXT,
167
- direction: MessageDirection.OUTGOING,
168
- status: MessageStatus.SENT,
169
- timestamp,
170
- seeker: new Uint8Array([4, 5, 6]),
171
- });
172
-
173
- expect(outgoingId).toBeDefined();
174
- });
175
- });
176
-
177
- describe('deduplication window configuration', () => {
178
- it('should respect custom deduplication window', async () => {
179
- const customConfig: SdkConfig = {
180
- ...defaultSdkConfig,
181
- messages: {
182
- ...defaultSdkConfig.messages,
183
- deduplicationWindowMs: 5000,
184
- },
185
- };
186
-
187
- const timestamp = new Date();
188
-
189
- await testDb.messages.add({
190
- ownerUserId: DEDUP_OWNER_USER_ID,
191
- contactUserId: DEDUP_CONTACT_USER_ID,
192
- content: 'Test message',
193
- type: MessageType.TEXT,
194
- direction: MessageDirection.INCOMING,
195
- status: MessageStatus.DELIVERED,
196
- timestamp,
197
- seeker: new Uint8Array([1, 2, 3]),
198
- });
199
-
200
- const timestamp2 = new Date(timestamp.getTime() + 10000);
201
- const windowMs = customConfig.messages.deduplicationWindowMs;
202
- const windowStart = new Date(timestamp2.getTime() - windowMs);
203
- const windowEnd = new Date(timestamp2.getTime() + windowMs);
204
-
205
- const duplicate = await testDb.messages
206
- .where('[ownerUserId+contactUserId]')
207
- .equals([DEDUP_OWNER_USER_ID, DEDUP_CONTACT_USER_ID])
208
- .and(
209
- msg =>
210
- msg.direction === MessageDirection.INCOMING &&
211
- msg.content === 'Test message' &&
212
- msg.timestamp >= windowStart &&
213
- msg.timestamp <= windowEnd
214
- )
215
- .first();
216
-
217
- expect(duplicate).toBeUndefined();
218
- });
219
- });
220
-
221
- describe('edge cases', () => {
222
- it('should handle empty content messages', async () => {
223
- const timestamp = new Date();
224
-
225
- const id1 = await testDb.messages.add({
226
- ownerUserId: DEDUP_OWNER_USER_ID,
227
- contactUserId: DEDUP_CONTACT_USER_ID,
228
- content: '',
229
- type: MessageType.KEEP_ALIVE,
230
- direction: MessageDirection.INCOMING,
231
- status: MessageStatus.DELIVERED,
232
- timestamp,
233
- seeker: new Uint8Array([1, 2, 3]),
234
- });
235
-
236
- const windowMs = 30000;
237
- const windowStart = new Date(timestamp.getTime() - windowMs);
238
- const windowEnd = new Date(timestamp.getTime() + windowMs);
239
-
240
- const duplicate = await testDb.messages
241
- .where('[ownerUserId+contactUserId]')
242
- .equals([DEDUP_OWNER_USER_ID, DEDUP_CONTACT_USER_ID])
243
- .and(
244
- msg =>
245
- msg.direction === MessageDirection.INCOMING &&
246
- msg.content === '' &&
247
- msg.timestamp >= windowStart &&
248
- msg.timestamp <= windowEnd
249
- )
250
- .first();
251
-
252
- expect(duplicate?.id).toBe(id1);
253
- });
254
-
255
- it('should handle messages from different contacts separately', async () => {
256
- const timestamp = new Date();
257
- const CONTACT_2_USER_ID = encodeUserId(new Uint8Array(32).fill(3));
258
-
259
- await testDb.discussions.add({
260
- ownerUserId: DEDUP_OWNER_USER_ID,
261
- contactUserId: CONTACT_2_USER_ID,
262
- direction: DiscussionDirection.RECEIVED,
263
- status: DiscussionStatus.ACTIVE,
264
- unreadCount: 0,
265
- createdAt: new Date(),
266
- updatedAt: new Date(),
267
- });
268
-
269
- await testDb.messages.add({
270
- ownerUserId: DEDUP_OWNER_USER_ID,
271
- contactUserId: DEDUP_CONTACT_USER_ID,
272
- content: 'Hello',
273
- type: MessageType.TEXT,
274
- direction: MessageDirection.INCOMING,
275
- status: MessageStatus.DELIVERED,
276
- timestamp,
277
- seeker: new Uint8Array([1, 2, 3]),
278
- });
279
-
280
- const windowMs = 30000;
281
- const windowStart = new Date(timestamp.getTime() - windowMs);
282
- const windowEnd = new Date(timestamp.getTime() + windowMs);
283
-
284
- const duplicateFromContact2 = await testDb.messages
285
- .where('[ownerUserId+contactUserId]')
286
- .equals([DEDUP_OWNER_USER_ID, CONTACT_2_USER_ID])
287
- .and(
288
- msg =>
289
- msg.direction === MessageDirection.INCOMING &&
290
- msg.content === 'Hello' &&
291
- msg.timestamp >= windowStart &&
292
- msg.timestamp <= windowEnd
293
- )
294
- .first();
295
-
296
- expect(duplicateFromContact2).toBeUndefined();
297
- });
298
- });
299
- });
@@ -1,331 +0,0 @@
1
- /**
2
- * Message startup behavior tests
3
- *
4
- * SENDING reset on startup, messages from unknown peers, seeker stabilization.
5
- */
6
-
7
- import { describe, it, expect, beforeEach } from 'vitest';
8
- import {
9
- GossipDatabase,
10
- MessageStatus,
11
- MessageDirection,
12
- MessageType,
13
- DiscussionStatus,
14
- DiscussionDirection,
15
- } from '../../src/db';
16
- import { encodeUserId } from '../../src/utils/userId';
17
- import { defaultSdkConfig } from '../../src/config/sdk';
18
-
19
- // ============================================================================
20
- // SENDING reset on startup
21
- // ============================================================================
22
-
23
- const RESET_OWNER_USER_ID = encodeUserId(new Uint8Array(32).fill(1));
24
- const RESET_CONTACT_USER_ID = encodeUserId(new Uint8Array(32).fill(2));
25
-
26
- describe('SENDING Reset on Startup', () => {
27
- let testDb: GossipDatabase;
28
-
29
- beforeEach(async () => {
30
- testDb = new GossipDatabase();
31
- if (!testDb.isOpen()) {
32
- await testDb.open();
33
- }
34
- await Promise.all(testDb.tables.map(table => table.clear()));
35
- });
36
-
37
- describe('resetStuckSendingMessages behavior', () => {
38
- async function resetStuckSendingMessages(): Promise<number> {
39
- return await testDb.messages
40
- .where('status')
41
- .equals(MessageStatus.SENDING)
42
- .modify({
43
- status: MessageStatus.WAITING_SESSION,
44
- encryptedMessage: undefined,
45
- seeker: undefined,
46
- });
47
- }
48
-
49
- it('should reset SENDING messages to WAITING_SESSION', async () => {
50
- const messageId = await testDb.messages.add({
51
- ownerUserId: RESET_OWNER_USER_ID,
52
- contactUserId: RESET_CONTACT_USER_ID,
53
- content: 'Test message',
54
- type: MessageType.TEXT,
55
- direction: MessageDirection.OUTGOING,
56
- status: MessageStatus.SENDING,
57
- timestamp: new Date(),
58
- encryptedMessage: new Uint8Array([1, 2, 3]),
59
- seeker: new Uint8Array([4, 5, 6]),
60
- });
61
-
62
- const count = await resetStuckSendingMessages();
63
-
64
- expect(count).toBe(1);
65
-
66
- const message = await testDb.messages.get(messageId);
67
- expect(message?.status).toBe(MessageStatus.WAITING_SESSION);
68
- expect(message?.encryptedMessage).toBeUndefined();
69
- expect(message?.seeker).toBeUndefined();
70
- });
71
-
72
- it('should clear encryptedMessage and seeker for re-encryption', async () => {
73
- const originalEncrypted = new Uint8Array([10, 20, 30, 40]);
74
- const originalSeeker = new Uint8Array([50, 60, 70, 80]);
75
-
76
- const messageId = await testDb.messages.add({
77
- ownerUserId: RESET_OWNER_USER_ID,
78
- contactUserId: RESET_CONTACT_USER_ID,
79
- content: 'Message with encryption data',
80
- type: MessageType.TEXT,
81
- direction: MessageDirection.OUTGOING,
82
- status: MessageStatus.SENDING,
83
- timestamp: new Date(),
84
- encryptedMessage: originalEncrypted,
85
- seeker: originalSeeker,
86
- });
87
-
88
- await resetStuckSendingMessages();
89
-
90
- const message = await testDb.messages.get(messageId);
91
-
92
- expect(message?.encryptedMessage).toBeUndefined();
93
- expect(message?.seeker).toBeUndefined();
94
- expect(message?.content).toBe('Message with encryption data');
95
- });
96
-
97
- it('should NOT affect messages in other statuses', async () => {
98
- const waitingId = await testDb.messages.add({
99
- ownerUserId: RESET_OWNER_USER_ID,
100
- contactUserId: RESET_CONTACT_USER_ID,
101
- content: 'Waiting',
102
- type: MessageType.TEXT,
103
- direction: MessageDirection.OUTGOING,
104
- status: MessageStatus.WAITING_SESSION,
105
- timestamp: new Date(),
106
- });
107
-
108
- const sentId = await testDb.messages.add({
109
- ownerUserId: RESET_OWNER_USER_ID,
110
- contactUserId: RESET_CONTACT_USER_ID,
111
- content: 'Sent',
112
- type: MessageType.TEXT,
113
- direction: MessageDirection.OUTGOING,
114
- status: MessageStatus.SENT,
115
- timestamp: new Date(),
116
- seeker: new Uint8Array([1, 2, 3]),
117
- });
118
-
119
- const deliveredId = await testDb.messages.add({
120
- ownerUserId: RESET_OWNER_USER_ID,
121
- contactUserId: RESET_CONTACT_USER_ID,
122
- content: 'Delivered',
123
- type: MessageType.TEXT,
124
- direction: MessageDirection.OUTGOING,
125
- status: MessageStatus.DELIVERED,
126
- timestamp: new Date(),
127
- seeker: new Uint8Array([4, 5, 6]),
128
- });
129
-
130
- const failedId = await testDb.messages.add({
131
- ownerUserId: RESET_OWNER_USER_ID,
132
- contactUserId: RESET_CONTACT_USER_ID,
133
- content: 'Failed',
134
- type: MessageType.TEXT,
135
- direction: MessageDirection.OUTGOING,
136
- status: MessageStatus.FAILED,
137
- timestamp: new Date(),
138
- });
139
-
140
- const count = await resetStuckSendingMessages();
141
-
142
- expect(count).toBe(0);
143
- expect((await testDb.messages.get(waitingId))?.status).toBe(
144
- MessageStatus.WAITING_SESSION
145
- );
146
- expect((await testDb.messages.get(sentId))?.status).toBe(
147
- MessageStatus.SENT
148
- );
149
- expect((await testDb.messages.get(deliveredId))?.status).toBe(
150
- MessageStatus.DELIVERED
151
- );
152
- expect((await testDb.messages.get(failedId))?.status).toBe(
153
- MessageStatus.FAILED
154
- );
155
- });
156
-
157
- it('should reset multiple SENDING messages', async () => {
158
- await testDb.messages.bulkAdd([
159
- {
160
- ownerUserId: RESET_OWNER_USER_ID,
161
- contactUserId: RESET_CONTACT_USER_ID,
162
- content: 'Message 1',
163
- type: MessageType.TEXT,
164
- direction: MessageDirection.OUTGOING,
165
- status: MessageStatus.SENDING,
166
- timestamp: new Date(),
167
- encryptedMessage: new Uint8Array([1]),
168
- seeker: new Uint8Array([1]),
169
- },
170
- {
171
- ownerUserId: RESET_OWNER_USER_ID,
172
- contactUserId: RESET_CONTACT_USER_ID,
173
- content: 'Message 2',
174
- type: MessageType.TEXT,
175
- direction: MessageDirection.OUTGOING,
176
- status: MessageStatus.SENDING,
177
- timestamp: new Date(),
178
- encryptedMessage: new Uint8Array([2]),
179
- seeker: new Uint8Array([2]),
180
- },
181
- {
182
- ownerUserId: RESET_OWNER_USER_ID,
183
- contactUserId: RESET_CONTACT_USER_ID,
184
- content: 'Message 3',
185
- type: MessageType.TEXT,
186
- direction: MessageDirection.OUTGOING,
187
- status: MessageStatus.SENDING,
188
- timestamp: new Date(),
189
- encryptedMessage: new Uint8Array([3]),
190
- seeker: new Uint8Array([3]),
191
- },
192
- ]);
193
-
194
- const count = await resetStuckSendingMessages();
195
-
196
- expect(count).toBe(3);
197
-
198
- const messages = await testDb.messages.toArray();
199
- expect(
200
- messages.every(m => m.status === MessageStatus.WAITING_SESSION)
201
- ).toBe(true);
202
- expect(messages.every(m => m.encryptedMessage === undefined)).toBe(true);
203
- expect(messages.every(m => m.seeker === undefined)).toBe(true);
204
- });
205
-
206
- it('should handle empty database gracefully', async () => {
207
- const count = await resetStuckSendingMessages();
208
- expect(count).toBe(0);
209
- });
210
- });
211
- });
212
-
213
- // ============================================================================
214
- // Messages from Unknown Peer
215
- // ============================================================================
216
-
217
- const EDGE_OWNER_USER_ID = encodeUserId(new Uint8Array(32).fill(1));
218
- const EDGE_CONTACT_USER_ID = encodeUserId(new Uint8Array(32).fill(2));
219
- const EDGE_UNKNOWN_USER_ID = encodeUserId(new Uint8Array(32).fill(99));
220
-
221
- describe('Messages from Unknown Peer', () => {
222
- let testDb: GossipDatabase;
223
-
224
- beforeEach(async () => {
225
- testDb = new GossipDatabase();
226
- await testDb.open();
227
- await Promise.all(testDb.tables.map(table => table.clear()));
228
-
229
- await testDb.discussions.add({
230
- ownerUserId: EDGE_OWNER_USER_ID,
231
- contactUserId: EDGE_CONTACT_USER_ID,
232
- direction: DiscussionDirection.RECEIVED,
233
- status: DiscussionStatus.ACTIVE,
234
- unreadCount: 0,
235
- createdAt: new Date(),
236
- updatedAt: new Date(),
237
- });
238
- });
239
-
240
- it('should not have discussion for unknown peer', async () => {
241
- const discussion = await testDb.getDiscussionByOwnerAndContact(
242
- EDGE_OWNER_USER_ID,
243
- EDGE_UNKNOWN_USER_ID
244
- );
245
-
246
- expect(discussion).toBeUndefined();
247
- });
248
-
249
- it('should have discussion for known peer', async () => {
250
- const discussion = await testDb.getDiscussionByOwnerAndContact(
251
- EDGE_OWNER_USER_ID,
252
- EDGE_CONTACT_USER_ID
253
- );
254
-
255
- expect(discussion).toBeDefined();
256
- expect(discussion?.contactUserId).toBe(EDGE_CONTACT_USER_ID);
257
- });
258
- });
259
-
260
- // ============================================================================
261
- // Seeker Stabilization Logic
262
- // ============================================================================
263
-
264
- describe('Seeker Stabilization Logic', () => {
265
- it('should detect when seekers are the same (stabilized)', () => {
266
- const seekers1 = new Set(['seeker1', 'seeker2', 'seeker3']);
267
- const seekers2 = new Set(['seeker1', 'seeker2', 'seeker3']);
268
-
269
- const areSame =
270
- seekers1.size === seekers2.size &&
271
- [...seekers1].every(s => seekers2.has(s));
272
-
273
- expect(areSame).toBe(true);
274
- });
275
-
276
- it('should detect when seekers changed (not stabilized)', () => {
277
- const seekers1 = new Set(['seeker1', 'seeker2']);
278
- const seekers2 = new Set(['seeker1', 'seeker2', 'seeker3']);
279
-
280
- const areSame =
281
- seekers1.size === seekers2.size &&
282
- [...seekers1].every(s => seekers2.has(s));
283
-
284
- expect(areSame).toBe(false);
285
- });
286
-
287
- it('should detect when seekers reduced', () => {
288
- const seekers1 = new Set(['seeker1', 'seeker2', 'seeker3']);
289
- const seekers2 = new Set(['seeker1', 'seeker2']);
290
-
291
- const areSame =
292
- seekers1.size === seekers2.size &&
293
- [...seekers1].every(s => seekers2.has(s));
294
-
295
- expect(areSame).toBe(false);
296
- });
297
-
298
- it('should handle empty seeker sets', () => {
299
- const seekers1 = new Set<string>();
300
- const seekers2 = new Set<string>();
301
-
302
- const areSame =
303
- seekers1.size === seekers2.size &&
304
- [...seekers1].every(s => seekers2.has(s));
305
-
306
- expect(areSame).toBe(true);
307
- });
308
-
309
- it('should respect maxFetchIterations limit', () => {
310
- const maxIterations = defaultSdkConfig.messages.maxFetchIterations;
311
- let iterations = 0;
312
- const seekersNeverStabilize = () => new Set([`seeker${iterations++}`]);
313
-
314
- let previousSeekers = new Set<string>();
315
- let loopCount = 0;
316
-
317
- while (loopCount < maxIterations) {
318
- const currentSeekers = seekersNeverStabilize();
319
- const stabilized =
320
- previousSeekers.size === currentSeekers.size &&
321
- [...previousSeekers].every(s => currentSeekers.has(s));
322
-
323
- if (stabilized) break;
324
-
325
- previousSeekers = currentSeekers;
326
- loopCount++;
327
- }
328
-
329
- expect(loopCount).toBe(maxIterations);
330
- });
331
- });