@massalabs/gossip-sdk 0.0.2-dev.20260317085237 → 0.0.2-dev.20260318110914
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/db/db.d.ts +5 -1
- package/dist/db/db.js +1 -0
- package/dist/db/generated-migrations.js +6 -0
- package/dist/db/queries/messages.d.ts +1 -0
- package/dist/db/queries/messages.js +10 -0
- package/dist/db/schema/messages.d.ts +19 -0
- package/dist/db/schema/messages.js +1 -0
- package/dist/proto/generated/message.d.ts +2 -1
- package/dist/proto/generated/message.js +1 -0
- package/dist/services/message.d.ts +3 -0
- package/dist/services/message.js +82 -2
- package/dist/utils/messageSerialization.d.ts +4 -0
- package/dist/utils/messageSerialization.js +29 -1
- package/package.json +1 -1
package/dist/db/db.d.ts
CHANGED
|
@@ -45,6 +45,9 @@ export interface Message {
|
|
|
45
45
|
editOf?: {
|
|
46
46
|
originalMsgId: Uint8Array;
|
|
47
47
|
};
|
|
48
|
+
reactionOf?: {
|
|
49
|
+
originalMsgId: Uint8Array;
|
|
50
|
+
};
|
|
48
51
|
}
|
|
49
52
|
export interface UserProfile {
|
|
50
53
|
userId: string;
|
|
@@ -96,7 +99,8 @@ export declare enum MessageType {
|
|
|
96
99
|
FILE = "file",
|
|
97
100
|
AUDIO = "audio",
|
|
98
101
|
VIDEO = "video",
|
|
99
|
-
DELETED = "deleted"
|
|
102
|
+
DELETED = "deleted",
|
|
103
|
+
REACTION = "reaction"
|
|
100
104
|
}
|
|
101
105
|
export interface ReadyAnnouncement {
|
|
102
106
|
announcement_bytes: Uint8Array;
|
package/dist/db/db.js
CHANGED
|
@@ -37,6 +37,7 @@ export var MessageType;
|
|
|
37
37
|
MessageType["AUDIO"] = "audio";
|
|
38
38
|
MessageType["VIDEO"] = "video";
|
|
39
39
|
MessageType["DELETED"] = "deleted";
|
|
40
|
+
MessageType["REACTION"] = "reaction";
|
|
40
41
|
})(MessageType || (MessageType = {}));
|
|
41
42
|
/** Serialize a SendAnnouncement to a JSON string for SQLite text column */
|
|
42
43
|
export function serializeSendAnnouncement(announcement) {
|
|
@@ -48,4 +48,10 @@ export const MIGRATIONS = [
|
|
|
48
48
|
'ALTER TABLE `discussions` ADD COLUMN `pinned` integer NOT NULL DEFAULT 0;',
|
|
49
49
|
],
|
|
50
50
|
},
|
|
51
|
+
{
|
|
52
|
+
idx: 3,
|
|
53
|
+
tag: '0003_messages_reaction_of',
|
|
54
|
+
when: 1742000000000,
|
|
55
|
+
statements: ['ALTER TABLE `messages` ADD COLUMN `reactionOf` text;'],
|
|
56
|
+
},
|
|
51
57
|
];
|
|
@@ -9,6 +9,7 @@ export declare class MessageQueries {
|
|
|
9
9
|
getById(id: number): Promise<MessageRow | undefined>;
|
|
10
10
|
getByOwnerAndContact(ownerUserId: string, contactUserId: string): Promise<MessageRow[]>;
|
|
11
11
|
getVisibleByOwnerAndContact(ownerUserId: string, contactUserId: string): Promise<MessageRow[]>;
|
|
12
|
+
getReactionsByOwnerAndContact(ownerUserId: string, contactUserId: string): Promise<MessageRow[]>;
|
|
12
13
|
getByOwnerAndSeeker(ownerUserId: string, seeker: Uint8Array): Promise<MessageRow | undefined>;
|
|
13
14
|
findByMessageId(ownerUserId: string, contactUserId: string, messageId: Uint8Array): Promise<MessageRow | undefined>;
|
|
14
15
|
insert(values: MessageInsert): Promise<number>;
|
|
@@ -32,6 +32,8 @@ export class MessageQueries {
|
|
|
32
32
|
.where(and(eq(schema.messages.ownerUserId, ownerUserId), eq(schema.messages.contactUserId, contactUserId),
|
|
33
33
|
// Hide keep-alive messages from UI
|
|
34
34
|
ne(schema.messages.type, MessageType.KEEP_ALIVE),
|
|
35
|
+
// Hide reaction messages (and any deleted reaction rows) from main message list
|
|
36
|
+
ne(schema.messages.type, MessageType.REACTION), sql `reactionOf IS NULL`,
|
|
35
37
|
// Hide delete control messages (outgoing DELETED with empty content)
|
|
36
38
|
or(ne(schema.messages.type, MessageType.DELETED), ne(schema.messages.direction, MessageDirection.OUTGOING), ne(schema.messages.content, '')),
|
|
37
39
|
// Hide edit control messages tagged via metadata.control === 'edit'
|
|
@@ -39,6 +41,14 @@ export class MessageQueries {
|
|
|
39
41
|
.orderBy(asc(schema.messages.id))
|
|
40
42
|
.all();
|
|
41
43
|
}
|
|
44
|
+
async getReactionsByOwnerAndContact(ownerUserId, contactUserId) {
|
|
45
|
+
return this.conn.db
|
|
46
|
+
.select()
|
|
47
|
+
.from(schema.messages)
|
|
48
|
+
.where(and(eq(schema.messages.ownerUserId, ownerUserId), eq(schema.messages.contactUserId, contactUserId), eq(schema.messages.type, MessageType.REACTION)))
|
|
49
|
+
.orderBy(asc(schema.messages.timestamp), asc(schema.messages.id))
|
|
50
|
+
.all();
|
|
51
|
+
}
|
|
42
52
|
async getByOwnerAndSeeker(ownerUserId, seeker) {
|
|
43
53
|
return this.conn.db
|
|
44
54
|
.select()
|
|
@@ -306,6 +306,25 @@ export declare const messages: import("drizzle-orm/sqlite-core").SQLiteTableWith
|
|
|
306
306
|
}, {}, {
|
|
307
307
|
length: number | undefined;
|
|
308
308
|
}>;
|
|
309
|
+
reactionOf: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
310
|
+
name: "reactionOf";
|
|
311
|
+
tableName: "messages";
|
|
312
|
+
dataType: "string";
|
|
313
|
+
columnType: "SQLiteText";
|
|
314
|
+
data: string;
|
|
315
|
+
driverParam: string;
|
|
316
|
+
notNull: false;
|
|
317
|
+
hasDefault: false;
|
|
318
|
+
isPrimaryKey: false;
|
|
319
|
+
isAutoincrement: false;
|
|
320
|
+
hasRuntimeDefault: false;
|
|
321
|
+
enumValues: [string, ...string[]];
|
|
322
|
+
baseColumn: never;
|
|
323
|
+
identity: undefined;
|
|
324
|
+
generated: undefined;
|
|
325
|
+
}, {}, {
|
|
326
|
+
length: number | undefined;
|
|
327
|
+
}>;
|
|
309
328
|
encryptedMessage: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
310
329
|
name: "encryptedMessage";
|
|
311
330
|
tableName: "messages";
|
|
@@ -17,6 +17,7 @@ export const messages = sqliteTable('messages', {
|
|
|
17
17
|
forwardOf: text('forwardOf'),
|
|
18
18
|
deleteOf: text('deleteOf'),
|
|
19
19
|
editOf: text('editOf'),
|
|
20
|
+
reactionOf: text('reactionOf'),
|
|
20
21
|
encryptedMessage: bytes('encryptedMessage'),
|
|
21
22
|
whenToSend: integer('whenToSend', { mode: 'timestamp_ms' }),
|
|
22
23
|
}, table => [
|
|
@@ -8,6 +8,7 @@ export var MessageType;
|
|
|
8
8
|
MessageType[MessageType["MESSAGE_TYPE_KEEP_ALIVE"] = 3] = "MESSAGE_TYPE_KEEP_ALIVE";
|
|
9
9
|
MessageType[MessageType["MESSAGE_TYPE_DELETE"] = 4] = "MESSAGE_TYPE_DELETE";
|
|
10
10
|
MessageType[MessageType["MESSAGE_TYPE_EDIT"] = 5] = "MESSAGE_TYPE_EDIT";
|
|
11
|
+
MessageType[MessageType["MESSAGE_TYPE_REACTION"] = 6] = "MESSAGE_TYPE_REACTION";
|
|
11
12
|
})(MessageType || (MessageType = {}));
|
|
12
13
|
const textEncoder = new TextEncoder();
|
|
13
14
|
const textDecoder = new TextDecoder();
|
|
@@ -87,6 +87,8 @@ export declare class MessageService {
|
|
|
87
87
|
getMessages(contactUserId: string): Promise<Message[]>;
|
|
88
88
|
/** Get only user-visible messages for a contact (filtered + ordered). */
|
|
89
89
|
getVisibleMessages(contactUserId: string): Promise<Message[]>;
|
|
90
|
+
/** Get all non-deleted reaction messages for a contact. */
|
|
91
|
+
getReactions(contactUserId: string): Promise<Message[]>;
|
|
90
92
|
/** Send a message, queued via QueueManager if available */
|
|
91
93
|
send(message: Omit<Message, 'id'>): Promise<SendMessageResult>;
|
|
92
94
|
/**
|
|
@@ -102,6 +104,7 @@ export declare class MessageService {
|
|
|
102
104
|
* so the peer can mark their copy as deleted as well.
|
|
103
105
|
*/
|
|
104
106
|
deleteMessage(id: number): Promise<boolean>;
|
|
107
|
+
sendReaction(contactUserId: string, emoji: string, originalMsgId: Uint8Array): Promise<SendMessageResult>;
|
|
105
108
|
/**
|
|
106
109
|
* Edit an outgoing message by its database ID.
|
|
107
110
|
* Updates the local content (preserving timestamp) and enqueues an edit
|
package/dist/services/message.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import { MessageDirection, MessageStatus, MessageType, MESSAGE_ID_SIZE, } from '../db/index.js';
|
|
8
8
|
import { decodeUserId, encodeUserId } from '../utils/userId.js';
|
|
9
9
|
import { SessionStatus } from '../wasm/bindings.js';
|
|
10
|
-
import { serializeRegularMessage, serializeReplyMessage, serializeForwardMessage, serializeKeepAliveMessage, serializeDeleteMessage, serializeEditMessage, deserializeMessage, } from '../utils/messageSerialization.js';
|
|
10
|
+
import { serializeRegularMessage, serializeReplyMessage, serializeForwardMessage, serializeKeepAliveMessage, serializeDeleteMessage, serializeEditMessage, serializeReactionMessage, deserializeMessage, } from '../utils/messageSerialization.js';
|
|
11
11
|
import { encodeToBase64, decodeFromBase64 } from '../utils/base64.js';
|
|
12
12
|
import { sessionStatusToString } from '../wasm/session.js';
|
|
13
13
|
import { Logger } from '../utils/logs.js';
|
|
@@ -89,6 +89,21 @@ function deserializeEditOf(json) {
|
|
|
89
89
|
originalMsgId: decodeFromBase64(parsed.originalMsgId),
|
|
90
90
|
};
|
|
91
91
|
}
|
|
92
|
+
function serializeReactionOf(reactionOf) {
|
|
93
|
+
if (!reactionOf)
|
|
94
|
+
return null;
|
|
95
|
+
return JSON.stringify({
|
|
96
|
+
originalMsgId: encodeToBase64(reactionOf.originalMsgId),
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
function deserializeReactionOf(json) {
|
|
100
|
+
if (!json)
|
|
101
|
+
return undefined;
|
|
102
|
+
const parsed = JSON.parse(json);
|
|
103
|
+
return {
|
|
104
|
+
originalMsgId: decodeFromBase64(parsed.originalMsgId),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
92
107
|
/** Serialize metadata to JSON string. */
|
|
93
108
|
function serializeMetadata(metadata) {
|
|
94
109
|
if (!metadata)
|
|
@@ -120,6 +135,7 @@ export function rowToMessage(row) {
|
|
|
120
135
|
forwardOf: deserializeForwardOf(row.forwardOf),
|
|
121
136
|
deleteOf: deserializeDeleteOf(row.deleteOf ?? null),
|
|
122
137
|
editOf: deserializeEditOf(row.editOf ?? null),
|
|
138
|
+
reactionOf: deserializeReactionOf(row.reactionOf ?? null),
|
|
123
139
|
encryptedMessage: row.encryptedMessage ?? undefined,
|
|
124
140
|
whenToSend: row.whenToSend ?? undefined,
|
|
125
141
|
};
|
|
@@ -293,11 +309,14 @@ export class MessageService {
|
|
|
293
309
|
forwardOf: serializeForwardOf(message.forwardOf),
|
|
294
310
|
deleteOf: serializeDeleteOf(message.deleteOf),
|
|
295
311
|
editOf: serializeEditOf(message.editOf),
|
|
312
|
+
reactionOf: serializeReactionOf(message.reactionOf),
|
|
296
313
|
encryptedMessage: message.encryptedMessage,
|
|
297
314
|
whenToSend: message.whenToSend,
|
|
298
315
|
});
|
|
299
316
|
const discussion = await this.queries.discussions.getByOwnerAndContact(message.ownerUserId, message.contactUserId);
|
|
300
|
-
if (discussion &&
|
|
317
|
+
if (discussion &&
|
|
318
|
+
message.type !== MessageType.KEEP_ALIVE &&
|
|
319
|
+
message.type !== MessageType.REACTION) {
|
|
301
320
|
await this.queries.discussions.updateById(discussion.id, {
|
|
302
321
|
lastMessageId: messageId,
|
|
303
322
|
lastMessageContent: message.content,
|
|
@@ -345,6 +364,7 @@ export class MessageService {
|
|
|
345
364
|
forwardOf: deserialized.forwardOf,
|
|
346
365
|
deleteOf: deserialized.deleteOf,
|
|
347
366
|
editOf: deserialized.editOf,
|
|
367
|
+
reactionOf: deserialized.reactionOf,
|
|
348
368
|
});
|
|
349
369
|
}
|
|
350
370
|
catch (deserializationError) {
|
|
@@ -406,6 +426,33 @@ export class MessageService {
|
|
|
406
426
|
// Do not insert a new message row for edit control messages
|
|
407
427
|
continue;
|
|
408
428
|
}
|
|
429
|
+
// Handle reaction messages by inserting a separate row
|
|
430
|
+
if (message.type === MessageType.REACTION &&
|
|
431
|
+
message.reactionOf?.originalMsgId) {
|
|
432
|
+
const discussion = await this.queries.discussions.getByOwnerAndContact(ownerUserId, message.senderId);
|
|
433
|
+
if (!discussion) {
|
|
434
|
+
log.error('no discussion for incoming reaction message', {
|
|
435
|
+
senderId: message.senderId,
|
|
436
|
+
preview: message.content.slice(0, 16),
|
|
437
|
+
});
|
|
438
|
+
continue;
|
|
439
|
+
}
|
|
440
|
+
const id = await this.queries.messages.insert({
|
|
441
|
+
messageId: message.messageId,
|
|
442
|
+
ownerUserId,
|
|
443
|
+
contactUserId: discussion.contactUserId,
|
|
444
|
+
content: message.content,
|
|
445
|
+
type: MessageType.REACTION,
|
|
446
|
+
direction: MessageDirection.INCOMING,
|
|
447
|
+
status: MessageStatus.DELIVERED,
|
|
448
|
+
timestamp: message.sentAt,
|
|
449
|
+
metadata: serializeMetadata({}),
|
|
450
|
+
reactionOf: serializeReactionOf(message.reactionOf),
|
|
451
|
+
});
|
|
452
|
+
storedIds.push(id);
|
|
453
|
+
// Do not update discussion lastMessageContent for reactions
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
409
456
|
const discussion = await this.queries.discussions.getByOwnerAndContact(ownerUserId, message.senderId);
|
|
410
457
|
if (!discussion) {
|
|
411
458
|
log.error('no discussion for incoming message', {
|
|
@@ -631,6 +678,19 @@ export class MessageService {
|
|
|
631
678
|
};
|
|
632
679
|
}
|
|
633
680
|
}
|
|
681
|
+
else if (message.type === MessageType.REACTION && message.reactionOf) {
|
|
682
|
+
const originalMsgId = message.reactionOf.originalMsgId;
|
|
683
|
+
if (!originalMsgId || originalMsgId.length !== MESSAGE_ID_SIZE) {
|
|
684
|
+
return {
|
|
685
|
+
success: false,
|
|
686
|
+
error: 'Original messageId is required for reaction messages',
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
return {
|
|
690
|
+
success: true,
|
|
691
|
+
data: serializeReactionMessage(message.content, originalMsgId, message.messageId),
|
|
692
|
+
};
|
|
693
|
+
}
|
|
634
694
|
else {
|
|
635
695
|
// Regular message with type tag
|
|
636
696
|
return {
|
|
@@ -896,6 +956,11 @@ export class MessageService {
|
|
|
896
956
|
const rows = await this.queries.messages.getVisibleByOwnerAndContact(this.session.userIdEncoded, contactUserId);
|
|
897
957
|
return rows.map(rowToMessage);
|
|
898
958
|
}
|
|
959
|
+
/** Get all non-deleted reaction messages for a contact. */
|
|
960
|
+
async getReactions(contactUserId) {
|
|
961
|
+
const rows = await this.queries.messages.getReactionsByOwnerAndContact(this.session.userIdEncoded, contactUserId);
|
|
962
|
+
return rows.map(rowToMessage);
|
|
963
|
+
}
|
|
899
964
|
/** Send a message, queued via QueueManager if available */
|
|
900
965
|
async send(message) {
|
|
901
966
|
if (this.queueManager) {
|
|
@@ -970,6 +1035,21 @@ export class MessageService {
|
|
|
970
1035
|
await this.refreshService?.stateUpdate();
|
|
971
1036
|
return true;
|
|
972
1037
|
}
|
|
1038
|
+
async sendReaction(contactUserId, emoji, originalMsgId) {
|
|
1039
|
+
const message = {
|
|
1040
|
+
ownerUserId: this.session.userIdEncoded,
|
|
1041
|
+
contactUserId,
|
|
1042
|
+
content: emoji,
|
|
1043
|
+
type: MessageType.REACTION,
|
|
1044
|
+
direction: MessageDirection.OUTGOING,
|
|
1045
|
+
status: MessageStatus.WAITING_SESSION,
|
|
1046
|
+
timestamp: new Date(),
|
|
1047
|
+
reactionOf: { originalMsgId },
|
|
1048
|
+
};
|
|
1049
|
+
const result = await this.send(message);
|
|
1050
|
+
await this.refreshService?.stateUpdate();
|
|
1051
|
+
return result;
|
|
1052
|
+
}
|
|
973
1053
|
/**
|
|
974
1054
|
* Edit an outgoing message by its database ID.
|
|
975
1055
|
* Updates the local content (preserving timestamp) and enqueues an edit
|
|
@@ -25,6 +25,9 @@ export interface DeserializedMessage {
|
|
|
25
25
|
editOf?: {
|
|
26
26
|
originalMsgId: Uint8Array;
|
|
27
27
|
};
|
|
28
|
+
reactionOf?: {
|
|
29
|
+
originalMsgId: Uint8Array;
|
|
30
|
+
};
|
|
28
31
|
type: MessageType;
|
|
29
32
|
}
|
|
30
33
|
/**
|
|
@@ -87,6 +90,7 @@ export declare function serializeDeleteMessage(originalMsgId: Uint8Array, messag
|
|
|
87
90
|
* @returns Serialized edit message bytes
|
|
88
91
|
*/
|
|
89
92
|
export declare function serializeEditMessage(newContent: string, originalMsgId: Uint8Array, messageId: Uint8Array): Uint8Array;
|
|
93
|
+
export declare function serializeReactionMessage(emoji: string, originalMsgId: Uint8Array, messageId: Uint8Array): Uint8Array;
|
|
90
94
|
/**
|
|
91
95
|
* Deserialize a message from bytes
|
|
92
96
|
*
|
|
@@ -136,6 +136,20 @@ export function serializeEditMessage(newContent, originalMsgId, messageId) {
|
|
|
136
136
|
citedMsgId: originalMsgId,
|
|
137
137
|
});
|
|
138
138
|
}
|
|
139
|
+
export function serializeReactionMessage(emoji, originalMsgId, messageId) {
|
|
140
|
+
if (messageId.length !== MESSAGE_ID_SIZE) {
|
|
141
|
+
throw new Error(`messageId must be ${MESSAGE_ID_SIZE} bytes`);
|
|
142
|
+
}
|
|
143
|
+
if (originalMsgId.length !== MESSAGE_ID_SIZE) {
|
|
144
|
+
throw new Error(`originalMsgId must be ${MESSAGE_ID_SIZE} bytes`);
|
|
145
|
+
}
|
|
146
|
+
return ProtoMessage.encode({
|
|
147
|
+
messageType: ProtoMessageType.MESSAGE_TYPE_REACTION,
|
|
148
|
+
messageId,
|
|
149
|
+
content: emoji,
|
|
150
|
+
citedMsgId: originalMsgId,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
139
153
|
/**
|
|
140
154
|
* Deserialize a message from bytes
|
|
141
155
|
*
|
|
@@ -161,6 +175,7 @@ export function deserializeMessage(buffer) {
|
|
|
161
175
|
let replyTo = undefined;
|
|
162
176
|
let deleteOf = undefined;
|
|
163
177
|
let editOf = undefined;
|
|
178
|
+
let reactionOf = undefined;
|
|
164
179
|
if (protoType === ProtoMessageType.MESSAGE_TYPE_REPLY) {
|
|
165
180
|
if (citedMsgId && citedMsgId.length === MESSAGE_ID_SIZE) {
|
|
166
181
|
replyTo = {
|
|
@@ -191,6 +206,16 @@ export function deserializeMessage(buffer) {
|
|
|
191
206
|
throw new Error(`invalid message format: message of type edit but citedMsgId empty or not correct size (${MESSAGE_ID_SIZE})`);
|
|
192
207
|
}
|
|
193
208
|
}
|
|
209
|
+
if (protoType === ProtoMessageType.MESSAGE_TYPE_REACTION) {
|
|
210
|
+
if (citedMsgId && citedMsgId.length === MESSAGE_ID_SIZE) {
|
|
211
|
+
reactionOf = {
|
|
212
|
+
originalMsgId: citedMsgId,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
throw new Error(`invalid message format: message of type reaction but citedMsgId empty or not correct size (${MESSAGE_ID_SIZE})`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
194
219
|
let forwardOf = undefined;
|
|
195
220
|
if (protoType === ProtoMessageType.MESSAGE_TYPE_FORWARD) {
|
|
196
221
|
if (decoded.forwardedContent &&
|
|
@@ -212,8 +237,11 @@ export function deserializeMessage(buffer) {
|
|
|
212
237
|
forwardOf,
|
|
213
238
|
deleteOf,
|
|
214
239
|
editOf,
|
|
240
|
+
reactionOf,
|
|
215
241
|
type: protoType === ProtoMessageType.MESSAGE_TYPE_DELETE
|
|
216
242
|
? MessageType.DELETED
|
|
217
|
-
:
|
|
243
|
+
: protoType === ProtoMessageType.MESSAGE_TYPE_REACTION
|
|
244
|
+
? MessageType.REACTION
|
|
245
|
+
: MessageType.TEXT,
|
|
218
246
|
};
|
|
219
247
|
}
|
package/package.json
CHANGED