@massalabs/gossip-sdk 0.0.2-dev.20260506133713 → 0.0.2-dev.20260507142137
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.
|
@@ -10,6 +10,7 @@ export declare enum SdkEventType {
|
|
|
10
10
|
MESSAGE_FAILED = "messageFailed",
|
|
11
11
|
MESSAGE_DELETED = "messageDeleted",
|
|
12
12
|
MESSAGE_UPDATED = "messageUpdated",
|
|
13
|
+
SELF_MESSAGE_UPDATED = "selfMessageUpdated",
|
|
13
14
|
SESSION_REQUESTED = "sessionRequested",
|
|
14
15
|
SESSION_CREATED = "sessionCreated",
|
|
15
16
|
SESSION_RENEWED = "sessionRenewed",
|
|
@@ -37,6 +38,9 @@ export type SdkEvents = {
|
|
|
37
38
|
[SdkEventType.MESSAGE_UPDATED]: {
|
|
38
39
|
messages: Message[];
|
|
39
40
|
};
|
|
41
|
+
[SdkEventType.SELF_MESSAGE_UPDATED]: {
|
|
42
|
+
messages: Message[];
|
|
43
|
+
};
|
|
40
44
|
[SdkEventType.SESSION_REQUESTED]: {
|
|
41
45
|
discussion: Discussion;
|
|
42
46
|
contact: Contact;
|
|
@@ -10,6 +10,7 @@ export var SdkEventType;
|
|
|
10
10
|
SdkEventType["MESSAGE_FAILED"] = "messageFailed";
|
|
11
11
|
SdkEventType["MESSAGE_DELETED"] = "messageDeleted";
|
|
12
12
|
SdkEventType["MESSAGE_UPDATED"] = "messageUpdated";
|
|
13
|
+
SdkEventType["SELF_MESSAGE_UPDATED"] = "selfMessageUpdated";
|
|
13
14
|
SdkEventType["SESSION_REQUESTED"] = "sessionRequested";
|
|
14
15
|
SdkEventType["SESSION_CREATED"] = "sessionCreated";
|
|
15
16
|
SdkEventType["SESSION_RENEWED"] = "sessionRenewed";
|
package/dist/gossip.js
CHANGED
|
@@ -331,7 +331,7 @@ class GossipSdk {
|
|
|
331
331
|
this._announcement.setPersistFlusher(() => this.awaitPendingPersist());
|
|
332
332
|
this._discussion.setMessageService(this._message);
|
|
333
333
|
this._refresh = new RefreshService(this._message, this._discussion, this._announcement, session, this.eventEmitter, queries, this.config);
|
|
334
|
-
this._selfMessage = new SelfMessageService(queries, session.userIdEncoded,
|
|
334
|
+
this._selfMessage = new SelfMessageService(queries, session.userIdEncoded, this.eventEmitter);
|
|
335
335
|
await this._selfMessage.ensureDiscussionExists();
|
|
336
336
|
// Publish gossip ID (public key) on messageProtocol so the user is discoverable.
|
|
337
337
|
// Non-blocking: login must succeed even when the API is unreachable.
|
package/dist/services/message.js
CHANGED
|
@@ -16,6 +16,7 @@ import { defaultSdkConfig } from '../config/sdk.js';
|
|
|
16
16
|
import { SdkEventType } from '../core/SdkEventEmitter.js';
|
|
17
17
|
import { and, eq, sql } from 'drizzle-orm';
|
|
18
18
|
import { POST_MESSAGE_TYPES } from '../utils/message.js';
|
|
19
|
+
import { SELF_CONTACT_ID } from './selfMessage.js';
|
|
19
20
|
// ---------------------------------------------------------------------------
|
|
20
21
|
// JSON serialization helpers for message fields stored as text in SQLite
|
|
21
22
|
// ---------------------------------------------------------------------------
|
|
@@ -652,12 +653,14 @@ export class MessageService {
|
|
|
652
653
|
log.info('queueing message', {
|
|
653
654
|
messageType: message.type,
|
|
654
655
|
});
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
656
|
+
if (message.contactUserId !== SELF_CONTACT_ID) {
|
|
657
|
+
const peerId = decodeUserId(message.contactUserId);
|
|
658
|
+
if (peerId.length !== 32) {
|
|
659
|
+
return {
|
|
660
|
+
success: false,
|
|
661
|
+
error: 'Invalid contact userId (must be 32 bytes)',
|
|
662
|
+
};
|
|
663
|
+
}
|
|
661
664
|
}
|
|
662
665
|
// Look up discussion
|
|
663
666
|
const discussion = await this.queries.discussions.getByOwnerAndContact(message.ownerUserId, message.contactUserId, parentTx);
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import { Queries } from '../db/queries/index.js';
|
|
2
|
-
import type { EncryptionKey } from '../wasm/encryption.js';
|
|
3
2
|
import { type Message } from '../db/db.js';
|
|
3
|
+
import { SdkEventEmitter } from '../core/SdkEventEmitter.js';
|
|
4
4
|
export declare const SELF_CONTACT_ID = "__self__";
|
|
5
5
|
export declare class SelfMessageService {
|
|
6
6
|
private readonly queries;
|
|
7
7
|
private readonly ownerUserId;
|
|
8
|
-
private readonly
|
|
9
|
-
constructor(queries: Queries, ownerUserId: string,
|
|
8
|
+
private readonly eventEmitter;
|
|
9
|
+
constructor(queries: Queries, ownerUserId: string, eventEmitter: SdkEventEmitter);
|
|
10
10
|
ensureDiscussionExists(): Promise<void>;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
send(
|
|
11
|
+
isSelfMessage(message: Message): boolean;
|
|
12
|
+
repliedMessageId(message: Message): number | null;
|
|
13
|
+
send(message: Message): Promise<Message>;
|
|
14
|
+
get(id: number): Promise<Message | undefined>;
|
|
14
15
|
getMessages(): Promise<Message[]>;
|
|
15
16
|
editMessage(id: number, newContent: string): Promise<void>;
|
|
16
17
|
deleteMessage(id: number): Promise<void>;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { decryptAead, encryptAead, nonceFromBytes, } from '../wasm/encryption.js';
|
|
2
1
|
import { MessageDirection, MessageStatus, MessageType, } from '../db/db.js';
|
|
3
|
-
import {
|
|
2
|
+
import { messages } from '../db/schema/index.js';
|
|
3
|
+
import { or, eq, sql, and } from 'drizzle-orm';
|
|
4
|
+
import { SdkEventType } from '../core/SdkEventEmitter.js';
|
|
5
|
+
import { rowToMessage } from './message.js';
|
|
4
6
|
export const SELF_CONTACT_ID = '__self__';
|
|
5
|
-
const AAD_EMPTY = new Uint8Array(0);
|
|
6
|
-
const ZERO_NONCE_BYTES = new Uint8Array(16);
|
|
7
7
|
export class SelfMessageService {
|
|
8
|
-
constructor(queries, ownerUserId,
|
|
8
|
+
constructor(queries, ownerUserId, eventEmitter) {
|
|
9
9
|
Object.defineProperty(this, "queries", {
|
|
10
10
|
enumerable: true,
|
|
11
11
|
configurable: true,
|
|
@@ -18,11 +18,11 @@ export class SelfMessageService {
|
|
|
18
18
|
writable: true,
|
|
19
19
|
value: ownerUserId
|
|
20
20
|
});
|
|
21
|
-
Object.defineProperty(this, "
|
|
21
|
+
Object.defineProperty(this, "eventEmitter", {
|
|
22
22
|
enumerable: true,
|
|
23
23
|
configurable: true,
|
|
24
24
|
writable: true,
|
|
25
|
-
value:
|
|
25
|
+
value: eventEmitter
|
|
26
26
|
});
|
|
27
27
|
}
|
|
28
28
|
async ensureDiscussionExists() {
|
|
@@ -53,51 +53,64 @@ export class SelfMessageService {
|
|
|
53
53
|
updatedAt: now,
|
|
54
54
|
});
|
|
55
55
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const ciphertext = await encryptAead(this.encryptionKey, nonce, new TextEncoder().encode(plaintext), AAD_EMPTY);
|
|
59
|
-
// Store only ciphertext; nonce is a fixed zero value for all messages.
|
|
60
|
-
return encodeToBase64(ciphertext);
|
|
56
|
+
isSelfMessage(message) {
|
|
57
|
+
return message.contactUserId === SELF_CONTACT_ID;
|
|
61
58
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const plaintextBytes = await decryptAead(this.encryptionKey, nonce, cipherBytes, AAD_EMPTY);
|
|
66
|
-
if (!plaintextBytes) {
|
|
67
|
-
throw new Error('Failed to decrypt self message');
|
|
59
|
+
repliedMessageId(message) {
|
|
60
|
+
if (!this.isSelfMessage(message)) {
|
|
61
|
+
return null;
|
|
68
62
|
}
|
|
69
|
-
|
|
63
|
+
const value = message.metadata?.originalMessageId;
|
|
64
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
65
|
+
return value;
|
|
66
|
+
}
|
|
67
|
+
if (typeof value === 'string') {
|
|
68
|
+
const parsed = Number.parseInt(value, 10);
|
|
69
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
70
72
|
}
|
|
71
|
-
async send(
|
|
72
|
-
const encryptedContent = await this.encryptContent(content);
|
|
73
|
-
const now = new Date();
|
|
73
|
+
async send(message) {
|
|
74
74
|
const id = await this.queries.messages.insert({
|
|
75
|
+
...message,
|
|
75
76
|
ownerUserId: this.ownerUserId,
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
type: MessageType.TEXT,
|
|
79
|
-
direction: MessageDirection.OUTGOING,
|
|
80
|
-
status: MessageStatus.SENT,
|
|
81
|
-
timestamp: now,
|
|
77
|
+
forwardOf: message.forwardOf ? JSON.stringify(message.forwardOf) : null,
|
|
78
|
+
metadata: message.metadata ? JSON.stringify(message.metadata) : null,
|
|
82
79
|
});
|
|
83
|
-
const
|
|
84
|
-
if (
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
80
|
+
const retrievedMessage = await this.get(id);
|
|
81
|
+
if (!retrievedMessage) {
|
|
82
|
+
throw new Error('Failed to send message');
|
|
83
|
+
}
|
|
84
|
+
return retrievedMessage;
|
|
85
|
+
}
|
|
86
|
+
async get(id) {
|
|
87
|
+
const row = await this.queries.messages.getById(id);
|
|
88
|
+
if (!row)
|
|
89
|
+
return undefined;
|
|
90
|
+
let metadata;
|
|
91
|
+
if (row.metadata) {
|
|
92
|
+
try {
|
|
93
|
+
metadata = JSON.parse(row.metadata);
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
metadata = undefined;
|
|
97
|
+
}
|
|
91
98
|
}
|
|
92
99
|
return {
|
|
93
|
-
id,
|
|
94
|
-
ownerUserId:
|
|
95
|
-
contactUserId:
|
|
96
|
-
content,
|
|
97
|
-
type:
|
|
100
|
+
id: row.id,
|
|
101
|
+
ownerUserId: row.ownerUserId,
|
|
102
|
+
contactUserId: row.contactUserId,
|
|
103
|
+
content: row.content,
|
|
104
|
+
type: row.type,
|
|
98
105
|
direction: MessageDirection.OUTGOING,
|
|
99
|
-
status:
|
|
100
|
-
timestamp:
|
|
106
|
+
status: row.status,
|
|
107
|
+
timestamp: row.timestamp,
|
|
108
|
+
forwardOf: row.forwardOf
|
|
109
|
+
? JSON.parse(row.forwardOf)
|
|
110
|
+
: undefined,
|
|
111
|
+
deleteOf: row.deleteOf ? JSON.parse(row.deleteOf) : undefined,
|
|
112
|
+
editOf: row.editOf ? JSON.parse(row.editOf) : undefined,
|
|
113
|
+
metadata,
|
|
101
114
|
};
|
|
102
115
|
}
|
|
103
116
|
async getMessages() {
|
|
@@ -105,16 +118,28 @@ export class SelfMessageService {
|
|
|
105
118
|
const result = [];
|
|
106
119
|
for (const row of rows) {
|
|
107
120
|
try {
|
|
108
|
-
|
|
121
|
+
let metadata;
|
|
122
|
+
if (row.metadata) {
|
|
123
|
+
try {
|
|
124
|
+
metadata = JSON.parse(row.metadata);
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
metadata = undefined;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
109
130
|
result.push({
|
|
110
131
|
id: row.id,
|
|
111
132
|
ownerUserId: row.ownerUserId,
|
|
112
133
|
contactUserId: row.contactUserId,
|
|
113
|
-
content:
|
|
134
|
+
content: row.content,
|
|
114
135
|
type: row.type,
|
|
136
|
+
forwardOf: row.forwardOf
|
|
137
|
+
? JSON.parse(row.forwardOf)
|
|
138
|
+
: undefined,
|
|
115
139
|
direction: MessageDirection.OUTGOING,
|
|
116
140
|
status: row.status,
|
|
117
141
|
timestamp: row.timestamp,
|
|
142
|
+
metadata,
|
|
118
143
|
});
|
|
119
144
|
}
|
|
120
145
|
catch {
|
|
@@ -127,41 +152,39 @@ export class SelfMessageService {
|
|
|
127
152
|
const row = await this.queries.messages.getById(id);
|
|
128
153
|
if (!row)
|
|
129
154
|
return;
|
|
130
|
-
const encryptedContent = await this.encryptContent(newContent);
|
|
131
155
|
const existingMetadata = row.metadata
|
|
132
156
|
? JSON.parse(row.metadata)
|
|
133
157
|
: {};
|
|
134
158
|
await this.queries.messages.updateById(id, {
|
|
135
|
-
content:
|
|
159
|
+
content: newContent,
|
|
136
160
|
metadata: JSON.stringify({ ...existingMetadata, edited: true }),
|
|
137
161
|
});
|
|
138
162
|
}
|
|
139
163
|
async deleteMessage(id) {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
164
|
+
await this.queries.conn.db
|
|
165
|
+
.delete(messages)
|
|
166
|
+
.where(or(eq(messages.id, id), and(eq(messages.type, MessageType.REACTION), sql `json_extract(${messages.metadata}, '$.originalMessageId') = ${id}`)));
|
|
167
|
+
// Remove $.originalMessageId from metadata of any message that references the deleted message
|
|
168
|
+
const updatedRows = await this.queries.conn.db
|
|
169
|
+
.update(messages)
|
|
170
|
+
.set({
|
|
171
|
+
metadata: sql `json_remove(${messages.metadata}, '$.originalMessageId')`,
|
|
172
|
+
})
|
|
173
|
+
.where(sql `json_extract(${messages.metadata}, '$.originalMessageId') = ${id}`)
|
|
174
|
+
.returning();
|
|
175
|
+
const updatedMessages = updatedRows.map(rowToMessage);
|
|
176
|
+
if (updatedMessages.length > 0) {
|
|
177
|
+
this.eventEmitter.emit(SdkEventType.SELF_MESSAGE_UPDATED, {
|
|
178
|
+
messages: updatedMessages,
|
|
179
|
+
});
|
|
155
180
|
}
|
|
156
|
-
await this.queries.messages.deleteById(id);
|
|
157
181
|
}
|
|
158
182
|
async sendReaction(emoji, originalMessageDbId) {
|
|
159
|
-
const encryptedEmoji = await this.encryptContent(emoji);
|
|
160
183
|
const now = new Date();
|
|
161
184
|
const id = await this.queries.messages.insert({
|
|
162
185
|
ownerUserId: this.ownerUserId,
|
|
163
186
|
contactUserId: SELF_CONTACT_ID,
|
|
164
|
-
content:
|
|
187
|
+
content: emoji,
|
|
165
188
|
type: MessageType.REACTION,
|
|
166
189
|
direction: MessageDirection.OUTGOING,
|
|
167
190
|
status: MessageStatus.SENT,
|
|
@@ -192,11 +215,10 @@ export class SelfMessageService {
|
|
|
192
215
|
const result = [];
|
|
193
216
|
for (const row of rows) {
|
|
194
217
|
try {
|
|
195
|
-
const emoji = await this.decryptContent(row.content);
|
|
196
218
|
const meta = row.metadata ? JSON.parse(row.metadata) : null;
|
|
197
219
|
const originalMessageId = meta?.originalMessageId;
|
|
198
220
|
if (typeof originalMessageId === 'number') {
|
|
199
|
-
result.push({ id: row.id, emoji, originalMessageId });
|
|
221
|
+
result.push({ id: row.id, emoji: row.content, originalMessageId });
|
|
200
222
|
}
|
|
201
223
|
}
|
|
202
224
|
catch {
|
package/package.json
CHANGED