@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.
@@ -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, message?: string): Promise<{
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, message) => this._discussion.initialize(contact, message),
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, userData?: Uint8Array): Promise<{
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, userData) {
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, userData);
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
- let rawMessage;
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 = extractedUsername ||
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, announcementMessage);
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 message - Optional message to include in the announcement
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, message?: string): Promise<{
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 message - Optional message to include in the announcement
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, message) {
79
+ async initialize(contact, payload) {
68
80
  const log = logger.forMethod('initialize');
69
81
  try {
70
82
  const userId = this.session.userIdEncoded;
71
- // Encode message as UTF-8 if provided
72
- const userData = message
73
- ? new TextEncoder().encode(message)
74
- : new Uint8Array(0);
75
- log.info(`${userId} is establishing session with contact ${contact.name}`);
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: parsedAnnouncementMessage,
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@massalabs/gossip-sdk",
3
- "version": "0.0.2-dev.20260210150149",
3
+ "version": "0.0.2-dev.20260211110128",
4
4
  "description": "Gossip SDK for automation, chatbot, and integration use cases",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",