@massalabs/gossip-sdk 0.0.2-dev.20260220052333 → 0.0.2-dev.20260220053835

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 (60) hide show
  1. package/README.md +2 -2
  2. package/dist/config/sdk.js +1 -1
  3. package/dist/contacts.d.ts +11 -17
  4. package/dist/contacts.js +20 -26
  5. package/dist/core/SdkEventEmitter.d.ts +2 -0
  6. package/dist/core/SdkEventEmitter.js +2 -0
  7. package/dist/core/SdkPolling.d.ts +2 -5
  8. package/dist/core/SdkPolling.js +1 -4
  9. package/dist/core/index.d.ts +1 -1
  10. package/dist/core/index.js +1 -0
  11. package/dist/db.d.ts +34 -56
  12. package/dist/db.js +39 -229
  13. package/dist/gossip.d.ts +54 -23
  14. package/dist/gossip.js +149 -120
  15. package/dist/index.d.ts +13 -4
  16. package/dist/index.js +13 -4
  17. package/dist/queries/activeSeekers.d.ts +2 -0
  18. package/dist/queries/activeSeekers.js +18 -0
  19. package/dist/queries/announcementCursors.d.ts +2 -0
  20. package/dist/queries/announcementCursors.js +20 -0
  21. package/dist/queries/contacts.d.ts +13 -0
  22. package/dist/queries/contacts.js +43 -0
  23. package/dist/queries/discussions.d.ts +21 -0
  24. package/dist/queries/discussions.js +73 -0
  25. package/dist/queries/index.d.ts +7 -0
  26. package/dist/queries/index.js +7 -0
  27. package/dist/queries/messages.d.ts +27 -0
  28. package/dist/queries/messages.js +120 -0
  29. package/dist/queries/pendingAnnouncements.d.ts +4 -0
  30. package/dist/queries/pendingAnnouncements.js +11 -0
  31. package/dist/queries/userProfile.d.ts +22 -0
  32. package/dist/queries/userProfile.js +116 -0
  33. package/dist/schema.d.ts +1280 -0
  34. package/dist/schema.js +164 -0
  35. package/dist/services/announcement.d.ts +8 -7
  36. package/dist/services/announcement.js +95 -121
  37. package/dist/services/auth.d.ts +1 -3
  38. package/dist/services/auth.js +4 -11
  39. package/dist/services/discussion.d.ts +8 -15
  40. package/dist/services/discussion.js +62 -62
  41. package/dist/services/message.d.ts +15 -26
  42. package/dist/services/message.js +282 -270
  43. package/dist/services/refresh.d.ts +3 -5
  44. package/dist/services/refresh.js +10 -13
  45. package/dist/sqlite-worker.d.ts +12 -0
  46. package/dist/sqlite-worker.js +106 -0
  47. package/dist/sqlite.d.ts +79 -0
  48. package/dist/sqlite.js +448 -0
  49. package/dist/utils/contacts.d.ts +2 -5
  50. package/dist/utils/contacts.js +23 -39
  51. package/dist/utils/discussions.d.ts +7 -2
  52. package/dist/utils/discussions.js +24 -5
  53. package/dist/utils/logs.js +1 -3
  54. package/dist/utils/validation.d.ts +2 -4
  55. package/dist/utils/validation.js +5 -10
  56. package/package.json +5 -7
  57. package/dist/api/messageProtocol/mock.d.ts +0 -12
  58. package/dist/api/messageProtocol/mock.js +0 -12
  59. package/dist/types/events.d.ts +0 -80
  60. package/dist/types/events.js +0 -7
@@ -3,13 +3,14 @@
3
3
  *
4
4
  * Class-based service for initializing, accepting, and managing discussions.
5
5
  */
6
- import { type Discussion, type Contact, type GossipDatabase } from '../db';
6
+ import { type Discussion, type Contact } from '../db';
7
7
  import { AnnouncementPayload } from '../utils/announcementPayload';
8
8
  import { AnnouncementService } from './announcement';
9
9
  import { SessionModule } from '../wasm/session';
10
+ import { RefreshService } from './refresh';
10
11
  import { Result } from '../utils/type';
11
12
  import { SdkEventEmitter } from '../core/SdkEventEmitter';
