@massalabs/gossip-sdk 0.0.2-dev.20260128094509 → 0.0.2-dev.20260128160824
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/api/messageProtocol/index.d.ts +19 -0
- package/dist/api/messageProtocol/index.js +26 -0
- package/dist/api/messageProtocol/mock.d.ts +12 -0
- package/{src/api/messageProtocol/mock.ts → dist/api/messageProtocol/mock.js} +2 -3
- package/dist/api/messageProtocol/rest.d.ts +22 -0
- package/dist/api/messageProtocol/rest.js +161 -0
- package/dist/api/messageProtocol/types.d.ts +61 -0
- package/dist/api/messageProtocol/types.js +6 -0
- package/dist/assets/generated/wasm/README.md +281 -0
- package/dist/assets/generated/wasm/gossip_wasm.d.ts +638 -0
- package/dist/assets/generated/wasm/gossip_wasm.js +1557 -0
- package/dist/assets/generated/wasm/gossip_wasm_bg.wasm +0 -0
- package/dist/assets/generated/wasm/gossip_wasm_bg.wasm.d.ts +164 -0
- package/dist/assets/generated/wasm/package.json +15 -0
- package/dist/assets/generated/wasm-node/README.md +281 -0
- package/dist/assets/generated/wasm-node/gossip_wasm.d.ts +443 -0
- package/dist/assets/generated/wasm-node/gossip_wasm.js +1488 -0
- package/dist/assets/generated/wasm-node/gossip_wasm_bg.wasm +0 -0
- package/dist/assets/generated/wasm-node/gossip_wasm_bg.wasm.d.ts +164 -0
- package/dist/assets/generated/wasm-node/package.json +11 -0
- package/dist/config/protocol.d.ts +36 -0
- package/dist/config/protocol.js +77 -0
- package/dist/config/sdk.d.ts +82 -0
- package/dist/config/sdk.js +55 -0
- package/{src/contacts.ts → dist/contacts.d.ts} +11 -95
- package/dist/contacts.js +166 -0
- package/dist/core/SdkEventEmitter.d.ts +36 -0
- package/dist/core/SdkEventEmitter.js +59 -0
- package/dist/core/SdkPolling.d.ts +35 -0
- package/dist/core/SdkPolling.js +100 -0
- package/{src/core/index.ts → dist/core/index.d.ts} +0 -2
- package/dist/core/index.js +5 -0
- package/dist/crypto/bip39.d.ts +34 -0
- package/dist/crypto/bip39.js +62 -0
- package/dist/crypto/encryption.d.ts +37 -0
- package/dist/crypto/encryption.js +46 -0
- package/dist/db.d.ts +190 -0
- package/dist/db.js +311 -0
- package/dist/gossipSdk.d.ts +274 -0
- package/dist/gossipSdk.js +690 -0
- package/dist/index.d.ts +59 -0
- package/dist/index.js +61 -0
- package/dist/services/announcement.d.ts +43 -0
- package/dist/services/announcement.js +491 -0
- package/dist/services/auth.d.ts +37 -0
- package/dist/services/auth.js +76 -0
- package/dist/services/discussion.d.ts +63 -0
- package/dist/services/discussion.js +297 -0
- package/dist/services/message.d.ts +74 -0
- package/dist/services/message.js +826 -0
- package/dist/services/refresh.d.ts +41 -0
- package/dist/services/refresh.js +205 -0
- package/{src/sw.ts → dist/sw.d.ts} +1 -8
- package/dist/sw.js +10 -0
- package/dist/types/events.d.ts +80 -0
- package/dist/types/events.js +7 -0
- package/dist/types.d.ts +32 -0
- package/dist/types.js +7 -0
- package/dist/utils/base64.d.ts +10 -0
- package/dist/utils/base64.js +30 -0
- package/dist/utils/contacts.d.ts +42 -0
- package/dist/utils/contacts.js +113 -0
- package/dist/utils/discussions.d.ts +24 -0
- package/dist/utils/discussions.js +38 -0
- package/dist/utils/logs.d.ts +19 -0
- package/dist/utils/logs.js +89 -0
- package/dist/utils/messageSerialization.d.ts +64 -0
- package/dist/utils/messageSerialization.js +184 -0
- package/dist/utils/queue.d.ts +50 -0
- package/dist/utils/queue.js +110 -0
- package/dist/utils/type.d.ts +10 -0
- package/dist/utils/type.js +4 -0
- package/dist/utils/userId.d.ts +40 -0
- package/dist/utils/userId.js +90 -0
- package/dist/utils/validation.d.ts +50 -0
- package/dist/utils/validation.js +112 -0
- package/dist/utils.d.ts +30 -0
- package/{src/utils.ts → dist/utils.js} +9 -19
- package/dist/wasm/encryption.d.ts +56 -0
- package/{src/wasm/encryption.ts → dist/wasm/encryption.js} +22 -51
- package/dist/wasm/index.d.ts +10 -0
- package/{src/wasm/index.ts → dist/wasm/index.js} +1 -8
- package/dist/wasm/loader.d.ts +22 -0
- package/dist/wasm/loader.js +78 -0
- package/dist/wasm/session.d.ts +85 -0
- package/dist/wasm/session.js +226 -0
- package/dist/wasm/userKeys.d.ts +17 -0
- package/{src/wasm/userKeys.ts → dist/wasm/userKeys.js} +6 -13
- package/package.json +15 -2
- package/src/api/messageProtocol/index.ts +0 -53
- package/src/api/messageProtocol/rest.ts +0 -209
- package/src/api/messageProtocol/types.ts +0 -70
- package/src/config/protocol.ts +0 -97
- package/src/config/sdk.ts +0 -131
- package/src/core/SdkEventEmitter.ts +0 -91
- package/src/core/SdkPolling.ts +0 -134
- package/src/crypto/bip39.ts +0 -84
- package/src/crypto/encryption.ts +0 -77
- package/src/db.ts +0 -465
- package/src/gossipSdk.ts +0 -994
- package/src/index.ts +0 -211
- package/src/services/announcement.ts +0 -653
- package/src/services/auth.ts +0 -95
- package/src/services/discussion.ts +0 -380
- package/src/services/message.ts +0 -1055
- package/src/services/refresh.ts +0 -234
- package/src/types/events.ts +0 -108
- package/src/types.ts +0 -70
- package/src/utils/base64.ts +0 -39
- package/src/utils/contacts.ts +0 -161
- package/src/utils/discussions.ts +0 -55
- package/src/utils/logs.ts +0 -86
- package/src/utils/messageSerialization.ts +0 -257
- package/src/utils/queue.ts +0 -106
- package/src/utils/type.ts +0 -7
- package/src/utils/userId.ts +0 -114
- package/src/utils/validation.ts +0 -144
- package/src/wasm/loader.ts +0 -123
- package/src/wasm/session.ts +0 -276
- package/test/config/protocol.spec.ts +0 -31
- package/test/config/sdk.spec.ts +0 -163
- package/test/db/helpers.spec.ts +0 -142
- package/test/db/operations.spec.ts +0 -128
- package/test/db/states.spec.ts +0 -535
- package/test/integration/discussion-flow.spec.ts +0 -422
- package/test/integration/messaging-flow.spec.ts +0 -708
- package/test/integration/sdk-lifecycle.spec.ts +0 -325
- package/test/mocks/index.ts +0 -9
- package/test/mocks/mockMessageProtocol.ts +0 -100
- package/test/services/auth.spec.ts +0 -311
- package/test/services/discussion.spec.ts +0 -279
- package/test/services/message-deduplication.spec.ts +0 -299
- package/test/services/message-startup.spec.ts +0 -331
- package/test/services/message.spec.ts +0 -817
- package/test/services/refresh.spec.ts +0 -199
- package/test/services/session-status.spec.ts +0 -349
- package/test/session/wasm.spec.ts +0 -227
- package/test/setup.ts +0 -52
- package/test/utils/contacts.spec.ts +0 -156
- package/test/utils/discussions.spec.ts +0 -66
- package/test/utils/queue.spec.ts +0 -52
- package/test/utils/serialization.spec.ts +0 -120
- package/test/utils/userId.spec.ts +0 -120
- package/test/utils/validation.spec.ts +0 -223
- package/test/utils.ts +0 -212
- package/tsconfig.json +0 -26
- package/tsconfig.tsbuildinfo +0 -1
- package/vitest.config.ts +0 -28
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Module Implementation
|
|
3
|
+
*
|
|
4
|
+
* This file contains the real WASM implementation of the SessionModule
|
|
5
|
+
* using SessionManagerWrapper and related WASM classes.
|
|
6
|
+
*/
|
|
7
|
+
import { SessionManagerWrapper, SessionStatus, SessionConfig, } from '#wasm';
|
|
8
|
+
import { encodeUserId } from '../utils/userId';
|
|
9
|
+
export class SessionModule {
|
|
10
|
+
constructor(userKeys, onPersist, config) {
|
|
11
|
+
Object.defineProperty(this, "sessionManager", {
|
|
12
|
+
enumerable: true,
|
|
13
|
+
configurable: true,
|
|
14
|
+
writable: true,
|
|
15
|
+
value: null
|
|
16
|
+
});
|
|
17
|
+
Object.defineProperty(this, "onPersist", {
|
|
18
|
+
enumerable: true,
|
|
19
|
+
configurable: true,
|
|
20
|
+
writable: true,
|
|
21
|
+
value: void 0
|
|
22
|
+
}); // Async callback for persistence
|
|
23
|
+
Object.defineProperty(this, "ourPk", {
|
|
24
|
+
enumerable: true,
|
|
25
|
+
configurable: true,
|
|
26
|
+
writable: true,
|
|
27
|
+
value: void 0
|
|
28
|
+
});
|
|
29
|
+
Object.defineProperty(this, "ourSk", {
|
|
30
|
+
enumerable: true,
|
|
31
|
+
configurable: true,
|
|
32
|
+
writable: true,
|
|
33
|
+
value: void 0
|
|
34
|
+
});
|
|
35
|
+
Object.defineProperty(this, "userId", {
|
|
36
|
+
enumerable: true,
|
|
37
|
+
configurable: true,
|
|
38
|
+
writable: true,
|
|
39
|
+
value: void 0
|
|
40
|
+
});
|
|
41
|
+
Object.defineProperty(this, "userIdEncoded", {
|
|
42
|
+
enumerable: true,
|
|
43
|
+
configurable: true,
|
|
44
|
+
writable: true,
|
|
45
|
+
value: void 0
|
|
46
|
+
});
|
|
47
|
+
this.ourPk = userKeys.public_keys();
|
|
48
|
+
this.ourSk = userKeys.secret_keys();
|
|
49
|
+
this.userId = this.ourPk.derive_id();
|
|
50
|
+
this.userIdEncoded = encodeUserId(this.userId);
|
|
51
|
+
const sessionConfig = config ?? SessionConfig.new_default();
|
|
52
|
+
this.sessionManager = new SessionManagerWrapper(sessionConfig);
|
|
53
|
+
this.onPersist = onPersist;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Set the persistence callback
|
|
57
|
+
*/
|
|
58
|
+
setOnPersist(callback) {
|
|
59
|
+
this.onPersist = callback;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Helper to trigger persistence after state changes.
|
|
63
|
+
* Returns a promise that resolves when persistence is complete.
|
|
64
|
+
* IMPORTANT: Callers should await this before sending data to network
|
|
65
|
+
* to prevent state loss on app crash.
|
|
66
|
+
*/
|
|
67
|
+
async persistIfNeeded() {
|
|
68
|
+
if (this.onPersist) {
|
|
69
|
+
await this.onPersist();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Trigger persistence explicitly and wait for completion.
|
|
74
|
+
* Use this when you need to ensure state is saved before proceeding.
|
|
75
|
+
*/
|
|
76
|
+
async persist() {
|
|
77
|
+
await this.persistIfNeeded();
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Initialize session from an encrypted blob
|
|
81
|
+
*/
|
|
82
|
+
load(profile, encryptionKey) {
|
|
83
|
+
// Clean up existing session if any
|
|
84
|
+
this.cleanup();
|
|
85
|
+
this.sessionManager = SessionManagerWrapper.from_encrypted_blob(profile.session, encryptionKey);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Serialize session to an encrypted blob
|
|
89
|
+
*/
|
|
90
|
+
toEncryptedBlob(key) {
|
|
91
|
+
if (!this.sessionManager) {
|
|
92
|
+
throw new Error('Session manager is not initialized');
|
|
93
|
+
}
|
|
94
|
+
return this.sessionManager.to_encrypted_blob(key);
|
|
95
|
+
}
|
|
96
|
+
cleanup() {
|
|
97
|
+
this.sessionManager?.free();
|
|
98
|
+
this.sessionManager = null;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Establish an outgoing session with a peer via the underlying WASM wrapper
|
|
102
|
+
* @param peerPk - The peer's public keys
|
|
103
|
+
* @param userData - Optional user data to include in the announcement (defaults to empty array)
|
|
104
|
+
* @returns The announcement bytes to publish
|
|
105
|
+
*/
|
|
106
|
+
async establishOutgoingSession(peerPk, userData) {
|
|
107
|
+
if (!this.sessionManager) {
|
|
108
|
+
throw new Error('Session manager is not initialized');
|
|
109
|
+
}
|
|
110
|
+
const userDataBytes = userData ?? new Uint8Array(0);
|
|
111
|
+
const result = this.sessionManager.establish_outgoing_session(peerPk, this.ourPk, this.ourSk, userDataBytes);
|
|
112
|
+
if (result.length === 0) {
|
|
113
|
+
throw new Error('Failed to establish outgoing session. Session manager returned empty announcement bytes.');
|
|
114
|
+
}
|
|
115
|
+
await this.persistIfNeeded();
|
|
116
|
+
return result;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Feed an incoming announcement into the session manager
|
|
120
|
+
* @returns AnnouncementResult containing the announcer's public keys, timestamp, and user data, or undefined if invalid
|
|
121
|
+
*/
|
|
122
|
+
async feedIncomingAnnouncement(announcementBytes) {
|
|
123
|
+
if (!this.sessionManager) {
|
|
124
|
+
throw new Error('Session manager is not initialized');
|
|
125
|
+
}
|
|
126
|
+
const result = this.sessionManager.feed_incoming_announcement(announcementBytes, this.ourPk, this.ourSk);
|
|
127
|
+
if (result) {
|
|
128
|
+
await this.persistIfNeeded();
|
|
129
|
+
}
|
|
130
|
+
return result;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Get the list of message board read keys (seekers) to monitor
|
|
134
|
+
*/
|
|
135
|
+
getMessageBoardReadKeys() {
|
|
136
|
+
if (!this.sessionManager) {
|
|
137
|
+
throw new Error('Session manager is not initialized');
|
|
138
|
+
}
|
|
139
|
+
return this.sessionManager.get_message_board_read_keys();
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Process an incoming ciphertext from the message board
|
|
143
|
+
*/
|
|
144
|
+
async feedIncomingMessageBoardRead(seeker, ciphertext) {
|
|
145
|
+
if (!this.sessionManager) {
|
|
146
|
+
throw new Error('Session manager is not initialized');
|
|
147
|
+
}
|
|
148
|
+
const result = this.sessionManager.feed_incoming_message_board_read(seeker, ciphertext, this.ourSk);
|
|
149
|
+
await this.persistIfNeeded();
|
|
150
|
+
return result;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Send a message to a peer.
|
|
154
|
+
* IMPORTANT: This persists session state before returning.
|
|
155
|
+
* The returned output should only be sent to network AFTER this resolves.
|
|
156
|
+
*/
|
|
157
|
+
async sendMessage(peerId, message) {
|
|
158
|
+
if (!this.sessionManager) {
|
|
159
|
+
throw new Error('Session manager is not initialized');
|
|
160
|
+
}
|
|
161
|
+
const result = this.sessionManager.send_message(peerId, message);
|
|
162
|
+
// CRITICAL: Persist session state BEFORE returning
|
|
163
|
+
// This ensures state is saved before the encrypted message goes on the network
|
|
164
|
+
await this.persistIfNeeded();
|
|
165
|
+
return result;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* List all known peer IDs
|
|
169
|
+
*/
|
|
170
|
+
peerList() {
|
|
171
|
+
if (!this.sessionManager) {
|
|
172
|
+
throw new Error('Session manager is not initialized');
|
|
173
|
+
}
|
|
174
|
+
return this.sessionManager.peer_list();
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Get the session status for a peer
|
|
178
|
+
*/
|
|
179
|
+
peerSessionStatus(peerId) {
|
|
180
|
+
if (!this.sessionManager) {
|
|
181
|
+
throw new Error('Session manager is not initialized');
|
|
182
|
+
}
|
|
183
|
+
return this.sessionManager.peer_session_status(peerId);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Discard a peer and all associated session state
|
|
187
|
+
*/
|
|
188
|
+
async peerDiscard(peerId) {
|
|
189
|
+
if (!this.sessionManager) {
|
|
190
|
+
throw new Error('Session manager is not initialized');
|
|
191
|
+
}
|
|
192
|
+
this.sessionManager.peer_discard(peerId);
|
|
193
|
+
await this.persistIfNeeded();
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Refresh sessions, returning peer IDs that need keep-alive messages
|
|
197
|
+
*/
|
|
198
|
+
async refresh() {
|
|
199
|
+
if (!this.sessionManager) {
|
|
200
|
+
throw new Error('Session manager is not initialized');
|
|
201
|
+
}
|
|
202
|
+
const result = this.sessionManager.refresh();
|
|
203
|
+
await this.persistIfNeeded();
|
|
204
|
+
return result;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
export function sessionStatusToString(status) {
|
|
208
|
+
switch (status) {
|
|
209
|
+
case SessionStatus.Active:
|
|
210
|
+
return 'Active';
|
|
211
|
+
case SessionStatus.UnknownPeer:
|
|
212
|
+
return 'UnknownPeer';
|
|
213
|
+
case SessionStatus.NoSession:
|
|
214
|
+
return 'NoSession';
|
|
215
|
+
case SessionStatus.PeerRequested:
|
|
216
|
+
return 'PeerRequested';
|
|
217
|
+
case SessionStatus.SelfRequested:
|
|
218
|
+
return 'SelfRequested';
|
|
219
|
+
case SessionStatus.Killed:
|
|
220
|
+
return 'Killed';
|
|
221
|
+
case SessionStatus.Saturated:
|
|
222
|
+
return 'Saturated';
|
|
223
|
+
default:
|
|
224
|
+
throw new Error(`Unknown session status: ${status}`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User Keys Support
|
|
3
|
+
*
|
|
4
|
+
* This file provides proxy functions for user key generation,
|
|
5
|
+
* ensuring proper initialization before calling any WASM functions.
|
|
6
|
+
*/
|
|
7
|
+
import { UserKeys } from '#wasm';
|
|
8
|
+
export { UserKeys };
|
|
9
|
+
/**
|
|
10
|
+
* Generate user keys from a passphrase using password-based key derivation
|
|
11
|
+
* This ensures WASM is initialized before calling
|
|
12
|
+
*
|
|
13
|
+
* @param passphrase - The user's passphrase
|
|
14
|
+
* @param secondaryKey - A 32-byte secondary public key
|
|
15
|
+
* @returns UserKeys object containing public and secret keys
|
|
16
|
+
*/
|
|
17
|
+
export declare function generateUserKeys(passphrase: string): Promise<UserKeys>;
|
|
@@ -4,16 +4,10 @@
|
|
|
4
4
|
* This file provides proxy functions for user key generation,
|
|
5
5
|
* ensuring proper initialization before calling any WASM functions.
|
|
6
6
|
*/
|
|
7
|
-
|
|
8
7
|
import { ensureWasmInitialized } from './loader';
|
|
9
|
-
import {
|
|
10
|
-
generate_user_keys as _generate_user_keys,
|
|
11
|
-
UserKeys,
|
|
12
|
-
} from '../assets/generated/wasm/gossip_wasm';
|
|
13
|
-
|
|
8
|
+
import { generate_user_keys as _generate_user_keys, UserKeys } from '#wasm';
|
|
14
9
|
// Re-export classes
|
|
15
10
|
export { UserKeys };
|
|
16
|
-
|
|
17
11
|
/**
|
|
18
12
|
* Generate user keys from a passphrase using password-based key derivation
|
|
19
13
|
* This ensures WASM is initialized before calling
|
|
@@ -22,10 +16,9 @@ export { UserKeys };
|
|
|
22
16
|
* @param secondaryKey - A 32-byte secondary public key
|
|
23
17
|
* @returns UserKeys object containing public and secret keys
|
|
24
18
|
*/
|
|
25
|
-
export async function generateUserKeys(passphrase
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
return keys;
|
|
19
|
+
export async function generateUserKeys(passphrase) {
|
|
20
|
+
await ensureWasmInitialized();
|
|
21
|
+
// The actual WASM function is synchronous, so we can call it directly
|
|
22
|
+
const keys = _generate_user_keys(passphrase);
|
|
23
|
+
return keys;
|
|
31
24
|
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@massalabs/gossip-sdk",
|
|
3
|
-
"version": "0.0.2-dev.
|
|
3
|
+
"version": "0.0.2-dev.20260128160824",
|
|
4
4
|
"description": "Gossip SDK for automation, chatbot, and integration use cases",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"README.md"
|
|
11
|
+
],
|
|
12
|
+
"imports": {
|
|
13
|
+
"#wasm": {
|
|
14
|
+
"browser": "./dist/assets/generated/wasm/gossip_wasm.js",
|
|
15
|
+
"default": "./dist/assets/generated/wasm-node/gossip_wasm.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
8
18
|
"exports": {
|
|
9
19
|
".": {
|
|
10
20
|
"types": "./dist/index.d.ts",
|
|
@@ -16,7 +26,9 @@
|
|
|
16
26
|
}
|
|
17
27
|
},
|
|
18
28
|
"scripts": {
|
|
19
|
-
"
|
|
29
|
+
"prebuild": "rimraf dist",
|
|
30
|
+
"build": "tsc && npm run copy:wasm",
|
|
31
|
+
"copy:wasm": "mkdir -p dist/assets/generated/wasm dist/assets/generated/wasm-node && cp -R src/assets/generated/wasm/* dist/assets/generated/wasm/ && cp -R src/assets/generated/wasm-node/* dist/assets/generated/wasm-node/",
|
|
20
32
|
"test": "vitest",
|
|
21
33
|
"test:run": "vitest run",
|
|
22
34
|
"test:coverage": "vitest run --coverage"
|
|
@@ -31,6 +43,7 @@
|
|
|
31
43
|
"license": "MIT",
|
|
32
44
|
"devDependencies": {
|
|
33
45
|
"fake-indexeddb": "^6.2.5",
|
|
46
|
+
"rimraf": "^6.1.2",
|
|
34
47
|
"typescript": "^5.9.3",
|
|
35
48
|
"vitest": "^4.0.17"
|
|
36
49
|
},
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Message Protocol Module
|
|
3
|
-
*
|
|
4
|
-
* Factory functions and exports for message protocol implementations.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
export type {
|
|
8
|
-
EncryptedMessage,
|
|
9
|
-
IMessageProtocol,
|
|
10
|
-
MessageProtocolResponse,
|
|
11
|
-
BulletinItem,
|
|
12
|
-
} from './types';
|
|
13
|
-
export { RestMessageProtocol } from './rest';
|
|
14
|
-
export { MessageProtocol } from './mock';
|
|
15
|
-
|
|
16
|
-
import type { IMessageProtocol } from './types';
|
|
17
|
-
import {
|
|
18
|
-
defaultMessageProtocol,
|
|
19
|
-
protocolConfig,
|
|
20
|
-
type MessageProtocolType,
|
|
21
|
-
} from '../../config/protocol';
|
|
22
|
-
|
|
23
|
-
import { RestMessageProtocol } from './rest';
|
|
24
|
-
import { MessageProtocol } from './mock';
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Factory function to create message protocol instances
|
|
28
|
-
*/
|
|
29
|
-
export function createMessageProtocol(
|
|
30
|
-
type: MessageProtocolType = defaultMessageProtocol,
|
|
31
|
-
config?: Partial<{ baseUrl: string; timeout: number; retryAttempts: number }>
|
|
32
|
-
): IMessageProtocol {
|
|
33
|
-
switch (type) {
|
|
34
|
-
case 'rest': {
|
|
35
|
-
return new RestMessageProtocol(
|
|
36
|
-
config?.baseUrl || protocolConfig.baseUrl,
|
|
37
|
-
config?.timeout || 10000,
|
|
38
|
-
config?.retryAttempts || 3
|
|
39
|
-
);
|
|
40
|
-
}
|
|
41
|
-
case 'mock': {
|
|
42
|
-
return new MessageProtocol(
|
|
43
|
-
config?.baseUrl || protocolConfig.baseUrl,
|
|
44
|
-
config?.timeout || 10000,
|
|
45
|
-
config?.retryAttempts || 3
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
default:
|
|
49
|
-
throw new Error(`Unsupported message protocol type: ${type}`);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export const restMessageProtocol = createMessageProtocol();
|
|
@@ -1,209 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* REST API implementation of the message protocol
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
BulletinItem,
|
|
7
|
-
EncryptedMessage,
|
|
8
|
-
IMessageProtocol,
|
|
9
|
-
MessageProtocolResponse,
|
|
10
|
-
} from './types';
|
|
11
|
-
import { encodeToBase64, decodeFromBase64 } from '../../utils/base64';
|
|
12
|
-
|
|
13
|
-
const BULLETIN_ENDPOINT = '/bulletin';
|
|
14
|
-
const MESSAGES_ENDPOINT = '/messages';
|
|
15
|
-
|
|
16
|
-
export type BulletinsPage = {
|
|
17
|
-
counter: string;
|
|
18
|
-
data: string;
|
|
19
|
-
}[];
|
|
20
|
-
|
|
21
|
-
type FetchMessagesResponse = {
|
|
22
|
-
key: string;
|
|
23
|
-
value: string;
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
export class RestMessageProtocol implements IMessageProtocol {
|
|
27
|
-
constructor(
|
|
28
|
-
private baseUrl: string,
|
|
29
|
-
private timeout: number = 10000,
|
|
30
|
-
private retryAttempts: number = 3
|
|
31
|
-
) {}
|
|
32
|
-
|
|
33
|
-
// TODO: Implement a fetch with pagination to avoid fetching all messages at once
|
|
34
|
-
async fetchMessages(seekers: Uint8Array[]): Promise<EncryptedMessage[]> {
|
|
35
|
-
const url = `${this.baseUrl}${MESSAGES_ENDPOINT}/fetch`;
|
|
36
|
-
|
|
37
|
-
const response = await this.makeRequest<FetchMessagesResponse[]>(url, {
|
|
38
|
-
method: 'POST',
|
|
39
|
-
headers: { 'Content-Type': 'application/json' },
|
|
40
|
-
body: JSON.stringify({ seekers: seekers.map(encodeToBase64) }),
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
if (!response.success || !response.data) {
|
|
44
|
-
throw new Error(response.error || 'Failed to fetch messages');
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return response.data.map((item: FetchMessagesResponse) => {
|
|
48
|
-
const seeker = decodeFromBase64(item.key);
|
|
49
|
-
const ciphertext = decodeFromBase64(item.value);
|
|
50
|
-
|
|
51
|
-
return {
|
|
52
|
-
seeker,
|
|
53
|
-
ciphertext,
|
|
54
|
-
};
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
async sendMessage(message: EncryptedMessage): Promise<void> {
|
|
59
|
-
const url = `${this.baseUrl}${MESSAGES_ENDPOINT}/`;
|
|
60
|
-
|
|
61
|
-
const response = await this.makeRequest<void>(url, {
|
|
62
|
-
method: 'POST',
|
|
63
|
-
headers: { 'Content-Type': 'application/json' },
|
|
64
|
-
body: JSON.stringify({
|
|
65
|
-
key: encodeToBase64(message.seeker),
|
|
66
|
-
value: encodeToBase64(message.ciphertext),
|
|
67
|
-
}),
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
if (!response.success) {
|
|
71
|
-
throw new Error(response.error || 'Failed to send message');
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
async sendAnnouncement(announcement: Uint8Array): Promise<string> {
|
|
76
|
-
const url = `${this.baseUrl}${BULLETIN_ENDPOINT}`;
|
|
77
|
-
|
|
78
|
-
const response = await this.makeRequest<{ counter: string }>(url, {
|
|
79
|
-
method: 'POST',
|
|
80
|
-
headers: { 'Content-Type': 'application/json' },
|
|
81
|
-
body: JSON.stringify({
|
|
82
|
-
data: encodeToBase64(announcement),
|
|
83
|
-
}),
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
if (!response.success || !response.data) {
|
|
87
|
-
throw new Error(response.error || 'Failed to broadcast outgoing session');
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return response.data.counter;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
async fetchAnnouncements(
|
|
94
|
-
limit: number = 50,
|
|
95
|
-
cursor?: string
|
|
96
|
-
): Promise<BulletinItem[]> {
|
|
97
|
-
const params = new URLSearchParams();
|
|
98
|
-
|
|
99
|
-
params.set('limit', limit.toString());
|
|
100
|
-
// Always pass 'after' parameter. If cursor is undefined, use '0' to fetch from the beginning.
|
|
101
|
-
// This ensures pagination works correctly: after=0 gets counters 1-20, after=20 gets 21-40, etc.
|
|
102
|
-
params.set('after', cursor ?? '0');
|
|
103
|
-
|
|
104
|
-
const url = `${this.baseUrl}${BULLETIN_ENDPOINT}?${params.toString()}`;
|
|
105
|
-
|
|
106
|
-
const response = await this.makeRequest<BulletinsPage>(url, {
|
|
107
|
-
method: 'GET',
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
if (!response.success || response.data == null) {
|
|
111
|
-
throw new Error(response.error || 'Failed to fetch announcements');
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
return response.data.map(item => ({
|
|
115
|
-
counter: item.counter,
|
|
116
|
-
data: decodeFromBase64(item.data),
|
|
117
|
-
}));
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
async fetchPublicKeyByUserId(userId: Uint8Array): Promise<string> {
|
|
121
|
-
const response = await this.makeRequest<{ value: string }>(
|
|
122
|
-
`${this.baseUrl}/auth/retrieve`,
|
|
123
|
-
{
|
|
124
|
-
method: 'POST',
|
|
125
|
-
headers: { 'Content-Type': 'application/json' },
|
|
126
|
-
body: JSON.stringify({ key: encodeToBase64(userId) }),
|
|
127
|
-
}
|
|
128
|
-
);
|
|
129
|
-
|
|
130
|
-
if (!response.success || !response.data) {
|
|
131
|
-
throw new Error(response.error || 'Failed to fetch public key');
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
if (!response.data.value) {
|
|
135
|
-
throw new Error('Public key not found');
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
return response.data.value;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
async postPublicKey(base64PublicKeys: string): Promise<string> {
|
|
142
|
-
const url = `${this.baseUrl}/auth`;
|
|
143
|
-
|
|
144
|
-
const response = await this.makeRequest<{ value: string }>(url, {
|
|
145
|
-
method: 'POST',
|
|
146
|
-
headers: { 'Content-Type': 'application/json' },
|
|
147
|
-
body: JSON.stringify({ value: base64PublicKeys }),
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
if (!response.success || !response.data) {
|
|
151
|
-
const errorMessage = response.error || 'Failed to store public key';
|
|
152
|
-
throw new Error(errorMessage);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
return response.data.value;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
private async makeRequest<T>(
|
|
159
|
-
url: string,
|
|
160
|
-
options: RequestInit
|
|
161
|
-
): Promise<MessageProtocolResponse<T>> {
|
|
162
|
-
let lastError: Error | null = null;
|
|
163
|
-
|
|
164
|
-
for (let attempt = 1; attempt <= this.retryAttempts; attempt++) {
|
|
165
|
-
try {
|
|
166
|
-
const controller = new AbortController();
|
|
167
|
-
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
168
|
-
|
|
169
|
-
const response = await fetch(url, {
|
|
170
|
-
...options,
|
|
171
|
-
signal: controller.signal,
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
clearTimeout(timeoutId);
|
|
175
|
-
|
|
176
|
-
if (!response.ok) {
|
|
177
|
-
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
const data = await response.json();
|
|
181
|
-
return { success: true, data };
|
|
182
|
-
} catch (error) {
|
|
183
|
-
lastError = error as Error;
|
|
184
|
-
console.warn(`Request attempt ${attempt} failed:`, error);
|
|
185
|
-
|
|
186
|
-
if (attempt < this.retryAttempts) {
|
|
187
|
-
// Exponential backoff
|
|
188
|
-
const delay = Math.pow(2, attempt) * 1000;
|
|
189
|
-
await new Promise(resolve => setTimeout(resolve, delay));
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
return {
|
|
195
|
-
success: false,
|
|
196
|
-
error: lastError?.message || 'Request failed after all retry attempts',
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
async changeNode(nodeUrl?: string): Promise<MessageProtocolResponse> {
|
|
201
|
-
return {
|
|
202
|
-
success: true,
|
|
203
|
-
data:
|
|
204
|
-
'This message protocol provider use a single node, so changing the node to ' +
|
|
205
|
-
nodeUrl +
|
|
206
|
-
' is not supported',
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
}
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Message Protocol Types and Interfaces
|
|
3
|
-
*
|
|
4
|
-
* Defines the core types and interfaces for message protocol operations.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
export type BulletinItem = {
|
|
8
|
-
counter: string;
|
|
9
|
-
data: Uint8Array;
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
export interface EncryptedMessage {
|
|
13
|
-
seeker: Uint8Array;
|
|
14
|
-
ciphertext: Uint8Array;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export interface MessageProtocolResponse<T = unknown> {
|
|
18
|
-
success: boolean;
|
|
19
|
-
data?: T;
|
|
20
|
-
error?: string;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Abstract interface for message protocol operations
|
|
25
|
-
*/
|
|
26
|
-
export interface IMessageProtocol {
|
|
27
|
-
/**
|
|
28
|
-
* Fetch encrypted messages for the provided set of seeker read keys
|
|
29
|
-
*/
|
|
30
|
-
fetchMessages(seekers: Uint8Array[]): Promise<EncryptedMessage[]>;
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Send an encrypted message to the key-value store
|
|
34
|
-
*/
|
|
35
|
-
sendMessage(message: EncryptedMessage): Promise<void>;
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Broadcast an outgoing session announcement produced by WASM.
|
|
39
|
-
* Returns the bulletin counter provided by the API.
|
|
40
|
-
*/
|
|
41
|
-
sendAnnouncement(announcement: Uint8Array): Promise<string>;
|
|
42
|
-
/**
|
|
43
|
-
* Fetch incoming discussion announcements from the bulletin storage.
|
|
44
|
-
* Returns raw announcement bytes as provided by the API.
|
|
45
|
-
* @param limit - Maximum number of announcements to fetch (default: 20)
|
|
46
|
-
* @param cursor - Optional cursor (counter) to fetch announcements after this value
|
|
47
|
-
*/
|
|
48
|
-
fetchAnnouncements(limit?: number, cursor?: string): Promise<BulletinItem[]>;
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Fetch public key by userId hash (base64 string)
|
|
52
|
-
* @param userId - Decoded userId bytes
|
|
53
|
-
* @returns Base64-encoded public keys
|
|
54
|
-
*/
|
|
55
|
-
fetchPublicKeyByUserId(userId: Uint8Array): Promise<string>;
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Store public key in the auth API
|
|
59
|
-
* @param base64PublicKeys - Base64-encoded public keys
|
|
60
|
-
* @returns The hash key (hex string) returned by the API
|
|
61
|
-
*/
|
|
62
|
-
postPublicKey(base64PublicKeys: string): Promise<string>;
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Change the current node provider
|
|
66
|
-
* @param nodeUrl - The URL of the new node
|
|
67
|
-
* @returns MessageProtocolResponse with the new node information
|
|
68
|
-
*/
|
|
69
|
-
changeNode(nodeUrl?: string): Promise<MessageProtocolResponse>;
|
|
70
|
-
}
|