@massalabs/gossip-sdk 0.0.2-dev.20260507154753 → 0.0.2-dev.20260507171428
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,7 +10,6 @@ export declare enum SdkEventType {
|
|
|
10
10
|
MESSAGE_FAILED = "messageFailed",
|
|
11
11
|
MESSAGE_DELETED = "messageDeleted",
|
|
12
12
|
MESSAGE_UPDATED = "messageUpdated",
|
|
13
|
-
SELF_MESSAGE_UPDATED = "selfMessageUpdated",
|
|
14
13
|
SESSION_REQUESTED = "sessionRequested",
|
|
15
14
|
SESSION_CREATED = "sessionCreated",
|
|
16
15
|
SESSION_RENEWED = "sessionRenewed",
|
|
@@ -38,9 +37,6 @@ export type SdkEvents = {
|
|
|
38
37
|
[SdkEventType.MESSAGE_UPDATED]: {
|
|
39
38
|
messages: Message[];
|
|
40
39
|
};
|
|
41
|
-
[SdkEventType.SELF_MESSAGE_UPDATED]: {
|
|
42
|
-
messages: Message[];
|
|
43
|
-
};
|
|
44
40
|
[SdkEventType.SESSION_REQUESTED]: {
|
|
45
41
|
discussion: Discussion;
|
|
46
42
|
contact: Contact;
|
|
@@ -10,7 +10,6 @@ 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";
|
|
14
13
|
SdkEventType["SESSION_REQUESTED"] = "sessionRequested";
|
|
15
14
|
SdkEventType["SESSION_CREATED"] = "sessionCreated";
|
|
16
15
|
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, encryptionKey);
|
|
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,7 +16,6 @@ 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';
|
|
20
19
|
// ---------------------------------------------------------------------------
|
|
21
20
|
// JSON serialization helpers for message fields stored as text in SQLite
|
|
22
21
|
// ---------------------------------------------------------------------------
|
|
@@ -653,14 +652,12 @@ export class MessageService {
|
|
|
653
652
|
log.info('queueing message', {
|
|
654
653
|
messageType: message.type,
|
|
655
654
|
});
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
};
|
|
663
|
-
}
|
|
655
|
+
const peerId = decodeUserId(message.contactUserId);
|
|
656
|
+
if (peerId.length !== 32) {
|
|
657
|
+
return {
|
|
658
|
+
success: false,
|
|
659
|
+
error: 'Invalid contact userId (must be 32 bytes)',
|
|
660
|
+
};
|
|
664
661
|
}
|
|
665
662
|
// Look up discussion
|
|
666
663
|
const discussion = await this.queries.discussions.getByOwnerAndContact(message.ownerUserId, message.contactUserId, parentTx);
|
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
import { Queries } from '../db/queries/index.js';
|
|
2
|
+
import type { EncryptionKey } from '../wasm/encryption.js';
|
|
2
3
|
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 encryptionKey;
|
|
9
|
+
constructor(queries: Queries, ownerUserId: string, encryptionKey: EncryptionKey);
|
|
10
10
|
ensureDiscussionExists(): Promise<void>;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
send(
|
|
14
|
-
get(id: number): Promise<Message | undefined>;
|
|
11
|
+
private encryptContent;
|
|
12
|
+
private decryptContent;
|
|
13
|
+
send(content: string): Promise<Message>;
|
|
15
14
|
getMessages(): Promise<Message[]>;
|
|
16
15
|
editMessage(id: number, newContent: string): Promise<void>;
|
|
17
16
|
deleteMessage(id: number): Promise<void>;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
+
import { decryptAead, encryptAead, nonceFromBytes, } from '../wasm/encryption.js';
|
|
1
2
|
import { MessageDirection, MessageStatus, MessageType, } from '../db/db.js';
|
|
2
|
-
import {
|
|
3
|
-
import { or, eq, sql, and } from 'drizzle-orm';
|
|
4
|
-
import { SdkEventType } from '../core/SdkEventEmitter.js';
|
|
5
|
-
import { rowToMessage } from './message.js';
|
|
3
|
+
import { encodeToBase64, decodeFromBase64 } from '../utils/base64.js';
|
|
6
4
|
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, encryptionKey) {
|
|
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, "encryptionKey", {
|
|
22
22
|
enumerable: true,
|
|
23
23
|
configurable: true,
|
|
24
24
|
writable: true,
|
|
25
|
-
value:
|
|
25
|
+
value: encryptionKey
|
|
26
26
|
});
|
|
27
27
|
}
|
|
28
28
|
async ensureDiscussionExists() {
|
|
@@ -53,64 +53,51 @@ export class SelfMessageService {
|
|
|
53
53
|
updatedAt: now,
|
|
54
54
|
});
|
|
55
55
|
}
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
async encryptContent(plaintext) {
|
|
57
|
+
const nonce = await nonceFromBytes(ZERO_NONCE_BYTES);
|
|
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);
|
|
58
61
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
+
async decryptContent(content) {
|
|
63
|
+
const cipherBytes = decodeFromBase64(content);
|
|
64
|
+
const nonce = await nonceFromBytes(ZERO_NONCE_BYTES);
|
|
65
|
+
const plaintextBytes = await decryptAead(this.encryptionKey, nonce, cipherBytes, AAD_EMPTY);
|
|
66
|
+
if (!plaintextBytes) {
|
|
67
|
+
throw new Error('Failed to decrypt self message');
|
|
62
68
|
}
|
|
63
|
-
|
|
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;
|
|
69
|
+
return new TextDecoder().decode(plaintextBytes);
|
|
72
70
|
}
|
|
73
|
-
async send(
|
|
71
|
+
async send(content) {
|
|
72
|
+
const encryptedContent = await this.encryptContent(content);
|
|
73
|
+
const now = new Date();
|
|
74
74
|
const id = await this.queries.messages.insert({
|
|
75
|
-
...message,
|
|
76
75
|
ownerUserId: this.ownerUserId,
|
|
77
|
-
|
|
78
|
-
|
|
76
|
+
contactUserId: SELF_CONTACT_ID,
|
|
77
|
+
content: encryptedContent,
|
|
78
|
+
type: MessageType.TEXT,
|
|
79
|
+
direction: MessageDirection.OUTGOING,
|
|
80
|
+
status: MessageStatus.SENT,
|
|
81
|
+
timestamp: now,
|
|
79
82
|
});
|
|
80
|
-
const
|
|
81
|
-
if (
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
}
|
|
83
|
+
const discussion = await this.queries.discussions.getByOwnerAndContact(this.ownerUserId, SELF_CONTACT_ID);
|
|
84
|
+
if (discussion?.id != null) {
|
|
85
|
+
await this.queries.discussions.updateById(discussion.id, {
|
|
86
|
+
lastMessageId: id,
|
|
87
|
+
lastMessageContent: null,
|
|
88
|
+
lastMessageTimestamp: now,
|
|
89
|
+
updatedAt: now,
|
|
90
|
+
});
|
|
98
91
|
}
|
|
99
92
|
return {
|
|
100
|
-
id
|
|
101
|
-
ownerUserId:
|
|
102
|
-
contactUserId:
|
|
103
|
-
content
|
|
104
|
-
type:
|
|
93
|
+
id,
|
|
94
|
+
ownerUserId: this.ownerUserId,
|
|
95
|
+
contactUserId: SELF_CONTACT_ID,
|
|
96
|
+
content,
|
|
97
|
+
type: MessageType.TEXT,
|
|
105
98
|
direction: MessageDirection.OUTGOING,
|
|
106
|
-
status:
|
|
107
|
-
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,
|
|
99
|
+
status: MessageStatus.SENT,
|
|
100
|
+
timestamp: now,
|
|
114
101
|
};
|
|
115
102
|
}
|
|
116
103
|
async getMessages() {
|
|
@@ -118,28 +105,16 @@ export class SelfMessageService {
|
|
|
118
105
|
const result = [];
|
|
119
106
|
for (const row of rows) {
|
|
120
107
|
try {
|
|
121
|
-
|
|
122
|
-
if (row.metadata) {
|
|
123
|
-
try {
|
|
124
|
-
metadata = JSON.parse(row.metadata);
|
|
125
|
-
}
|
|
126
|
-
catch {
|
|
127
|
-
metadata = undefined;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
108
|
+
const plaintext = await this.decryptContent(row.content);
|
|
130
109
|
result.push({
|
|
131
110
|
id: row.id,
|
|
132
111
|
ownerUserId: row.ownerUserId,
|
|
133
112
|
contactUserId: row.contactUserId,
|
|
134
|
-
content:
|
|
113
|
+
content: plaintext,
|
|
135
114
|
type: row.type,
|
|
136
|
-
forwardOf: row.forwardOf
|
|
137
|
-
? JSON.parse(row.forwardOf)
|
|
138
|
-
: undefined,
|
|
139
115
|
direction: MessageDirection.OUTGOING,
|
|
140
116
|
status: row.status,
|
|
141
117
|
timestamp: row.timestamp,
|
|
142
|
-
metadata,
|
|
143
118
|
});
|
|
144
119
|
}
|
|
145
120
|
catch {
|
|
@@ -152,39 +127,41 @@ export class SelfMessageService {
|
|
|
152
127
|
const row = await this.queries.messages.getById(id);
|
|
153
128
|
if (!row)
|
|
154
129
|
return;
|
|
130
|
+
const encryptedContent = await this.encryptContent(newContent);
|
|
155
131
|
const existingMetadata = row.metadata
|
|
156
132
|
? JSON.parse(row.metadata)
|
|
157
133
|
: {};
|
|
158
134
|
await this.queries.messages.updateById(id, {
|
|
159
|
-
content:
|
|
135
|
+
content: encryptedContent,
|
|
160
136
|
metadata: JSON.stringify({ ...existingMetadata, edited: true }),
|
|
161
137
|
});
|
|
162
138
|
}
|
|
163
139
|
async deleteMessage(id) {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
});
|
|
140
|
+
// Delete any reactions that reference this message via metadata.originalMessageId
|
|
141
|
+
const reactions = await this.queries.messages.getReactionsByOwnerAndContact(this.ownerUserId, SELF_CONTACT_ID);
|
|
142
|
+
const toDelete = reactions.filter(row => {
|
|
143
|
+
if (!row.metadata)
|
|
144
|
+
return false;
|
|
145
|
+
try {
|
|
146
|
+
const meta = JSON.parse(row.metadata);
|
|
147
|
+
return meta?.originalMessageId === id;
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
for (const reaction of toDelete) {
|
|
154
|
+
await this.queries.messages.deleteById(reaction.id);
|
|
180
155
|
}
|
|
156
|
+
await this.queries.messages.deleteById(id);
|
|
181
157
|
}
|
|
182
158
|
async sendReaction(emoji, originalMessageDbId) {
|
|
159
|
+
const encryptedEmoji = await this.encryptContent(emoji);
|
|
183
160
|
const now = new Date();
|
|
184
161
|
const id = await this.queries.messages.insert({
|
|
185
162
|
ownerUserId: this.ownerUserId,
|
|
186
163
|
contactUserId: SELF_CONTACT_ID,
|
|
187
|
-
content:
|
|
164
|
+
content: encryptedEmoji,
|
|
188
165
|
type: MessageType.REACTION,
|
|
189
166
|
direction: MessageDirection.OUTGOING,
|
|
190
167
|
status: MessageStatus.SENT,
|
|
@@ -215,10 +192,11 @@ export class SelfMessageService {
|
|
|
215
192
|
const result = [];
|
|
216
193
|
for (const row of rows) {
|
|
217
194
|
try {
|
|
195
|
+
const emoji = await this.decryptContent(row.content);
|
|
218
196
|
const meta = row.metadata ? JSON.parse(row.metadata) : null;
|
|
219
197
|
const originalMessageId = meta?.originalMessageId;
|
|
220
198
|
if (typeof originalMessageId === 'number') {
|
|
221
|
-
result.push({ id: row.id, emoji
|
|
199
|
+
result.push({ id: row.id, emoji, originalMessageId });
|
|
222
200
|
}
|
|
223
201
|
}
|
|
224
202
|
catch {
|
package/package.json
CHANGED