12
- export interface discussionInitializationResult {
13
+ export interface DiscussionInitializationResult {
13
14
  discussionId: number;
14
15
  announcement: Uint8Array;
15
16
  }
@@ -18,7 +19,7 @@ export interface discussionInitializationResult {
18
19
  *
19
20
  * @example
20
21
  * ```typescript
21
- * const discussionService = new DiscussionService(db, announcementService, session, refreshService);
22
+ * const discussionService = new DiscussionService(announcementService, session, eventEmitter, refreshService);
22
23
  *
23
24
  * // Initialize a new discussion
24
25
  * const result = await discussionService.initialize(contact, 'Hello!');
@@ -31,11 +32,12 @@ export interface discussionInitializationResult {
31
32
  * ```
32
33
  */
33
34
  export declare class DiscussionService {
34
- private db;
35
35
  private announcementService;
36
36
  private session;
37
37
  private eventEmitter;
38
- constructor(db: GossipDatabase, announcementService: AnnouncementService, session: SessionModule, eventEmitter: SdkEventEmitter);
38
+ private refreshService?;
39
+ constructor(announcementService: AnnouncementService, session: SessionModule, eventEmitter: SdkEventEmitter, refreshService?: RefreshService);
40
+ setRefreshService(refreshService: RefreshService): void;
39
41
  /**
40
42
  * Initialize a discussion with a contact using SessionManager
41
43
  * @param contact - The contact to start a discussion with
@@ -53,7 +55,7 @@ export declare class DiscussionService {
53
55
  * await discussionService.initialize(contact, payload);
54
56
  * ```
55
57
  */
56
- initialize(contact: Contact, payload?: AnnouncementPayload): Promise<Result<discussionInitializationResult, Error>>;
58
+ initialize(contact: Contact, payload?: AnnouncementPayload): Promise<Result<DiscussionInitializationResult, Error>>;
57
59
  /**
58
60
  * Accept a discussion request from a contact using SessionManager
59
61
  * @param discussion - The discussion to accept
@@ -69,12 +71,3 @@ export declare class DiscussionService {
69
71
  */
70
72
  createSessionForContact(contactUserId: string, userData: Uint8Array): Promise<Result<Uint8Array, Error>>;
71
73
  }
72
- /**
73
- * Reset the send queue for a contact.
74
- * All messages that are not yet delivered (i.e. READY, SENDING, SENT) are reset to WAITING_SESSION.
75
- * @param db - The database instance
76
- * @param ownerUserId - The user ID of the owner
77
- * @param contactUserId - The user ID of the contact
78
- * @returns A Promise that resolves when the send queue is reset
79
- */
80
- export declare function resetSendQueue(db: GossipDatabase, ownerUserId: string, contactUserId: string): Promise<void>;
@@ -3,18 +3,20 @@
3
3
  *
4
4
  * Class-based service for initializing, accepting, and managing discussions.
5
5
  */
6
- import { DiscussionDirection, MessageDirection, MessageStatus, MessageType, } from '../db';
6
+ import { DiscussionDirection, DiscussionStatus, MessageDirection, MessageStatus, MessageType, serializeSendAnnouncement, } from '../db';
7
+ import { toDiscussion } from '../utils/discussions';
7
8
  import { encodeAnnouncementPayload, } from '../utils/announcementPayload';
8
9
  import { UserPublicKeys } from '../wasm/bindings';
9
10
  import { Logger } from '../utils/logs';
10
11
  import { SdkEventType } from '../core/SdkEventEmitter';
12
+ import { getContactByOwnerAndUser, getDiscussionByOwnerAndContact, getDiscussionById, insertDiscussion, updateDiscussionById, deleteDiscussionById, insertMessage, resetSendQueueMessages, } from '../queries';
11
13
  const logger = new Logger('DiscussionService');
12
14
  /**
13
15
  * Service for managing discussions between users.
14
16
  *
15
17
  * @example
16
18
  * ```typescript
17
- * const discussionService = new DiscussionService(db, announcementService, session, refreshService);
19
+ * const discussionService = new DiscussionService(announcementService, session, eventEmitter, refreshService);
18
20
  *
19
21
  * // Initialize a new discussion
20
22
  * const result = await discussionService.initialize(contact, 'Hello!');
@@ -27,35 +29,38 @@ const logger = new Logger('DiscussionService');
27
29
  * ```
28
30
  */
29
31
  export class DiscussionService {
30
- constructor(db, announcementService, session, eventEmitter) {
31
- Object.defineProperty(this, "db", {
32
+ constructor(announcementService, session, eventEmitter, refreshService) {
33
+ Object.defineProperty(this, "announcementService", {
32
34
  enumerable: true,
33
35
  configurable: true,
34
36
  writable: true,
35
37
  value: void 0
36
38
  });
37
- Object.defineProperty(this, "announcementService", {
39
+ Object.defineProperty(this, "session", {
38
40
  enumerable: true,
39
41
  configurable: true,
40
42
  writable: true,
41
43
  value: void 0
42
44
  });
43
- Object.defineProperty(this, "session", {
45
+ Object.defineProperty(this, "eventEmitter", {
44
46
  enumerable: true,
45
47
  configurable: true,
46
48
  writable: true,
47
49
  value: void 0
48
50
  });
49
- Object.defineProperty(this, "eventEmitter", {
51
+ Object.defineProperty(this, "refreshService", {
50
52
  enumerable: true,
51
53
  configurable: true,
52
54
  writable: true,
53
55
  value: void 0
54
56
  });
55
- this.db = db;
56
57
  this.announcementService = announcementService;
57
58
  this.session = session;
58
59
  this.eventEmitter = eventEmitter;
60
+ this.refreshService = refreshService;
61
+ }
62
+ setRefreshService(refreshService) {
63
+ this.refreshService = refreshService;
59
64
  }
60
65
  /**
61
66
  * Initialize a discussion with a contact using SessionManager
@@ -78,20 +83,20 @@ export class DiscussionService {
78
83
  const log = logger.forMethod('initialize');
79
84
  try {
80
85
  const userId = this.session.userIdEncoded;
81
- const existing = await this.db.getDiscussionByOwnerAndContact(userId, contact.userId);
86
+ const existing = await getDiscussionByOwnerAndContact(userId, contact.userId);
82
87
  if (existing?.id) {
83
88
  return {
84
89
  success: false,
85
90
  error: new Error('Discussion already exists'),
86
91
  };
87
92
  }
88
- const discussionId = await this.db.discussions.add({
93
+ log.info(`${userId} is establishing session with contact ${contact.name}`);
94
+ const discussionId = await insertDiscussion({
89
95
  ownerUserId: userId,
90
96
  contactUserId: contact.userId,
91
97
  weAccepted: true,
92
- sendAnnouncement: null,
93
- lastAnnouncementMessage: payload?.message,
94
98
  direction: DiscussionDirection.INITIATED,
99
+ status: DiscussionStatus.PENDING,
95
100
  unreadCount: 0,
96
101
  createdAt: new Date(),
97
102
  updatedAt: new Date(),
@@ -103,11 +108,11 @@ export class DiscussionService {
103
108
  }
104
109
  const result = await this.createSessionForContact(contact.userId, payloadBytes ?? new Uint8Array(0));
105
110
  if (!result.success) {
106
- await this.db.discussions.delete(discussionId);
111
+ await deleteDiscussionById(discussionId);
107
112
  return { success: false, error: result.error };
108
113
  }
109
114
  if (payload?.message) {
110
- await this.db.addMessage({
115
+ await insertMessage({
111
116
  ownerUserId: userId,
112
117
  contactUserId: contact.userId,
113
118
  content: payload.message,
@@ -116,11 +121,15 @@ export class DiscussionService {
116
121
  status: MessageStatus.READ, // Announcement message are not like other msg. they are set as read to prevent them from being sent again if session is renewed
117
122
  timestamp: new Date(),
118
123
  });
124
+ // Store the announcement message on the discussion so the UI can display it
125
+ await updateDiscussionById(discussionId, {
126
+ announcementMessage: payload.message,
127
+ });
119
128
  }
120
129
  // Emit status change event
121
- const discussion = await this.db.discussions.get(discussionId);
130
+ const discussion = await getDiscussionById(discussionId);
122
131
  if (discussion) {
123
- this.eventEmitter.emit(SdkEventType.SESSION_CREATED, discussion);
132
+ this.eventEmitter.emit(SdkEventType.SESSION_CREATED, toDiscussion(discussion));
124
133
  }
125
134
  return {
126
135
  success: true,
@@ -151,7 +160,7 @@ export class DiscussionService {
151
160
  bytes: result.data?.length ?? 0,
152
161
  });
153
162
  // Emit status change event
154
- const updatedDiscussion = await this.db.discussions.get(discussion.id);
163
+ const updatedDiscussion = await getDiscussionById(discussion.id);
155
164
  if (updatedDiscussion) {
156
165
  this.eventEmitter.emit(SdkEventType.SESSION_ACCEPTED, updatedDiscussion.contactUserId);
157
166
  }
@@ -177,11 +186,11 @@ export class DiscussionService {
177
186
  async createSessionForContact(contactUserId, userData) {
178
187
  const log = logger.forMethod('createSessionForContact');
179
188
  const ownerUserId = this.session.userIdEncoded;
180
- const discussion = await this.db.getDiscussionByOwnerAndContact(ownerUserId, contactUserId);
189
+ const discussion = await getDiscussionByOwnerAndContact(ownerUserId, contactUserId);
181
190
  if (!discussion) {
182
191
  return { success: false, error: new Error('Discussion not found') };
183
192
  }
184
- const contact = await this.db.getContactByOwnerAndUserId(ownerUserId, contactUserId);
193
+ const contact = await getContactByOwnerAndUser(ownerUserId, contactUserId);
185
194
  if (!contact) {
186
195
  return { success: false, error: new Error('Contact not found') };
187
196
  }
@@ -195,50 +204,41 @@ export class DiscussionService {
195
204
  return sessionResult;
196
205
  }
197
206
  const now = new Date();
198
- // Wrap discussion update and queue reset in a transaction for atomicity
199
- const err = await this.db.transaction('rw', this.db.discussions, this.db.messages, async () => {
200
- try {
201
- // add the new announcement to the discussion
202
- await this.db.discussions.update(discussion.id, {
203
- weAccepted: true,
204
- sendAnnouncement: {
205
- announcement_bytes: sessionResult.data,
206
- when_to_send: now,
207
- },
208
- updatedAt: now,
209
- });
210
- // reset all messages in send queue to WAITING_SESSION for this contact
211
- await resetSendQueue(this.db, ownerUserId, contactUserId);
212
- return undefined;
213
- }
214
- catch (error) {
215
- return new Error('Failed to update discussion: ' + error);
216
- }
217
- });
218
- if (err) {
219
- return { success: false, error: err };
207
+ try {
208
+ // add the new announcement to the discussion
209
+ await updateDiscussionById(discussion.id, {
210
+ weAccepted: true,
211
+ sendAnnouncement: serializeSendAnnouncement({
212
+ announcement_bytes: sessionResult.data,
213
+ when_to_send: now,
214
+ }),
215
+ initiationAnnouncement: sessionResult.data,
216
+ updatedAt: now,
217
+ });
218
+ // reset all messages in send queue to WAITING_SESSION for this contact
219
+ await resetSendQueueMessages(ownerUserId, contactUserId, [
220
+ MessageStatus.READY,
221
+ MessageStatus.SENT,
222
+ ]);
223
+ }
224
+ catch (error) {
225
+ return {
226
+ success: false,
227
+ error: new Error('Failed to update discussion: ' + error),
228
+ };
229
+ }
230
+ try {
231
+ /* trigger a state update to send the new announcement
232
+ If the stateUpdate function is already running, it will be skipped.
233
+ */
234
+ await this.refreshService?.stateUpdate();
235
+ }
236
+ catch (error) {
237
+ return {
238
+ success: false,
239
+ error: new Error('Failed to trigger state update: ' + error),
240
+ };
220
241
  }
221
242
  return { success: true, data: sessionResult.data };
222
243
  }
223
244
  }
224
- /**
225
- * Reset the send queue for a contact.
226
- * All messages that are not yet delivered (i.e. READY, SENDING, SENT) are reset to WAITING_SESSION.
227
- * @param db - The database instance
228
- * @param ownerUserId - The user ID of the owner
229
- * @param contactUserId - The user ID of the contact
230
- * @returns A Promise that resolves when the send queue is reset
231
- */
232
- export async function resetSendQueue(db, ownerUserId, contactUserId) {
233
- await db.messages
234
- .where('[ownerUserId+contactUserId]')
235
- .equals([ownerUserId, contactUserId])
236
- .and(message => message.direction === MessageDirection.OUTGOING &&
237
- [MessageStatus.READY, MessageStatus.SENT].includes(message.status))
238
- .modify({
239
- status: MessageStatus.WAITING_SESSION,
240
- encryptedMessage: undefined,
241
- seeker: undefined,
242
- whenToSend: undefined,
243
- });
244
- }
@@ -4,13 +4,16 @@
4
4
  * Handles fetching encrypted messages from the protocol and decrypting them.
5
5
  * This service works both in host app contexts and SDK/automation context.
6
6
  */
7
- import { type Message, type GossipDatabase } from '../db';
7
+ import { type Message } from '../db';
8
+ import { type MessageRow } from '../queries';
8
9
  import { IMessageProtocol } from '../api/messageProtocol';
9
10
  import { SessionModule } from '../wasm';
10
11
  import { Result } from '../utils/type';
11
12
  import { SdkConfig } from '../config/sdk';
12
- import { DiscussionService } from './discussion';
13
13
  import { SdkEventEmitter } from '../core/SdkEventEmitter';
14
+ import type { RefreshService } from './refresh';
15
+ /** Convert a SQLite row from the messages table to a Message object. */
16
+ export declare function rowToMessage(row: MessageRow): Message;
14
17
  export interface MessageResult {
15
18
  success: boolean;
16
19
  newMessagesCount: number;
@@ -22,43 +25,29 @@ export interface SendMessageResult {
22
25
  error?: string;
23
26
  }
24
27
  export declare class MessageService {
25
- private db;
26
28
  private messageProtocol;
27
29
  private session;
28
- private discussionService;
29
30
  private eventEmitter;
30
31
  private config;
32
+ private refreshService?;
31
33
  private processingContacts;
32
34
  private isFetchingMessages;
33
- constructor(db: GossipDatabase, messageProtocol: IMessageProtocol, session: SessionModule, discussionService: DiscussionService, eventEmitter: SdkEventEmitter, config?: SdkConfig);
35
+ constructor(messageProtocol: IMessageProtocol, session: SessionModule, eventEmitter: SdkEventEmitter, config?: SdkConfig);
36
+ setRefreshService(refreshService: RefreshService): void;
34
37
  fetchMessages(): Promise<MessageResult>;
38
+ /**
39
+ * Add a message to SQLite and update the corresponding discussion.
40
+ */
41
+ private addMessageAndUpdateDiscussion;
35
42
  private decryptMessages;
36
43
  private storeDecryptedMessages;
37
44
  /**
38
- * Checks for duplicate incoming messages in the message database based on messageId.
39
- *
40
- * A message is considered a duplicate if:
41
- * - It has the same messageId
42
- * - It is from the same sender (contactUserId)
43
- * - Belongs to the same conversation (ownerUserId)
44
- *
45
- * This protects against scenarios where:
46
- * 1. A sender successfully delivers a message to the network,
47
- * 2. Their app crashes or resets before marking the message as SENT in the local DB,
48
- * 3. On restart, the sender's app re-sends the message (possibly with a different seeker),
49
- * 4. The receiver gets the same message twice,
50
- * 5. Duplicates are prevented by matching messageId byte-for-byte.
51
- *
52
- * This method uses messageId (usually a 12-byte random value) for exact match detection.
53
- * If a duplicate is found, it updates certain fields (e.g., content, replyTo, forwardOf)
54
- * on the existing message and returns true.
55
- *
56
- * @param message - The incoming decrypted message object.
57
- * @param ownerUserId - The userId of the message database owner (the recipient).
58
- * @returns true if a duplicate message (by messageId) was found; otherwise false.
45
+ * Checks for duplicate incoming messages based on messageId (12-byte random).
46
+ * Returns true if a message with the same messageId already exists (skips storage).
59
47
  */
60
48
  private handleDuplicateMessageId;
61
49
  findMessageByMsgId(messageId: Uint8Array, ownerUserId: string, contactUserId?: string): Promise<Message | undefined>;
50
+ findMessageBySeeker(seeker: Uint8Array, ownerUserId: string): Promise<Message | undefined>;
62
51
  private acknowledgeMessages;
63
52
  sendMessage(message: Message): Promise<SendMessageResult>;
64
53
  private serializeMessage;