@massalabs/gossip-sdk 0.0.2-dev.20260212071538 → 0.0.2-dev.20260217093203

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 CHANGED
@@ -125,8 +125,8 @@ const result = await sdk.messages.send({
125
125
  // Fetch new messages from server
126
126
  const fetchResult = await sdk.messages.fetch();
127
127
 
128
- // Find message by seeker
129
- const msg = await sdk.messages.findBySeeker(seeker, ownerUserId);
128
+ // Find message by messageId
129
+ const msg = await gossipSdk.messages.findByMsgId(messageId, ownerUserId);
130
130
 
131
131
  // Mark as read
132
132
  await sdk.messages.markAsRead(messageId);
@@ -0,0 +1 @@
1
+ export { restMessageProtocol, RestMessageProtocol, type EncryptedMessage, } from './messageProtocol';
@@ -0,0 +1 @@
1
+ export { restMessageProtocol, RestMessageProtocol, } from './messageProtocol';
@@ -4,16 +4,14 @@
4
4
  * Factory functions and exports for message protocol implementations.
5
5
  */
6
6
  export type { EncryptedMessage, IMessageProtocol, MessageProtocolResponse, BulletinItem, } from './types';
7
- export { RestMessageProtocol } from './rest';
8
- export { MessageProtocol } from './mock';
9
7
  import type { IMessageProtocol } from './types';
10
- import { type MessageProtocolType } from '../../config/protocol';
11
8
  /**
12
9
  * Factory function to create message protocol instances
13
10
  */
14
- export declare function createMessageProtocol(type?: MessageProtocolType, config?: Partial<{
11
+ export declare function createMessageProtocol(config?: Partial<{
15
12
  baseUrl: string;
16
13
  timeout: number;
17
14
  retryAttempts: number;
18
15
  }>): IMessageProtocol;
19
16
  export declare const restMessageProtocol: IMessageProtocol;
17
+ export { RestMessageProtocol } from './rest';
@@ -3,24 +3,13 @@
3
3
  *
4
4
  * Factory functions and exports for message protocol implementations.
5
5
  */
6
- export { RestMessageProtocol } from './rest';
7
- export { MessageProtocol } from './mock';
8
- import { defaultMessageProtocol, protocolConfig, } from '../../config/protocol';
6
+ import { protocolConfig } from '../../config/protocol';
9
7
  import { RestMessageProtocol } from './rest';
10
- import { MessageProtocol } from './mock';
11
8
  /**
12
9
  * Factory function to create message protocol instances
13
10
  */
14
- export function createMessageProtocol(type = defaultMessageProtocol, config) {
15
- switch (type) {
16
- case 'rest': {
17
- return new RestMessageProtocol(config?.baseUrl || protocolConfig.baseUrl, config?.timeout || 10000, config?.retryAttempts || 3);
18
- }
19
- case 'mock': {
20
- return new MessageProtocol(config?.baseUrl || protocolConfig.baseUrl, config?.timeout || 10000, config?.retryAttempts || 3);
21
- }
22
- default:
23
- throw new Error(`Unsupported message protocol type: ${type}`);
24
- }
11
+ export function createMessageProtocol(config) {
12
+ return new RestMessageProtocol(config?.baseUrl ?? protocolConfig.baseUrl, config?.timeout ?? protocolConfig.timeout, config?.retryAttempts ?? protocolConfig.retryAttempts);
25
13
  }
26
14
  export const restMessageProtocol = createMessageProtocol();
15
+ export { RestMessageProtocol } from './rest';
@@ -0,0 +1,2 @@
1
+ export * from './bip39';
2
+ export * from './encryption';
@@ -0,0 +1,2 @@
1
+ export * from './bip39';
2
+ export * from './encryption';
package/dist/db.d.ts CHANGED
@@ -6,6 +6,7 @@
6
6
  */
7
7
  import Dexie, { Table } from 'dexie';
8
8
  export type AuthMethod = 'capacitor' | 'webauthn' | 'password';
9
+ export declare const MESSAGE_ID_SIZE = 12;
9
10
  export interface Contact {
10
11
  id?: number;
11
12
  ownerUserId: string;
@@ -19,6 +20,7 @@ export interface Contact {
19
20
  }
20
21
  export interface Message {
21
22
  id?: number;
23
+ messageId?: Uint8Array;
22
24
  ownerUserId: string;
23
25
  contactUserId: string;
24
26
  content: string;
@@ -32,12 +34,11 @@ export interface Message {
32
34
  metadata?: Record<string, unknown>;
33
35
  seeker?: Uint8Array;
34
36
  replyTo?: {
35
- originalContent?: string;
36
- originalSeeker: Uint8Array;
37
+ originalMsgId: Uint8Array;
37
38
  };
38
39
  forwardOf?: {
39
40
  originalContent?: string;
40
- originalSeeker: Uint8Array;
41
+ originalContactId?: Uint8Array;
41
42
  };
42
43
  }
43
44
  export interface UserProfile {
package/dist/db.js CHANGED
@@ -5,6 +5,8 @@
5
5
  * Provides tables for contacts, messages, discussions, user profiles, and more.
6
6
  */
7
7
  import Dexie from 'dexie';
8
+ // Constants
9
+ export const MESSAGE_ID_SIZE = 12;
8
10
  export var DiscussionDirection;
9
11
  (function (DiscussionDirection) {
10
12
  DiscussionDirection["RECEIVED"] = "received";
@@ -174,8 +174,8 @@ interface MessageServiceAPI {
174
174
  send(message: Omit<Message, 'id'>): Promise<SendMessageResult>;
175
175
  /** Fetch and decrypt messages from the protocol */
176
176
  fetch(): Promise<MessageResult>;
177
- /** Find a message by its seeker */
178
- findBySeeker(seeker: Uint8Array, ownerUserId: string): Promise<Message | undefined>;
177
+ /** Find a message by its messageId */
178
+ findByMsgId(messageId: Uint8Array, ownerUserId: string, contactUserId?: string): Promise<Message | undefined>;
179
179
  /** Mark a message as read */
180
180
  markAsRead(id: number): Promise<boolean>;
181
181
  }
@@ -60,9 +60,6 @@ import { SessionManagerWrapper, } from './wasm/bindings';
60
60
  import { SdkEventEmitter, SdkEventType, } from './core/SdkEventEmitter';
61
61
  import { SdkPolling } from './core/SdkPolling';
62
62
  export { SdkEventType };
63
- // ─────────────────────────────────────────────────────────────────────────────
64
- // Types
65
- // ─────────────────────────────────────────────────────────────────────────────
66
63
  export var SdkStatus;
67
64
  (function (SdkStatus) {
68
65
  SdkStatus["UNINITIALIZED"] = "uninitialized";
@@ -270,7 +267,7 @@ class GossipSdk {
270
267
  return result;
271
268
  }),
272
269
  fetch: () => this._message.fetchMessages(),
273
- findBySeeker: (seeker, ownerUserId) => this._message.findMessageBySeeker(seeker, ownerUserId),
270
+ findByMsgId: (messageId, ownerUserId, contactUserId) => this._message.findMessageByMsgId(messageId, ownerUserId, contactUserId),
274
271
  markAsRead: id => this._message.markAsRead(id),
275
272
  };
276
273
  this._discussionsAPI = {
package/dist/index.d.ts CHANGED
@@ -12,21 +12,9 @@
12
12
  *
13
13
  * @packageDocumentation
14
14
  */
15
- export { createGossipSdk, GossipSdk, SdkEventType } from './gossipSdk';
16
- export type { GossipSdkInitOptions, OpenSessionOptions, SdkEventHandlers, } from './gossipSdk';
15
+ export * from './api';
16
+ export * from './crypto';
17
17
  export * from './db';
18
- export * from './utils/userId';
19
- export * from './utils/base64';
20
- export * from './utils/validation';
21
- export * from './utils/announcementPayload';
22
- export * from './utils/discussions';
23
- export * from './utils/contacts';
24
- export type { Result } from './utils/type';
25
- export * from './crypto/bip39';
26
- export * from './crypto/encryption';
27
- export * from './wasm/encryption';
28
- export * from './wasm/userKeys';
29
- export { SessionStatus, UserPublicKeys, UserSecretKeys, SendMessageOutput, ReceiveMessageOutput, AnnouncementResult, } from './wasm/bindings';
30
- export { restMessageProtocol, RestMessageProtocol, } from './api/messageProtocol';
31
- export type { EncryptedMessage } from './api/messageProtocol';
32
- export type { PublicKeyResult } from './services/auth';
18
+ export * from './gossip';
19
+ export * from './utils';
20
+ export * from './wasm';
package/dist/index.js CHANGED
@@ -12,35 +12,9 @@
12
12
  *
13
13
  * @packageDocumentation
14
14
  */
15
- // ─────────────────────────────────────────────────────────────────────────────
16
- // SDK Primary API
17
- // ─────────────────────────────────────────────────────────────────────────────
18
- export { createGossipSdk, GossipSdk, SdkEventType } from './gossipSdk';
19
- // ─────────────────────────────────────────────────────────────────────────────
20
- // Database — entity types, enums, database class
21
- // ─────────────────────────────────────────────────────────────────────────────
15
+ export * from './api';
16
+ export * from './crypto';
22
17
  export * from './db';
23
- // ─────────────────────────────────────────────────────────────────────────────
24
- // Utilities — export entire modules (safe: pure functions / types)
25
- // ─────────────────────────────────────────────────────────────────────────────
26
- export * from './utils/userId';
27
- export * from './utils/base64';
28
- export * from './utils/validation';
29
- export * from './utils/announcementPayload';
30
- export * from './utils/discussions';
31
- export * from './utils/contacts';
32
- // ─────────────────────────────────────────────────────────────────────────────
33
- // Crypto — key generation, encryption, mnemonic
34
- // ─────────────────────────────────────────────────────────────────────────────
35
- export * from './crypto/bip39';
36
- export * from './crypto/encryption';
37
- export * from './wasm/encryption';
38
- export * from './wasm/userKeys';
39
- // ─────────────────────────────────────────────────────────────────────────────
40
- // WASM bindings — session status, public keys, protocol outputs
41
- // ─────────────────────────────────────────────────────────────────────────────
42
- export { SessionStatus, UserPublicKeys, UserSecretKeys, SendMessageOutput, ReceiveMessageOutput, AnnouncementResult, } from './wasm/bindings';
43
- // ─────────────────────────────────────────────────────────────────────────────
44
- // Protocol
45
- // ─────────────────────────────────────────────────────────────────────────────
46
- export { restMessageProtocol, RestMessageProtocol, } from './api/messageProtocol';
18
+ export * from './gossip';
19
+ export * from './utils';
20
+ export * from './wasm';
@@ -0,0 +1,18 @@
1
+ export declare enum MessageType {
2
+ MESSAGE_TYPE_REGULAR = 0,
3
+ MESSAGE_TYPE_REPLY = 1,
4
+ MESSAGE_TYPE_FORWARD = 2,
5
+ MESSAGE_TYPE_KEEP_ALIVE = 3
6
+ }
7
+ export interface Message {
8
+ messageType?: MessageType;
9
+ messageId?: Uint8Array;
10
+ content?: string;
11
+ citedMsgId?: Uint8Array;
12
+ citedContactId?: Uint8Array;
13
+ forwardedContent?: string;
14
+ }
15
+ export declare const Message: {
16
+ encode(message: Message): Uint8Array;
17
+ decode(buffer: Uint8Array): Message;
18
+ };
@@ -0,0 +1,141 @@
1
+ // Generated by protoc from: src/proto/message.proto
2
+ // Do not edit manually.
3
+ export var MessageType;
4
+ (function (MessageType) {
5
+ MessageType[MessageType["MESSAGE_TYPE_REGULAR"] = 0] = "MESSAGE_TYPE_REGULAR";
6
+ MessageType[MessageType["MESSAGE_TYPE_REPLY"] = 1] = "MESSAGE_TYPE_REPLY";
7
+ MessageType[MessageType["MESSAGE_TYPE_FORWARD"] = 2] = "MESSAGE_TYPE_FORWARD";
8
+ MessageType[MessageType["MESSAGE_TYPE_KEEP_ALIVE"] = 3] = "MESSAGE_TYPE_KEEP_ALIVE";
9
+ })(MessageType || (MessageType = {}));
10
+ const textEncoder = new TextEncoder();
11
+ const textDecoder = new TextDecoder();
12
+ function encodeVarint(value, out) {
13
+ let v = value >>> 0;
14
+ while (v >= 0x80) {
15
+ out.push((v & 0x7f) | 0x80);
16
+ v >>>= 7;
17
+ }
18
+ out.push(v);
19
+ }
20
+ function decodeVarint(buffer, offset) {
21
+ let result = 0;
22
+ let shift = 0;
23
+ let pos = offset;
24
+ while (pos < buffer.length) {
25
+ const byte = buffer[pos++];
26
+ result |= (byte & 0x7f) << shift;
27
+ if ((byte & 0x80) === 0) {
28
+ return { value: result >>> 0, offset: pos };
29
+ }
30
+ shift += 7;
31
+ if (shift > 35) {
32
+ throw new Error('Varint too long');
33
+ }
34
+ }
35
+ throw new Error('Truncated varint');
36
+ }
37
+ function writeBytes(fieldNumber, bytes, out) {
38
+ encodeVarint((fieldNumber << 3) | 2, out);
39
+ encodeVarint(bytes.length, out);
40
+ for (let i = 0; i < bytes.length; i++) {
41
+ out.push(bytes[i]);
42
+ }
43
+ }
44
+ function writeString(fieldNumber, value, out) {
45
+ const bytes = textEncoder.encode(value);
46
+ writeBytes(fieldNumber, bytes, out);
47
+ }
48
+ function skipField(wireType, buffer, offset) {
49
+ switch (wireType) {
50
+ case 0: {
51
+ return decodeVarint(buffer, offset).offset;
52
+ }
53
+ case 2: {
54
+ const { value: length, offset: nextOffset } = decodeVarint(buffer, offset);
55
+ return nextOffset + length;
56
+ }
57
+ default:
58
+ throw new Error(`Unsupported wire type: ${wireType}`);
59
+ }
60
+ }
61
+ export const Message = {
62
+ encode(message) {
63
+ const out = [];
64
+ if (message.messageType !== undefined) {
65
+ encodeVarint(1 << 3, out);
66
+ encodeVarint(message.messageType, out);
67
+ }
68
+ if (message.messageId !== undefined) {
69
+ writeBytes(2, message.messageId, out);
70
+ }
71
+ if (message.content !== undefined) {
72
+ writeString(3, message.content, out);
73
+ }
74
+ if (message.citedMsgId !== undefined) {
75
+ writeBytes(4, message.citedMsgId, out);
76
+ }
77
+ if (message.citedContactId !== undefined) {
78
+ writeBytes(5, message.citedContactId, out);
79
+ }
80
+ if (message.forwardedContent !== undefined) {
81
+ writeString(6, message.forwardedContent, out);
82
+ }
83
+ return new Uint8Array(out);
84
+ },
85
+ decode(buffer) {
86
+ const message = { content: '' };
87
+ let offset = 0;
88
+ while (offset < buffer.length) {
89
+ const { value: key, offset: nextOffset } = decodeVarint(buffer, offset);
90
+ offset = nextOffset;
91
+ const fieldNumber = key >> 3;
92
+ const wireType = key & 0x7;
93
+ switch (fieldNumber) {
94
+ case 1: {
95
+ const result = decodeVarint(buffer, offset);
96
+ message.messageType = result.value;
97
+ offset = result.offset;
98
+ break;
99
+ }
100
+ case 2: {
101
+ const { value: length, offset: lengthOffset } = decodeVarint(buffer, offset);
102
+ const end = lengthOffset + length;
103
+ message.messageId = buffer.slice(lengthOffset, end);
104
+ offset = end;
105
+ break;
106
+ }
107
+ case 3: {
108
+ const { value: length, offset: lengthOffset } = decodeVarint(buffer, offset);
109
+ const end = lengthOffset + length;
110
+ message.content = textDecoder.decode(buffer.slice(lengthOffset, end));
111
+ offset = end;
112
+ break;
113
+ }
114
+ case 4: {
115
+ const { value: length, offset: lengthOffset } = decodeVarint(buffer, offset);
116
+ const end = lengthOffset + length;
117
+ message.citedMsgId = buffer.slice(lengthOffset, end);
118
+ offset = end;
119
+ break;
120
+ }
121
+ case 5: {
122
+ const { value: length, offset: lengthOffset } = decodeVarint(buffer, offset);
123
+ const end = lengthOffset + length;
124
+ message.citedContactId = buffer.slice(lengthOffset, end);
125
+ offset = end;
126
+ break;
127
+ }
128
+ case 6: {
129
+ const { value: length, offset: lengthOffset } = decodeVarint(buffer, offset);
130
+ const end = lengthOffset + length;
131
+ message.forwardedContent = textDecoder.decode(buffer.slice(lengthOffset, end));
132
+ offset = end;
133
+ break;
134
+ }
135
+ default:
136
+ offset = skipField(wireType, buffer, offset);
137
+ }
138
+ }
139
+ return message;
140
+ },
141
+ };
@@ -6,13 +6,6 @@
6
6
  import { UserPublicKeys } from '../wasm/bindings';
7
7
  import { IMessageProtocol } from '../api/messageProtocol/types';
8
8
  import { type GossipDatabase } from '../db';
9
- export type PublicKeyResult = {
10
- publicKey: UserPublicKeys;
11
- error?: never;
12
- } | {
13
- publicKey?: never;
14
- error: string;
15
- };
16
9
  export declare class AuthService {
17
10
  private db;
18
11
  messageProtocol: IMessageProtocol;
@@ -21,7 +14,7 @@ export declare class AuthService {
21
14
  * Fetch public key by userId
22
15
  * @param userId - Bech32-encoded userId (e.g., "gossip1...")
23
16
  */
24
- fetchPublicKeyByUserId(userId: string): Promise<PublicKeyResult>;
17
+ fetchPublicKeyByUserId(userId: string): Promise<UserPublicKeys>;
25
18
  /**
26
19
  * Ensure public key is published (check first, then publish if needed).
27
20
  * If no user profile exists, the key is still published so the gossip ID is discoverable.
@@ -28,14 +28,10 @@ export class AuthService {
28
28
  async fetchPublicKeyByUserId(userId) {
29
29
  try {
30
30
  const base64PublicKey = await this.messageProtocol.fetchPublicKeyByUserId(decodeUserId(userId));
31
- return {
32
- publicKey: UserPublicKeys.from_bytes(decodeFromBase64(base64PublicKey)),
33
- };
31
+ return UserPublicKeys.from_bytes(decodeFromBase64(base64PublicKey));
34
32
  }
35
33
  catch (err) {
36
- return {
37
- error: getPublicKeyErrorMessage(err),
38
- };
34
+ throw new Error(getPublicKeyErrorMessage(err));
39
35
  }
40
36
  }
41
37
  /**
@@ -35,28 +35,30 @@ export declare class MessageService {
35
35
  private decryptMessages;
36
36
  private storeDecryptedMessages;
37
37
  /**
38
- * Check if a message is a duplicate based on content and timestamp.
38
+ * Checks for duplicate incoming messages in the message database based on messageId.
39
39
  *
40
- * A message is considered duplicate if:
41
- * - Same sender (contactUserId)
42
- * - Same content
43
- * - Incoming direction
44
- * - Timestamp within deduplication window (default 30 seconds)
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)
45
44
  *
46
- * This handles the edge case where:
47
- * 1. Sender sends message successfully to network
48
- * 2. Sender app crashes before updating DB status to SENT
49
- * 3. On restart, message is reset to WAITING_SESSION and re-sent
50
- * 4. Receiver gets the same message twice with different seekers
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
51
  *
52
- * @param ownerUserId - The owner's user ID
53
- * @param contactUserId - The sender's user ID
54
- * @param content - The message content
55
- * @param timestamp - The message timestamp
56
- * @returns true if a duplicate exists
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.
57
59
  */
58
- private isDuplicateMessage;
59
- findMessageBySeeker(seeker: Uint8Array, ownerUserId: string): Promise<Message | undefined>;
60
+ private handleDuplicateMessageId;
61
+ findMessageByMsgId(messageId: Uint8Array, ownerUserId: string, contactUserId?: string): Promise<Message | undefined>;
60
62
  private acknowledgeMessages;
61
63
  sendMessage(message: Message): Promise<SendMessageResult>;
62
64
  private serializeMessage;
@@ -4,7 +4,7 @@
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 { MessageDirection, MessageStatus, MessageType, } from '../db';
7
+ import { MessageDirection, MessageStatus, MessageType, MESSAGE_ID_SIZE, } from '../db';
8
8
  import { decodeUserId, encodeUserId } from '../utils/userId';
9
9
  import { SessionStatus } from '../wasm/bindings';
10
10
  import { serializeRegularMessage, serializeReplyMessage, serializeForwardMessage, serializeKeepAliveMessage, deserializeMessage, } from '../utils/messageSerialization';
@@ -167,25 +167,22 @@ export class MessageService {
167
167
  if (deserialized.type === MessageType.KEEP_ALIVE) {
168
168
  continue;
169
169
  }
170
+ if (!deserialized.messageId ||
171
+ deserialized.messageId.length !== MESSAGE_ID_SIZE) {
172
+ log.warn('missing or invalid messageId, skipping message', {
173
+ messageId: deserialized.messageId,
174
+ });
175
+ }
170
176
  decrypted.push({
171
177
  content: deserialized.content,
172
178
  sentAt: new Date(Number(out.timestamp)),
173
179
  senderId: encodeUserId(out.user_id),
174
180
  seeker: msg.seeker,
181
+ messageId: deserialized.messageId ?? new Uint8Array(),
175
182
  encryptedMessage: msg.ciphertext,
176
183
  type: deserialized.type,
177
- replyTo: deserialized.replyTo
178
- ? {
179
- originalContent: deserialized.replyTo.originalContent,
180
- originalSeeker: deserialized.replyTo.originalSeeker,
181
- }
182
- : undefined,
183
- forwardOf: deserialized.forwardOf
184
- ? {
185
- originalContent: deserialized.forwardOf.originalContent,
186
- originalSeeker: deserialized.forwardOf.originalSeeker,
187
- }
188
- : undefined,
184
+ replyTo: deserialized.replyTo,
185
+ forwardOf: deserialized.forwardOf,
189
186
  });
190
187
  }
191
188
  catch (deserializationError) {
@@ -219,27 +216,22 @@ export class MessageService {
219
216
  });
220
217
  return null;
221
218
  }
222
- // Check for duplicate message (same content + similar timestamp from same sender)
223
- // This handles edge case: app crashes after network send but before DB update,
224
- // message gets re-sent on restart, peer receives duplicate
225
- const isDuplicate = await this.isDuplicateMessage(ownerUserId, message.senderId, message.content, message.sentAt);
219
+ // If received msg has same messageId as a previously received msg
220
+ const isDuplicate = await this.handleDuplicateMessageId(message, ownerUserId);
226
221
  if (isDuplicate) {
227
- log.info('skipping duplicate message', {
222
+ log.info('Duplicate message received, skipping', {
228
223
  senderId: message.senderId,
229
224
  preview: message.content.slice(0, 30),
230
- timestamp: message.sentAt.toISOString(),
231
225
  });
232
226
  return null;
233
227
  }
234
- let replyToMessageId;
235
- if (message.replyTo?.originalSeeker) {
236
- const original = await this.findMessageBySeeker(message.replyTo.originalSeeker, ownerUserId);
228
+ if (message.replyTo?.originalMsgId) {
229
+ const original = await this.findMessageByMsgId(message.replyTo.originalMsgId, ownerUserId, message.senderId);
237
230
  if (!original) {
238
231
  log.warn('reply target not found', {
239
- originalSeeker: encodeToBase64(message.replyTo.originalSeeker),
232
+ originalMsgId: encodeToBase64(message.replyTo.originalMsgId),
240
233
  });
241
234
  }
242
- replyToMessageId = original?.id;
243
235
  }
244
236
  const id = await this.db.messages.add({
245
237
  ownerUserId,
@@ -250,24 +242,16 @@ export class MessageService {
250
242
  status: MessageStatus.DELIVERED,
251
243
  timestamp: message.sentAt,
252
244
  metadata: {},
253
- seeker: message.seeker, // Store the seeker of the incoming message
245
+ messageId: message.messageId,
254
246
  replyTo: message.replyTo
255
247
  ? {
256
- // Store the original content as a fallback only if we couldn't find
257
- // the original message in the database (replyToMessageId is undefined).
258
- // If the original message exists, we don't need to store the content
259
- // since we can fetch it using the originalSeeker.
260
- originalContent: replyToMessageId
261
- ? undefined
262
- : message.replyTo.originalContent,
263
- // Store the seeker (used to find the original message)
264
- originalSeeker: message.replyTo.originalSeeker,
248
+ originalMsgId: message.replyTo.originalMsgId,
265
249
  }
266
250
  : undefined,
267
251
  forwardOf: message.forwardOf
268
252
  ? {
269
253
  originalContent: message.forwardOf.originalContent,
270
- originalSeeker: message.forwardOf.originalSeeker,
254
+ originalContactId: message.forwardOf.originalContactId,
271
255
  }
272
256
  : undefined,
273
257
  });
@@ -296,45 +280,74 @@ export class MessageService {
296
280
  return storedIds;
297
281
  }
298
282
  /**
299
- * Check if a message is a duplicate based on content and timestamp.
283
+ * Checks for duplicate incoming messages in the message database based on messageId.
300
284
  *
301
- * A message is considered duplicate if:
302
- * - Same sender (contactUserId)
303
- * - Same content
304
- * - Incoming direction
305
- * - Timestamp within deduplication window (default 30 seconds)
285
+ * A message is considered a duplicate if:
286
+ * - It has the same messageId
287
+ * - It is from the same sender (contactUserId)
288
+ * - Belongs to the same conversation (ownerUserId)
306
289
  *
307
- * This handles the edge case where:
308
- * 1. Sender sends message successfully to network
309
- * 2. Sender app crashes before updating DB status to SENT
310
- * 3. On restart, message is reset to WAITING_SESSION and re-sent
311
- * 4. Receiver gets the same message twice with different seekers
290
+ * This protects against scenarios where:
291
+ * 1. A sender successfully delivers a message to the network,
292
+ * 2. Their app crashes or resets before marking the message as SENT in the local DB,
293
+ * 3. On restart, the sender's app re-sends the message (possibly with a different seeker),
294
+ * 4. The receiver gets the same message twice,
295
+ * 5. Duplicates are prevented by matching messageId byte-for-byte.
312
296
  *
313
- * @param ownerUserId - The owner's user ID
314
- * @param contactUserId - The sender's user ID
315
- * @param content - The message content
316
- * @param timestamp - The message timestamp
317
- * @returns true if a duplicate exists
297
+ * This method uses messageId (usually a 12-byte random value) for exact match detection.
298
+ * If a duplicate is found, it updates certain fields (e.g., content, replyTo, forwardOf)
299
+ * on the existing message and returns true.
300
+ *
301
+ * @param message - The incoming decrypted message object.
302
+ * @param ownerUserId - The userId of the message database owner (the recipient).
303
+ * @returns true if a duplicate message (by messageId) was found; otherwise false.
318
304
  */
319
- async isDuplicateMessage(ownerUserId, contactUserId, content, timestamp) {
320
- const windowMs = this.config.messages.deduplicationWindowMs;
321
- const windowStart = new Date(timestamp.getTime() - windowMs);
322
- const windowEnd = new Date(timestamp.getTime() + windowMs);
323
- // Query for messages from same sender with same content within time window
324
- const existing = await this.db.messages
325
- .where('[ownerUserId+contactUserId]')
326
- .equals([ownerUserId, contactUserId])
327
- .and(msg => msg.direction === MessageDirection.INCOMING &&
328
- msg.content === content &&
329
- msg.timestamp >= windowStart &&
330
- msg.timestamp <= windowEnd)
331
- .first();
332
- return existing !== undefined;
305
+ async handleDuplicateMessageId(message, ownerUserId) {
306
+ // Check for duplicate message using messageId (12 bytes random)
307
+ if (message.messageId) {
308
+ const existingWithMessageId = await this.db.messages
309
+ .where('[ownerUserId+contactUserId]')
310
+ .equals([ownerUserId, message.senderId])
311
+ .and(m => {
312
+ if (!m.messageId || !message.messageId)
313
+ return false;
314
+ if (m.messageId.length !== message.messageId.length)
315
+ return false;
316
+ for (let i = 0; i < m.messageId.length; i++) {
317
+ if (m.messageId[i] !== message.messageId[i])
318
+ return false;
319
+ }
320
+ return true;
321
+ })
322
+ .first();
323
+ if (existingWithMessageId) {
324
+ await this.db.messages.update(existingWithMessageId.id, {
325
+ content: message.content,
326
+ replyTo: message.replyTo,
327
+ forwardOf: message.forwardOf,
328
+ });
329
+ return true;
330
+ }
331
+ }
332
+ return false;
333
333
  }
334
- async findMessageBySeeker(seeker, ownerUserId) {
335
- return await this.db.messages
336
- .where('[ownerUserId+seeker]')
337
- .equals([ownerUserId, seeker])
334
+ async findMessageByMsgId(messageId, ownerUserId, contactUserId) {
335
+ const baseQuery = contactUserId
336
+ ? this.db.messages
337
+ .where('[ownerUserId+contactUserId]')
338
+ .equals([ownerUserId, contactUserId])
339
+ : this.db.messages.where('ownerUserId').equals(ownerUserId);
340
+ return await baseQuery
341
+ .and(m => {
342
+ if (!m.messageId || m.messageId.length !== messageId.length) {
343
+ return false;
344
+ }
345
+ for (let i = 0; i < messageId.length; i++) {
346
+ if (m.messageId[i] !== messageId[i])
347
+ return false;
348
+ }
349
+ return true;
350
+ })
338
351
  .first();
339
352
  }
340
353
  async acknowledgeMessages(seekers, userId) {
@@ -349,8 +362,8 @@ export class MessageService {
349
362
  seekers.has(encodeToBase64(message.seeker)))
350
363
  .modify({
351
364
  status: MessageStatus.DELIVERED,
352
- encryptedMessage: undefined,
353
- whenToSend: undefined,
365
+ serializedContent: undefined,
366
+ seeker: undefined,
354
367
  });
355
368
  // After marking as DELIVERED, clean up DELIVERED keep-alive messages
356
369
  await this.db.messages
@@ -393,6 +406,10 @@ export class MessageService {
393
406
  return new Error('Discussion not found');
394
407
  }
395
408
  try {
409
+ const randomMessageId = message.type !== MessageType.KEEP_ALIVE
410
+ ? crypto.getRandomValues(new Uint8Array(MESSAGE_ID_SIZE))
411
+ : undefined;
412
+ message.messageId = randomMessageId;
396
413
  messageId = await this.db.addMessage({
397
414
  ...message,
398
415
  status: MessageStatus.WAITING_SESSION,
@@ -417,8 +434,14 @@ export class MessageService {
417
434
  }
418
435
  async serializeMessage(message) {
419
436
  const log = logger.forMethod('serializeMessage');
420
- if (message.replyTo?.originalSeeker) {
421
- const originalMessage = await this.findMessageBySeeker(message.replyTo.originalSeeker, message.ownerUserId);
437
+ if (!message.messageId && message.type !== MessageType.KEEP_ALIVE) {
438
+ return {
439
+ success: false,
440
+ error: 'Message ID is required',
441
+ };
442
+ }
443
+ if (message.replyTo) {
444
+ const originalMessage = await this.findMessageByMsgId(message.replyTo.originalMsgId, message.ownerUserId);
422
445
  if (!originalMessage) {
423
446
  return {
424
447
  success: false,
@@ -427,7 +450,7 @@ export class MessageService {
427
450
  }
428
451
  return {
429
452
  success: true,
430
- data: serializeReplyMessage(message.content, originalMessage.content, message.replyTo.originalSeeker),
453
+ data: serializeReplyMessage(message.content, message.replyTo.originalMsgId, message.messageId),
431
454
  };
432
455
  }
433
456
  else if (message.type === MessageType.KEEP_ALIVE) {
@@ -436,12 +459,11 @@ export class MessageService {
436
459
  data: serializeKeepAliveMessage(),
437
460
  };
438
461
  }
439
- else if (message.forwardOf?.originalContent &&
440
- message.forwardOf.originalSeeker) {
462
+ else if (message.forwardOf) {
441
463
  try {
442
464
  return {
443
465
  success: true,
444
- data: serializeForwardMessage(message.forwardOf.originalContent, message.content, message.forwardOf.originalSeeker),
466
+ data: serializeForwardMessage(message.forwardOf.originalContent ?? '', message.content, message.messageId, message.forwardOf.originalContactId),
445
467
  };
446
468
  }
447
469
  catch (error) {
@@ -456,7 +478,7 @@ export class MessageService {
456
478
  // Regular message with type tag
457
479
  return {
458
480
  success: true,
459
- data: serializeRegularMessage(message.content),
481
+ data: serializeRegularMessage(message.content, message.messageId),
460
482
  };
461
483
  }
462
484
  }
@@ -633,6 +655,8 @@ export class MessageService {
633
655
  ) {
634
656
  await this.db.messages.update(msg.id, {
635
657
  status: MessageStatus.SENT,
658
+ encryptedMessage: undefined,
659
+ whenToSend: undefined,
636
660
  });
637
661
  return true;
638
662
  }
@@ -0,0 +1,8 @@
1
+ export * from './announcementPayload';
2
+ export * from './base64';
3
+ export * from './contacts';
4
+ export * from './discussions';
5
+ export * from './queue';
6
+ export * from './type';
7
+ export * from './userId';
8
+ export * from './validation';
@@ -0,0 +1,8 @@
1
+ export * from './announcementPayload';
2
+ export * from './base64';
3
+ export * from './contacts';
4
+ export * from './discussions';
5
+ export * from './queue';
6
+ export * from './type';
7
+ export * from './userId';
8
+ export * from './validation';
@@ -5,16 +5,17 @@
5
5
  * Supports regular text messages, replies, forwards, and keep-alive messages.
6
6
  */
7
7
  import { MessageType } from '../db';
8
- export declare const MESSAGE_TYPE_KEEP_ALIVE = 3;
8
+ import { MessageType as ProtoMessageType } from '../proto/generated/message';
9
+ export declare const MESSAGE_TYPE_KEEP_ALIVE = ProtoMessageType.MESSAGE_TYPE_KEEP_ALIVE;
9
10
  export interface DeserializedMessage {
10
11
  content: string;
12
+ messageId?: Uint8Array;
11
13
  replyTo?: {
12
- originalContent: string;
13
- originalSeeker: Uint8Array;
14
+ originalMsgId: Uint8Array;
14
15
  };
15
16
  forwardOf?: {
16
17
  originalContent: string;
17
- originalSeeker: Uint8Array;
18
+ originalContactId?: Uint8Array;
18
19
  };
19
20
  type: MessageType;
20
21
  }
@@ -26,34 +27,36 @@ export declare function serializeKeepAliveMessage(): Uint8Array;
26
27
  /**
27
28
  * Serialize a regular text message
28
29
  *
29
- * Format: [type: 1 byte][content: variable]
30
+ * Format: [type: 1 byte][messageId: 12 bytes][content: variable]
30
31
  *
31
32
  * @param content - The message content string
33
+ * @param messageId - 12-byte random message ID
32
34
  * @returns Serialized message bytes
33
35
  */
34
- export declare function serializeRegularMessage(content: string): Uint8Array;
36
+ export declare function serializeRegularMessage(content: string, messageId: Uint8Array): Uint8Array;
35
37
  /**
36
38
  * Serialize a reply message
37
39
  *
38
- * Format: [type: 1 byte][originalContentLen: 4 bytes][originalContent][seeker: 34 bytes][newContent]
40
+ * Format: protobuf Message with citedMsgId set
39
41
  *
40
42
  * @param newContent - The reply content
41
- * @param originalContent - The content being replied to
42
- * @param originalSeeker - The seeker of the original message
43
+ * @param originalMsgId - The messageId of the message being replied to
44
+ * @param messageId - 12-byte random message ID
43
45
  * @returns Serialized reply message bytes
44
46
  */
45
- export declare function serializeReplyMessage(newContent: string, originalContent: string, originalSeeker: Uint8Array): Uint8Array;
47
+ export declare function serializeReplyMessage(newContent: string, originalMsgId: Uint8Array, messageId: Uint8Array): Uint8Array;
46
48
  /**
47
49
  * Serialize a forward message
48
50
  *
49
- * Format: [type: 1 byte][forwardContentLen: 4 bytes][forwardContent][seeker: 34 bytes][newContent]
51
+ * Format: protobuf Message with citedContactId set
50
52
  *
51
- * @param forwardContent - The content being forwarded
53
+ * @param forwardedContent - The content being forwarded
52
54
  * @param newContent - Optional new content to add (empty string if none)
53
- * @param originalSeeker - The seeker of the original message
55
+ * @param originalContactId - The contact ID (32 bytes) of the original message
56
+ * @param messageId - 12-byte random message ID
54
57
  * @returns Serialized forward message bytes
55
58
  */
56
- export declare function serializeForwardMessage(forwardContent: string, newContent: string, originalSeeker: Uint8Array): Uint8Array;
59
+ export declare function serializeForwardMessage(forwardedContent: string, newContent: string, messageId: Uint8Array, originalContactId?: Uint8Array): Uint8Array;
57
60
  /**
58
61
  * Deserialize a message from bytes
59
62
  *
@@ -4,110 +4,87 @@
4
4
  * Functions for serializing and deserializing messages for the protocol.
5
5
  * Supports regular text messages, replies, forwards, and keep-alive messages.
6
6
  */
7
- import { strToBytes, bytesToStr, U32 } from '@massalabs/massa-web3';
8
- import { MessageType } from '../db';
9
- // Message type constants (protocol-level)
10
- const MESSAGE_TYPE_REGULAR = 0x00;
11
- const MESSAGE_TYPE_REPLY = 0x01;
12
- const MESSAGE_TYPE_FORWARD = 0x02;
13
- export const MESSAGE_TYPE_KEEP_ALIVE = 0x03;
14
- // Seeker size: 1 byte length prefix + 32 bytes hash + 1 byte key index
15
- const SEEKER_SIZE = 34;
7
+ import { MessageType, MESSAGE_ID_SIZE } from '../db';
8
+ import { Message as ProtoMessage, MessageType as ProtoMessageType, } from '../proto/generated/message';
9
+ export const MESSAGE_TYPE_KEEP_ALIVE = ProtoMessageType.MESSAGE_TYPE_KEEP_ALIVE;
16
10
  /**
17
11
  * Serialize a keep-alive message
18
12
  * Keep-alive messages are used to maintain session activity
19
13
  */
20
14
  export function serializeKeepAliveMessage() {
21
- return new Uint8Array([MESSAGE_TYPE_KEEP_ALIVE]);
15
+ return ProtoMessage.encode({
16
+ messageType: ProtoMessageType.MESSAGE_TYPE_KEEP_ALIVE,
17
+ content: '',
18
+ });
22
19
  }
23
20
  /**
24
21
  * Serialize a regular text message
25
22
  *
26
- * Format: [type: 1 byte][content: variable]
23
+ * Format: [type: 1 byte][messageId: 12 bytes][content: variable]
27
24
  *
28
25
  * @param content - The message content string
26
+ * @param messageId - 12-byte random message ID
29
27
  * @returns Serialized message bytes
30
28
  */
31
- export function serializeRegularMessage(content) {
32
- const contentBytes = strToBytes(content);
33
- const result = new Uint8Array(1 + contentBytes.length);
34
- result[0] = MESSAGE_TYPE_REGULAR;
35
- result.set(contentBytes, 1);
36
- return result;
29
+ export function serializeRegularMessage(content, messageId) {
30
+ if (messageId.length !== MESSAGE_ID_SIZE) {
31
+ throw new Error(`messageId must be ${MESSAGE_ID_SIZE} bytes`);
32
+ }
33
+ return ProtoMessage.encode({
34
+ messageType: ProtoMessageType.MESSAGE_TYPE_REGULAR,
35
+ messageId,
36
+ content,
37
+ });
37
38
  }
38
39
  /**
39
40
  * Serialize a reply message
40
41
  *
41
- * Format: [type: 1 byte][originalContentLen: 4 bytes][originalContent][seeker: 34 bytes][newContent]
42
+ * Format: protobuf Message with citedMsgId set
42
43
  *
43
44
  * @param newContent - The reply content
44
- * @param originalContent - The content being replied to
45
- * @param originalSeeker - The seeker of the original message
45
+ * @param originalMsgId - The messageId of the message being replied to
46
+ * @param messageId - 12-byte random message ID
46
47
  * @returns Serialized reply message bytes
47
48
  */
48
- export function serializeReplyMessage(newContent, originalContent, originalSeeker) {
49
- const newContentBytes = strToBytes(newContent);
50
- const originalContentBytes = strToBytes(originalContent);
51
- const originalContentLenBytes = U32.toBytes(BigInt(originalContentBytes.length));
52
- // Calculate total size
53
- const totalSize = 1 + // type
54
- originalContentLenBytes.length + // length prefix (4 bytes)
55
- originalContentBytes.length + // original content
56
- SEEKER_SIZE + // seeker
57
- newContentBytes.length; // new content
58
- const result = new Uint8Array(totalSize);
59
- let offset = 0;
60
- // Type byte
61
- result[offset++] = MESSAGE_TYPE_REPLY;
62
- // Original content length (4 bytes)
63
- result.set(originalContentLenBytes, offset);
64
- offset += originalContentLenBytes.length;
65
- // Original content
66
- result.set(originalContentBytes, offset);
67
- offset += originalContentBytes.length;
68
- // Seeker (34 bytes)
69
- result.set(originalSeeker, offset);
70
- offset += SEEKER_SIZE;
71
- // New content
72
- result.set(newContentBytes, offset);
73
- return result;
49
+ export function serializeReplyMessage(newContent, originalMsgId, messageId) {
50
+ if (messageId.length !== MESSAGE_ID_SIZE) {
51
+ throw new Error(`messageId must be ${MESSAGE_ID_SIZE} bytes`);
52
+ }
53
+ if (originalMsgId.length !== MESSAGE_ID_SIZE) {
54
+ throw new Error(`originalMsgId must be ${MESSAGE_ID_SIZE} bytes`);
55
+ }
56
+ return ProtoMessage.encode({
57
+ messageType: ProtoMessageType.MESSAGE_TYPE_REPLY,
58
+ messageId,
59
+ content: newContent,
60
+ citedMsgId: originalMsgId,
61
+ });
74
62
  }
75
63
  /**
76
64
  * Serialize a forward message
77
65
  *
78
- * Format: [type: 1 byte][forwardContentLen: 4 bytes][forwardContent][seeker: 34 bytes][newContent]
66
+ * Format: protobuf Message with citedContactId set
79
67
  *
80
- * @param forwardContent - The content being forwarded
68
+ * @param forwardedContent - The content being forwarded
81
69
  * @param newContent - Optional new content to add (empty string if none)
82
- * @param originalSeeker - The seeker of the original message
70
+ * @param originalContactId - The contact ID (32 bytes) of the original message
71
+ * @param messageId - 12-byte random message ID
83
72
  * @returns Serialized forward message bytes
84
73
  */
85
- export function serializeForwardMessage(forwardContent, newContent, originalSeeker) {
86
- const newContentBytes = strToBytes(newContent);
87
- const forwardContentBytes = strToBytes(forwardContent);
88
- const forwardContentLenBytes = U32.toBytes(BigInt(forwardContentBytes.length));
89
- // Calculate total size
90
- const totalSize = 1 + // type
91
- forwardContentLenBytes.length + // length prefix (4 bytes)
92
- forwardContentBytes.length + // forward content
93
- SEEKER_SIZE + // seeker
94
- newContentBytes.length; // new content
95
- const result = new Uint8Array(totalSize);
96
- let offset = 0;
97
- // Type byte
98
- result[offset++] = MESSAGE_TYPE_FORWARD;
99
- // Forward content length (4 bytes)
100
- result.set(forwardContentLenBytes, offset);
101
- offset += forwardContentLenBytes.length;
102
- // Forward content
103
- result.set(forwardContentBytes, offset);
104
- offset += forwardContentBytes.length;
105
- // Seeker (34 bytes)
106
- result.set(originalSeeker, offset);
107
- offset += SEEKER_SIZE;
108
- // New content
109
- result.set(newContentBytes, offset);
110
- return result;
74
+ export function serializeForwardMessage(forwardedContent, newContent, messageId, originalContactId) {
75
+ if (messageId.length !== MESSAGE_ID_SIZE) {
76
+ throw new Error(`messageId must be ${MESSAGE_ID_SIZE} bytes`);
77
+ }
78
+ if (originalContactId && originalContactId.length !== 32) {
79
+ throw new Error('originalContactId must be 32 bytes');
80
+ }
81
+ return ProtoMessage.encode({
82
+ messageType: ProtoMessageType.MESSAGE_TYPE_FORWARD,
83
+ messageId,
84
+ content: newContent,
85
+ citedContactId: originalContactId,
86
+ forwardedContent,
87
+ });
111
88
  }
112
89
  /**
113
90
  * Deserialize a message from bytes
@@ -117,68 +94,50 @@ export function serializeForwardMessage(forwardContent, newContent, originalSeek
117
94
  * @throws Error if message format is invalid
118
95
  */
119
96
  export function deserializeMessage(buffer) {
120
- if (buffer.length < 1) {
97
+ if (buffer.length === 0) {
121
98
  throw new Error('Empty message buffer');
122
99
  }
123
- const messageType = buffer[0];
124
- switch (messageType) {
125
- case MESSAGE_TYPE_KEEP_ALIVE:
126
- return {
127
- content: '',
128
- type: MessageType.KEEP_ALIVE,
129
- };
130
- case MESSAGE_TYPE_REGULAR:
131
- return {
132
- content: bytesToStr(buffer.slice(1)),
133
- type: MessageType.TEXT,
134
- };
135
- case MESSAGE_TYPE_REPLY: {
136
- // Format: [type: 1][originalContentLen: 4][originalContent][seeker: 34][newContent]
137
- let offset = 1;
138
- // Read original content length (4 bytes)
139
- const originalContentLen = Number(U32.fromBytes(buffer.slice(offset, offset + 4)));
140
- offset += 4;
141
- // Read original content
142
- const originalContent = bytesToStr(buffer.slice(offset, offset + originalContentLen));
143
- offset += originalContentLen;
144
- // Read seeker (34 bytes)
145
- const originalSeeker = buffer.slice(offset, offset + SEEKER_SIZE);
146
- offset += SEEKER_SIZE;
147
- // Read new content (rest of buffer)
148
- const content = bytesToStr(buffer.slice(offset));
149
- return {
150
- content,
151
- replyTo: {
152
- originalContent,
153
- originalSeeker,
154
- },
155
- type: MessageType.TEXT,
100
+ const decoded = ProtoMessage.decode(buffer);
101
+ const protoType = decoded.messageType ?? ProtoMessageType.MESSAGE_TYPE_REGULAR;
102
+ if (protoType === ProtoMessageType.MESSAGE_TYPE_KEEP_ALIVE) {
103
+ return {
104
+ content: '',
105
+ type: MessageType.KEEP_ALIVE,
106
+ };
107
+ }
108
+ const content = decoded.content ?? '';
109
+ const messageId = decoded.messageId;
110
+ const citedMsgId = decoded.citedMsgId;
111
+ let replyTo = undefined;
112
+ if (protoType === ProtoMessageType.MESSAGE_TYPE_REPLY) {
113
+ if (citedMsgId && citedMsgId.length === MESSAGE_ID_SIZE) {
114
+ replyTo = {
115
+ originalMsgId: citedMsgId,
156
116
  };
157
117
  }
158
- case MESSAGE_TYPE_FORWARD: {
159
- // Format: [type: 1][forwardContentLen: 4][forwardContent][seeker: 34][newContent]
160
- let offset = 1;
161
- // Read forward content length (4 bytes)
162
- const forwardContentLen = Number(U32.fromBytes(buffer.slice(offset, offset + 4)));
163
- offset += 4;
164
- // Read forward content
165
- const originalContent = bytesToStr(buffer.slice(offset, offset + forwardContentLen));
166
- offset += forwardContentLen;
167
- // Read seeker (34 bytes)
168
- const originalSeeker = buffer.slice(offset, offset + SEEKER_SIZE);
169
- offset += SEEKER_SIZE;
170
- // Read new content (rest of buffer)
171
- const content = bytesToStr(buffer.slice(offset));
172
- return {
173
- content,
174
- forwardOf: {
175
- originalContent,
176
- originalSeeker,
177
- },
178
- type: MessageType.TEXT,
118
+ else {
119
+ throw new Error(`invalid message format: message of type reply but citedMsgId empty or not correct size (${MESSAGE_ID_SIZE})`);
120
+ }
121
+ }
122
+ let forwardOf = undefined;
123
+ if (protoType === ProtoMessageType.MESSAGE_TYPE_FORWARD) {
124
+ if (decoded.forwardedContent &&
125
+ decoded.citedContactId &&
126
+ decoded.citedContactId.length === 32) {
127
+ forwardOf = {
128
+ originalContent: decoded.forwardedContent,
129
+ originalContactId: decoded.citedContactId,
179
130
  };
180
131
  }
181
- default:
182
- throw new Error(`Unknown message type: ${messageType}`);
132
+ else {
133
+ throw new Error(`invalid message format: message of type forward but forwardedContent empty or not correct size (${MESSAGE_ID_SIZE}) or citedContactId empty or not correct size (32)`);
134
+ }
183
135
  }
136
+ return {
137
+ content,
138
+ messageId,
139
+ replyTo,
140
+ forwardOf,
141
+ type: MessageType.TEXT,
142
+ };
184
143
  }
@@ -8,3 +8,4 @@ export { SessionModule, sessionStatusToString } from './session';
8
8
  export { initializeWasm, ensureWasmInitialized, startWasmInitialization, } from './loader';
9
9
  export * from './encryption';
10
10
  export * from './userKeys';
11
+ export { SessionStatus, UserPublicKeys, UserSecretKeys, SendMessageOutput, ReceiveMessageOutput, AnnouncementResult, } from './bindings';
@@ -11,3 +11,4 @@ export { initializeWasm, ensureWasmInitialized, startWasmInitialization, } from
11
11
  // Export specialized WASM functionality
12
12
  export * from './encryption';
13
13
  export * from './userKeys';
14
+ export { SessionStatus, UserPublicKeys, UserSecretKeys, SendMessageOutput, ReceiveMessageOutput, AnnouncementResult, } from './bindings';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@massalabs/gossip-sdk",
3
- "version": "0.0.2-dev.20260212071538",
3
+ "version": "0.0.2-dev.20260217093203",
4
4
  "description": "Gossip SDK for automation, chatbot, and integration use cases",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",