@massalabs/gossip-sdk 0.0.2-dev.20260210150149 → 0.0.2-dev.20260211110128
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/dist/gossipSdk.d.ts +2 -1
- package/dist/gossipSdk.js +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/services/announcement.d.ts +1 -1
- package/dist/services/announcement.js +6 -49
- package/dist/services/discussion.d.ts +14 -2
- package/dist/services/discussion.js +20 -29
- package/dist/utils/announcementPayload.d.ts +6 -0
- package/dist/utils/announcementPayload.js +23 -0
- package/package.json +1 -1
package/dist/gossipSdk.d.ts
CHANGED
|
@@ -48,6 +48,7 @@ import type { DeleteContactResult, UpdateContactNameResult } from './utils/conta
|
|
|
48
48
|
import { type ValidationResult } from './utils/validation';
|
|
49
49
|
import type { UserPublicKeys } from './wasm/bindings';
|
|
50
50
|
import { type SdkEventType, type SdkEventHandlers } from './core/SdkEventEmitter';
|
|
51
|
+
import { AnnouncementPayload } from './utils/announcementPayload';
|
|
51
52
|
export type { SdkEventType, SdkEventHandlers };
|
|
52
53
|
export interface GossipSdkInitOptions {
|
|
53
54
|
/** Database instance */
|
|
@@ -210,7 +211,7 @@ interface MessageServiceAPI {
|
|
|
210
211
|
}
|
|
211
212
|
interface DiscussionServiceAPI {
|
|
212
213
|
/** Start a new discussion with a contact */
|
|
213
|
-
start(contact: Contact,
|
|
214
|
+
start(contact: Contact, payload: AnnouncementPayload): Promise<{
|
|
214
215
|
discussionId: number;
|
|
215
216
|
announcement: Uint8Array;
|
|
216
217
|
}>;
|
package/dist/gossipSdk.js
CHANGED
|
@@ -322,7 +322,7 @@ class GossipSdkImpl {
|
|
|
322
322
|
findBySeeker: (seeker, ownerUserId) => this._message.findMessageBySeeker(seeker, ownerUserId),
|
|
323
323
|
};
|
|
324
324
|
this._discussionsAPI = {
|
|
325
|
-
start: (contact,
|
|
325
|
+
start: (contact, payload) => this._discussion.initialize(contact, payload),
|
|
326
326
|
accept: discussion => this._discussion.accept(discussion),
|
|
327
327
|
renew: contactUserId => this._discussion.renew(contactUserId),
|
|
328
328
|
isStable: (ownerUserId, contactUserId) => this._discussion.isStableState(ownerUserId, contactUserId),
|
package/dist/index.d.ts
CHANGED
|
@@ -35,6 +35,8 @@ export { getContacts, getContact, addContact, updateContactName, deleteContact,
|
|
|
35
35
|
export type { UpdateContactNameResult, DeleteContactResult, } from './utils/contacts';
|
|
36
36
|
export { updateDiscussionName } from './utils/discussions';
|
|
37
37
|
export type { UpdateDiscussionNameResult } from './utils/discussions';
|
|
38
|
+
export { encodeAnnouncementPayload, decodeAnnouncementPayload, } from './utils/announcementPayload';
|
|
39
|
+
export type { AnnouncementPayload } from './utils/announcementPayload';
|
|
38
40
|
export * from './types';
|
|
39
41
|
export { createMessageProtocol, restMessageProtocol, RestMessageProtocol, MessageProtocol, } from './api/messageProtocol';
|
|
40
42
|
export type { IMessageProtocol, EncryptedMessage, MessageProtocolResponse, BulletinItem, } from './api/messageProtocol';
|
package/dist/index.js
CHANGED
|
@@ -34,6 +34,8 @@ export { RefreshService } from './services/refresh';
|
|
|
34
34
|
export { getContacts, getContact, addContact, updateContactName, deleteContact, } from './contacts';
|
|
35
35
|
// Discussion utilities
|
|
36
36
|
export { updateDiscussionName } from './utils/discussions';
|
|
37
|
+
// Announcement payload helpers
|
|
38
|
+
export { encodeAnnouncementPayload, decodeAnnouncementPayload, } from './utils/announcementPayload';
|
|
37
39
|
// Types
|
|
38
40
|
export * from './types';
|
|
39
41
|
// Message Protocol
|
|
@@ -29,7 +29,7 @@ export declare class AnnouncementService {
|
|
|
29
29
|
counter?: string;
|
|
30
30
|
error?: string;
|
|
31
31
|
}>;
|
|
32
|
-
establishSession(contactPublicKeys: UserPublicKeys,
|
|
32
|
+
establishSession(contactPublicKeys: UserPublicKeys, payloadBytes?: Uint8Array): Promise<{
|
|
33
33
|
success: boolean;
|
|
34
34
|
error?: string;
|
|
35
35
|
announcement: Uint8Array;
|
|
@@ -9,6 +9,7 @@ import { SessionStatus } from '../wasm/bindings';
|
|
|
9
9
|
import { sessionStatusToString } from '../wasm/session';
|
|
10
10
|
import { Logger } from '../utils/logs';
|
|
11
11
|
import { defaultSdkConfig } from '../config/sdk';
|
|
12
|
+
import { decodeAnnouncementPayload } from '../utils/announcementPayload';
|
|
12
13
|
const logger = new Logger('AnnouncementService');
|
|
13
14
|
export const EstablishSessionError = 'Session manager failed to establish outgoing session';
|
|
14
15
|
export class AnnouncementService {
|
|
@@ -73,11 +74,11 @@ export class AnnouncementService {
|
|
|
73
74
|
};
|
|
74
75
|
}
|
|
75
76
|
}
|
|
76
|
-
async establishSession(contactPublicKeys,
|
|
77
|
+
async establishSession(contactPublicKeys, payloadBytes) {
|
|
77
78
|
const log = logger.forMethod('establishSession');
|
|
78
79
|
const contactUserId = encodeUserId(contactPublicKeys.derive_id());
|
|
79
80
|
// CRITICAL: await to ensure session state is persisted before sending
|
|
80
|
-
const announcement = await this.session.establishOutgoingSession(contactPublicKeys,
|
|
81
|
+
const announcement = await this.session.establishOutgoingSession(contactPublicKeys, payloadBytes);
|
|
81
82
|
if (announcement.length === 0) {
|
|
82
83
|
log.error('empty announcement returned', { contactUserId });
|
|
83
84
|
return {
|
|
@@ -367,51 +368,7 @@ export class AnnouncementService {
|
|
|
367
368
|
return { success: true };
|
|
368
369
|
}
|
|
369
370
|
log.info('announcement intended for us — decrypting');
|
|
370
|
-
|
|
371
|
-
if (result.user_data?.length > 0) {
|
|
372
|
-
try {
|
|
373
|
-
rawMessage = new TextDecoder().decode(result.user_data);
|
|
374
|
-
}
|
|
375
|
-
catch (error) {
|
|
376
|
-
log.error('failed to decode user data', error);
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
// Parse announcement message format:
|
|
380
|
-
// - JSON format: {"u":"username","m":"message"} (current)
|
|
381
|
-
// - Legacy colon format: "username:message" (backwards compat)
|
|
382
|
-
// - Plain text: "message" (oldest format)
|
|
383
|
-
// The username is used as the initial contact name if present.
|
|
384
|
-
// TODO: Remove legacy colon and plain text format support once all clients are updated
|
|
385
|
-
let extractedUsername;
|
|
386
|
-
let announcementMessage;
|
|
387
|
-
if (rawMessage) {
|
|
388
|
-
// Try JSON format first (starts with '{')
|
|
389
|
-
if (rawMessage.startsWith('{')) {
|
|
390
|
-
try {
|
|
391
|
-
const parsed = JSON.parse(rawMessage);
|
|
392
|
-
extractedUsername = parsed.u?.trim() || undefined;
|
|
393
|
-
announcementMessage = parsed.m?.trim() || undefined;
|
|
394
|
-
}
|
|
395
|
-
catch {
|
|
396
|
-
// Invalid JSON, treat as plain text
|
|
397
|
-
announcementMessage = rawMessage;
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
else {
|
|
401
|
-
// Legacy format: check for colon separator
|
|
402
|
-
const colonIndex = rawMessage.indexOf(':');
|
|
403
|
-
if (colonIndex !== -1) {
|
|
404
|
-
extractedUsername =
|
|
405
|
-
rawMessage.slice(0, colonIndex).trim() || undefined;
|
|
406
|
-
announcementMessage =
|
|
407
|
-
rawMessage.slice(colonIndex + 1).trim() || undefined;
|
|
408
|
-
}
|
|
409
|
-
else {
|
|
410
|
-
// Plain text (oldest format)
|
|
411
|
-
announcementMessage = rawMessage;
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
}
|
|
371
|
+
const { username, message } = decodeAnnouncementPayload(result.user_data);
|
|
415
372
|
const announcerPkeys = result.announcer_public_keys;
|
|
416
373
|
const contactUserIdRaw = announcerPkeys.derive_id();
|
|
417
374
|
const contactUserId = encodeUserId(contactUserIdRaw);
|
|
@@ -426,7 +383,7 @@ export class AnnouncementService {
|
|
|
426
383
|
const isNewContact = !contact;
|
|
427
384
|
if (isNewContact) {
|
|
428
385
|
// Use extracted username if present, otherwise generate temporary name
|
|
429
|
-
const name =
|
|
386
|
+
const name = username ||
|
|
430
387
|
(await this._generateTemporaryContactName(this.session.userIdEncoded));
|
|
431
388
|
await this.db.contacts.add({
|
|
432
389
|
ownerUserId: this.session.userIdEncoded,
|
|
@@ -445,7 +402,7 @@ export class AnnouncementService {
|
|
|
445
402
|
log.error('contact lookup failed after creation');
|
|
446
403
|
throw new Error('Could not find or create contact');
|
|
447
404
|
}
|
|
448
|
-
const { discussionId } = await this._handleReceivedDiscussion(this.session.userIdEncoded, contactUserId,
|
|
405
|
+
const { discussionId } = await this._handleReceivedDiscussion(this.session.userIdEncoded, contactUserId, message);
|
|
449
406
|
// Emit event for new discussion request
|
|
450
407
|
if (this.events.onDiscussionRequest) {
|
|
451
408
|
const discussion = await this.db.discussions.get(discussionId);
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { type Discussion, type Contact, type GossipDatabase } from '../db';
|
|
7
7
|
import { AnnouncementService } from './announcement';
|
|
8
8
|
import { SessionModule } from '../wasm/session';
|
|
9
|
+
import { AnnouncementPayload } from '../utils/announcementPayload';
|
|
9
10
|
import { GossipSdkEvents } from '../types/events';
|
|
10
11
|
/**
|
|
11
12
|
* Service for managing discussions between users.
|
|
@@ -33,10 +34,21 @@ export declare class DiscussionService {
|
|
|
33
34
|
/**
|
|
34
35
|
* Initialize a discussion with a contact using SessionManager
|
|
35
36
|
* @param contact - The contact to start a discussion with
|
|
36
|
-
* @param
|
|
37
|
+
* @param payload - Optional payload to include in the announcement (username and message)
|
|
37
38
|
* @returns The discussion ID and the created announcement
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```ts
|
|
42
|
+
* const payload: AnnouncementPayload = {
|
|
43
|
+
* username: 'alice',
|
|
44
|
+
* message: 'Hello!',
|
|
45
|
+
* };
|
|
46
|
+
*
|
|
47
|
+
* const { discussionId, announcement } =
|
|
48
|
+
* await discussionService.initialize(contact, payload);
|
|
49
|
+
* ```
|
|
38
50
|
*/
|
|
39
|
-
initialize(contact: Contact,
|
|
51
|
+
initialize(contact: Contact, payload?: AnnouncementPayload): Promise<{
|
|
40
52
|
discussionId: number;
|
|
41
53
|
announcement: Uint8Array;
|
|
42
54
|
}>;
|
|
@@ -8,6 +8,7 @@ import { UserPublicKeys, SessionStatus } from '../wasm/bindings';
|
|
|
8
8
|
import { EstablishSessionError } from './announcement';
|
|
9
9
|
import { sessionStatusToString } from '../wasm/session';
|
|
10
10
|
import { decodeUserId } from '../utils/userId';
|
|
11
|
+
import { encodeAnnouncementPayload, } from '../utils/announcementPayload';
|
|
11
12
|
import { Logger } from '../utils/logs';
|
|
12
13
|
const logger = new Logger('DiscussionService');
|
|
13
14
|
/**
|
|
@@ -61,19 +62,29 @@ export class DiscussionService {
|
|
|
61
62
|
/**
|
|
62
63
|
* Initialize a discussion with a contact using SessionManager
|
|
63
64
|
* @param contact - The contact to start a discussion with
|
|
64
|
-
* @param
|
|
65
|
+
* @param payload - Optional payload to include in the announcement (username and message)
|
|
65
66
|
* @returns The discussion ID and the created announcement
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```ts
|
|
70
|
+
* const payload: AnnouncementPayload = {
|
|
71
|
+
* username: 'alice',
|
|
72
|
+
* message: 'Hello!',
|
|
73
|
+
* };
|
|
74
|
+
*
|
|
75
|
+
* const { discussionId, announcement } =
|
|
76
|
+
* await discussionService.initialize(contact, payload);
|
|
77
|
+
* ```
|
|
66
78
|
*/
|
|
67
|
-
async initialize(contact,
|
|
79
|
+
async initialize(contact, payload) {
|
|
68
80
|
const log = logger.forMethod('initialize');
|
|
69
81
|
try {
|
|
70
82
|
const userId = this.session.userIdEncoded;
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const result = await this.announcementService.establishSession(UserPublicKeys.from_bytes(contact.publicKeys), userData);
|
|
83
|
+
let payloadBytes;
|
|
84
|
+
if (payload) {
|
|
85
|
+
payloadBytes = encodeAnnouncementPayload(payload.username, payload.message);
|
|
86
|
+
}
|
|
87
|
+
const result = await this.announcementService.establishSession(UserPublicKeys.from_bytes(contact.publicKeys), payloadBytes);
|
|
77
88
|
let status = DiscussionStatus.PENDING;
|
|
78
89
|
if (!result.success) {
|
|
79
90
|
log.error(`Failed to establish session with contact ${contact.name}, got error: ${result.error}`);
|
|
@@ -85,26 +96,6 @@ export class DiscussionService {
|
|
|
85
96
|
else {
|
|
86
97
|
log.info(`session established with contact and announcement sent: ${result.announcement.length}... bytes`);
|
|
87
98
|
}
|
|
88
|
-
// Parse announcement message to extract only the actual message content.
|
|
89
|
-
// The message parameter may be JSON format: {"u":"username","m":"message"}
|
|
90
|
-
// We only want to store the "m" (message) field, not the full JSON.
|
|
91
|
-
let parsedAnnouncementMessage;
|
|
92
|
-
if (message) {
|
|
93
|
-
if (message.startsWith('{')) {
|
|
94
|
-
try {
|
|
95
|
-
const parsed = JSON.parse(message);
|
|
96
|
-
parsedAnnouncementMessage = parsed.m?.trim() || undefined;
|
|
97
|
-
}
|
|
98
|
-
catch {
|
|
99
|
-
// Invalid JSON, treat as plain text
|
|
100
|
-
parsedAnnouncementMessage = message;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
else {
|
|
104
|
-
parsedAnnouncementMessage = message;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
// Persist discussion immediately with the announcement for reliable retry
|
|
108
99
|
const discussionId = await this.db.discussions.add({
|
|
109
100
|
ownerUserId: userId,
|
|
110
101
|
contactUserId: contact.userId,
|
|
@@ -112,7 +103,7 @@ export class DiscussionService {
|
|
|
112
103
|
status: status,
|
|
113
104
|
nextSeeker: undefined,
|
|
114
105
|
initiationAnnouncement: result.announcement,
|
|
115
|
-
announcementMessage:
|
|
106
|
+
announcementMessage: payload?.message,
|
|
116
107
|
unreadCount: 0,
|
|
117
108
|
createdAt: new Date(),
|
|
118
109
|
updatedAt: new Date(),
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export interface AnnouncementPayload {
|
|
2
|
+
username?: string;
|
|
3
|
+
message?: string;
|
|
4
|
+
}
|
|
5
|
+
export declare const encodeAnnouncementPayload: (username?: string, message?: string) => Uint8Array | undefined;
|
|
6
|
+
export declare const decodeAnnouncementPayload: (data?: Uint8Array) => AnnouncementPayload;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Args } from '@massalabs/massa-web3';
|
|
2
|
+
export const encodeAnnouncementPayload = (username, message) => {
|
|
3
|
+
const u = username?.trim() || '';
|
|
4
|
+
const m = message?.trim() || '';
|
|
5
|
+
if (!u && !m) {
|
|
6
|
+
return undefined;
|
|
7
|
+
}
|
|
8
|
+
return new Args().addString(u).addString(m).serialize();
|
|
9
|
+
};
|
|
10
|
+
export const decodeAnnouncementPayload = (data) => {
|
|
11
|
+
if (!data) {
|
|
12
|
+
return { username: undefined, message: undefined };
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
const args = new Args(data);
|
|
16
|
+
const username = args.nextString() || undefined;
|
|
17
|
+
const message = args.nextString() || undefined;
|
|
18
|
+
return { username, message };
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return { username: undefined, message: undefined };
|
|
22
|
+
}
|
|
23
|
+
};
|
package/package.json
CHANGED