@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.
- package/README.md +2 -2
- package/dist/config/sdk.js +1 -1
- package/dist/contacts.d.ts +11 -17
- package/dist/contacts.js +20 -26
- package/dist/core/SdkEventEmitter.d.ts +2 -0
- package/dist/core/SdkEventEmitter.js +2 -0
- package/dist/core/SdkPolling.d.ts +2 -5
- package/dist/core/SdkPolling.js +1 -4
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.js +1 -0
- package/dist/db.d.ts +34 -56
- package/dist/db.js +39 -229
- package/dist/gossip.d.ts +54 -23
- package/dist/gossip.js +149 -120
- package/dist/index.d.ts +13 -4
- package/dist/index.js +13 -4
- package/dist/queries/activeSeekers.d.ts +2 -0
- package/dist/queries/activeSeekers.js +18 -0
- package/dist/queries/announcementCursors.d.ts +2 -0
- package/dist/queries/announcementCursors.js +20 -0
- package/dist/queries/contacts.d.ts +13 -0
- package/dist/queries/contacts.js +43 -0
- package/dist/queries/discussions.d.ts +21 -0
- package/dist/queries/discussions.js +73 -0
- package/dist/queries/index.d.ts +7 -0
- package/dist/queries/index.js +7 -0
- package/dist/queries/messages.d.ts +27 -0
- package/dist/queries/messages.js +120 -0
- package/dist/queries/pendingAnnouncements.d.ts +4 -0
- package/dist/queries/pendingAnnouncements.js +11 -0
- package/dist/queries/userProfile.d.ts +22 -0
- package/dist/queries/userProfile.js +116 -0
- package/dist/schema.d.ts +1280 -0
- package/dist/schema.js +164 -0
- package/dist/services/announcement.d.ts +8 -7
- package/dist/services/announcement.js +95 -121
- package/dist/services/auth.d.ts +1 -3
- package/dist/services/auth.js +4 -11
- package/dist/services/discussion.d.ts +8 -15
- package/dist/services/discussion.js +62 -62
- package/dist/services/message.d.ts +15 -26
- package/dist/services/message.js +282 -270
- package/dist/services/refresh.d.ts +3 -5
- package/dist/services/refresh.js +10 -13
- package/dist/sqlite-worker.d.ts +12 -0
- package/dist/sqlite-worker.js +106 -0
- package/dist/sqlite.d.ts +79 -0
- package/dist/sqlite.js +448 -0
- package/dist/utils/contacts.d.ts +2 -5
- package/dist/utils/contacts.js +23 -39
- package/dist/utils/discussions.d.ts +7 -2
- package/dist/utils/discussions.js +24 -5
- package/dist/utils/logs.js +1 -3
- package/dist/utils/validation.d.ts +2 -4
- package/dist/utils/validation.js +5 -10
- package/package.json +5 -7
- package/dist/api/messageProtocol/mock.d.ts +0 -12
- package/dist/api/messageProtocol/mock.js +0 -12
- package/dist/types/events.d.ts +0 -80
- 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
|
|
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
|
|
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(
|
|
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
|
-
|
|
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<
|
|
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(
|
|
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(
|
|
31
|
-
Object.defineProperty(this, "
|
|
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, "
|
|
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, "
|
|
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, "
|
|
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
|
|
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
|
-
|
|
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
|
|
111
|
+
await deleteDiscussionById(discussionId);
|
|
107
112
|
return { success: false, error: result.error };
|
|
108
113
|
}
|
|
109
114
|
if (payload?.message) {
|
|
110
|
-
await
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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
|
|
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(
|
|
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
|
|
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;
|