@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 +2 -2
- package/dist/api/index.d.ts +1 -0
- package/dist/api/index.js +1 -0
- package/dist/api/messageProtocol/index.d.ts +2 -4
- package/dist/api/messageProtocol/index.js +4 -15
- package/dist/crypto/index.d.ts +2 -0
- package/dist/crypto/index.js +2 -0
- package/dist/db.d.ts +4 -3
- package/dist/db.js +2 -0
- package/dist/{gossipSdk.d.ts → gossip.d.ts} +2 -2
- package/dist/{gossipSdk.js → gossip.js} +1 -4
- package/dist/index.d.ts +5 -17
- package/dist/index.js +5 -31
- package/dist/proto/generated/message.d.ts +18 -0
- package/dist/proto/generated/message.js +141 -0
- package/dist/services/auth.d.ts +1 -8
- package/dist/services/auth.js +2 -6
- package/dist/services/message.d.ts +20 -18
- package/dist/services/message.js +102 -78
- package/dist/utils/index.d.ts +8 -0
- package/dist/utils/index.js +8 -0
- package/dist/utils/messageSerialization.d.ts +17 -14
- package/dist/utils/messageSerialization.js +91 -132
- package/dist/wasm/index.d.ts +1 -0
- package/dist/wasm/index.js +1 -0
- package/package.json +1 -1
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
|
|
129
|
-
const msg = await
|
|
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(
|
|
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
|
-
|
|
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(
|
|
15
|
-
|
|
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';
|
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
|
-
|
|
36
|
-
originalSeeker: Uint8Array;
|
|
37
|
+
originalMsgId: Uint8Array;
|
|
37
38
|
};
|
|
38
39
|
forwardOf?: {
|
|
39
40
|
originalContent?: string;
|
|
40
|
-
|
|
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
|
|
178
|
-
|
|
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
|
-
|
|
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
|
|
16
|
-
export
|
|
15
|
+
export * from './api';
|
|
16
|
+
export * from './crypto';
|
|
17
17
|
export * from './db';
|
|
18
|
-
export * from './
|
|
19
|
-
export * from './utils
|
|
20
|
-
export * from './
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
};
|
package/dist/services/auth.d.ts
CHANGED
|
@@ -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<
|
|
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.
|
package/dist/services/auth.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
*
|
|
38
|
+
* Checks for duplicate incoming messages in the message database based on messageId.
|
|
39
39
|
*
|
|
40
|
-
* A message is considered duplicate if:
|
|
41
|
-
* -
|
|
42
|
-
* -
|
|
43
|
-
* -
|
|
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
|
|
47
|
-
* 1.
|
|
48
|
-
* 2.
|
|
49
|
-
* 3. On restart, message
|
|
50
|
-
* 4.
|
|
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
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
* @
|
|
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
|
|
59
|
-
|
|
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;
|
package/dist/services/message.js
CHANGED
|
@@ -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
|
-
//
|
|
223
|
-
|
|
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('
|
|
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
|
-
|
|
235
|
-
|
|
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
|
-
|
|
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
|
-
|
|
245
|
+
messageId: message.messageId,
|
|
254
246
|
replyTo: message.replyTo
|
|
255
247
|
? {
|
|
256
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
283
|
+
* Checks for duplicate incoming messages in the message database based on messageId.
|
|
300
284
|
*
|
|
301
|
-
* A message is considered duplicate if:
|
|
302
|
-
* -
|
|
303
|
-
* -
|
|
304
|
-
* -
|
|
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
|
|
308
|
-
* 1.
|
|
309
|
-
* 2.
|
|
310
|
-
* 3. On restart, message
|
|
311
|
-
* 4.
|
|
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
|
-
*
|
|
314
|
-
*
|
|
315
|
-
*
|
|
316
|
-
*
|
|
317
|
-
* @
|
|
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
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
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
|
|
335
|
-
|
|
336
|
-
.
|
|
337
|
-
|
|
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
|
-
|
|
353
|
-
|
|
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.
|
|
421
|
-
|
|
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,
|
|
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
|
|
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.
|
|
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
|
}
|
|
@@ -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
|
-
|
|
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
|
-
|
|
13
|
-
originalSeeker: Uint8Array;
|
|
14
|
+
originalMsgId: Uint8Array;
|
|
14
15
|
};
|
|
15
16
|
forwardOf?: {
|
|
16
17
|
originalContent: string;
|
|
17
|
-
|
|
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:
|
|
40
|
+
* Format: protobuf Message with citedMsgId set
|
|
39
41
|
*
|
|
40
42
|
* @param newContent - The reply content
|
|
41
|
-
* @param
|
|
42
|
-
* @param
|
|
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,
|
|
47
|
+
export declare function serializeReplyMessage(newContent: string, originalMsgId: Uint8Array, messageId: Uint8Array): Uint8Array;
|
|
46
48
|
/**
|
|
47
49
|
* Serialize a forward message
|
|
48
50
|
*
|
|
49
|
-
* Format:
|
|
51
|
+
* Format: protobuf Message with citedContactId set
|
|
50
52
|
*
|
|
51
|
-
* @param
|
|
53
|
+
* @param forwardedContent - The content being forwarded
|
|
52
54
|
* @param newContent - Optional new content to add (empty string if none)
|
|
53
|
-
* @param
|
|
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(
|
|
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 {
|
|
8
|
-
import { MessageType } from '../
|
|
9
|
-
|
|
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
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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:
|
|
42
|
+
* Format: protobuf Message with citedMsgId set
|
|
42
43
|
*
|
|
43
44
|
* @param newContent - The reply content
|
|
44
|
-
* @param
|
|
45
|
-
* @param
|
|
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,
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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:
|
|
66
|
+
* Format: protobuf Message with citedContactId set
|
|
79
67
|
*
|
|
80
|
-
* @param
|
|
68
|
+
* @param forwardedContent - The content being forwarded
|
|
81
69
|
* @param newContent - Optional new content to add (empty string if none)
|
|
82
|
-
* @param
|
|
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(
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
|
97
|
+
if (buffer.length === 0) {
|
|
121
98
|
throw new Error('Empty message buffer');
|
|
122
99
|
}
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
-
|
|
182
|
-
throw new Error(`
|
|
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
|
}
|
package/dist/wasm/index.d.ts
CHANGED
|
@@ -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';
|
package/dist/wasm/index.js
CHANGED
|
@@ -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