@massalabs/gossip-sdk 0.0.1

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.
Files changed (69) hide show
  1. package/README.md +484 -0
  2. package/package.json +41 -0
  3. package/src/api/messageProtocol/index.ts +53 -0
  4. package/src/api/messageProtocol/mock.ts +13 -0
  5. package/src/api/messageProtocol/rest.ts +209 -0
  6. package/src/api/messageProtocol/types.ts +70 -0
  7. package/src/config/protocol.ts +97 -0
  8. package/src/config/sdk.ts +131 -0
  9. package/src/contacts.ts +210 -0
  10. package/src/core/SdkEventEmitter.ts +91 -0
  11. package/src/core/SdkPolling.ts +134 -0
  12. package/src/core/index.ts +9 -0
  13. package/src/crypto/bip39.ts +84 -0
  14. package/src/crypto/encryption.ts +77 -0
  15. package/src/db.ts +465 -0
  16. package/src/gossipSdk.ts +994 -0
  17. package/src/index.ts +211 -0
  18. package/src/services/announcement.ts +653 -0
  19. package/src/services/auth.ts +95 -0
  20. package/src/services/discussion.ts +380 -0
  21. package/src/services/message.ts +1055 -0
  22. package/src/services/refresh.ts +234 -0
  23. package/src/sw.ts +17 -0
  24. package/src/types/events.ts +108 -0
  25. package/src/types.ts +70 -0
  26. package/src/utils/base64.ts +39 -0
  27. package/src/utils/contacts.ts +161 -0
  28. package/src/utils/discussions.ts +55 -0
  29. package/src/utils/logs.ts +86 -0
  30. package/src/utils/messageSerialization.ts +257 -0
  31. package/src/utils/queue.ts +106 -0
  32. package/src/utils/type.ts +7 -0
  33. package/src/utils/userId.ts +114 -0
  34. package/src/utils/validation.ts +144 -0
  35. package/src/utils.ts +47 -0
  36. package/src/wasm/encryption.ts +108 -0
  37. package/src/wasm/index.ts +20 -0
  38. package/src/wasm/loader.ts +123 -0
  39. package/src/wasm/session.ts +276 -0
  40. package/src/wasm/userKeys.ts +31 -0
  41. package/test/config/protocol.spec.ts +31 -0
  42. package/test/config/sdk.spec.ts +163 -0
  43. package/test/db/helpers.spec.ts +142 -0
  44. package/test/db/operations.spec.ts +128 -0
  45. package/test/db/states.spec.ts +535 -0
  46. package/test/integration/discussion-flow.spec.ts +422 -0
  47. package/test/integration/messaging-flow.spec.ts +708 -0
  48. package/test/integration/sdk-lifecycle.spec.ts +325 -0
  49. package/test/mocks/index.ts +9 -0
  50. package/test/mocks/mockMessageProtocol.ts +100 -0
  51. package/test/services/auth.spec.ts +311 -0
  52. package/test/services/discussion.spec.ts +279 -0
  53. package/test/services/message-deduplication.spec.ts +299 -0
  54. package/test/services/message-startup.spec.ts +331 -0
  55. package/test/services/message.spec.ts +817 -0
  56. package/test/services/refresh.spec.ts +199 -0
  57. package/test/services/session-status.spec.ts +349 -0
  58. package/test/session/wasm.spec.ts +227 -0
  59. package/test/setup.ts +52 -0
  60. package/test/utils/contacts.spec.ts +156 -0
  61. package/test/utils/discussions.spec.ts +66 -0
  62. package/test/utils/queue.spec.ts +52 -0
  63. package/test/utils/serialization.spec.ts +120 -0
  64. package/test/utils/userId.spec.ts +120 -0
  65. package/test/utils/validation.spec.ts +223 -0
  66. package/test/utils.ts +212 -0
  67. package/tsconfig.json +26 -0
  68. package/tsconfig.tsbuildinfo +1 -0
  69. package/vitest.config.ts +28 -0
package/src/utils.ts ADDED
@@ -0,0 +1,47 @@
1
+ /**
2
+ * SDK Utilities
3
+ *
4
+ * Helper functions for SDK configuration.
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * import { configureSdk } from 'gossip-sdk';
9
+ *
10
+ * configureSdk({
11
+ * db,
12
+ * protocolBaseUrl: 'https://api.example.com',
13
+ * });
14
+ * ```
15
+ */
16
+
17
+ import type { GossipDatabase } from './db';
18
+ import { setDb } from './db';
19
+ import { startWasmInitialization } from './wasm/loader';
20
+ import { setProtocolBaseUrl } from './config/protocol';
21
+
22
+ export interface SdkRuntimeConfig {
23
+ db?: GossipDatabase;
24
+ protocolBaseUrl?: string;
25
+ }
26
+
27
+ /**
28
+ * Configure runtime adapters for the SDK.
29
+ * Call this once during application startup.
30
+ *
31
+ * This also starts WASM initialization in the background.
32
+ *
33
+ * Note: Service instances (MessageService, AnnouncementService, etc.)
34
+ * should be created by the app with the required dependencies.
35
+ */
36
+ export function configureSdk(config: SdkRuntimeConfig): void {
37
+ if (config.db) {
38
+ setDb(config.db);
39
+ }
40
+
41
+ if (config.protocolBaseUrl) {
42
+ setProtocolBaseUrl(config.protocolBaseUrl);
43
+ }
44
+
45
+ // Start WASM initialization in the background (non-blocking)
46
+ startWasmInitialization();
47
+ }
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Encryption, Keys, and AEAD Support
3
+ *
4
+ * This file provides proxy functions for EncryptionKey, Nonce classes,
5
+ * and AEAD (Authenticated Encryption with Additional Data) operations,
6
+ * ensuring proper initialization before calling any WASM functions.
7
+ */
8
+
9
+ import { ensureWasmInitialized } from './loader';
10
+ import {
11
+ EncryptionKey,
12
+ Nonce,
13
+ aead_encrypt as _aead_encrypt,
14
+ aead_decrypt as _aead_decrypt,
15
+ } from '../assets/generated/wasm/gossip_wasm';
16
+
17
+ // Re-export classes
18
+ export { EncryptionKey, Nonce };
19
+
20
+ /**
21
+ * Generate a new random encryption key (64 bytes)
22
+ * This ensures WASM is initialized before calling
23
+ */
24
+ export async function generateEncryptionKey(): Promise<EncryptionKey> {
25
+ await ensureWasmInitialized();
26
+ return EncryptionKey.generate();
27
+ }
28
+
29
+ /**
30
+ * Generate a deterministic encryption key (64 bytes) from a seed string.
31
+ * This ensures WASM is initialized before calling.
32
+ */
33
+ export async function generateEncryptionKeyFromSeed(
34
+ seed: string,
35
+ salt: Uint8Array
36
+ ): Promise<EncryptionKey> {
37
+ await ensureWasmInitialized();
38
+ return EncryptionKey.from_seed(seed, salt);
39
+ }
40
+
41
+ /**
42
+ * Create an encryption key from raw bytes (must be 64 bytes)
43
+ * This ensures WASM is initialized before calling
44
+ */
45
+ export async function encryptionKeyFromBytes(
46
+ bytes: Uint8Array
47
+ ): Promise<EncryptionKey> {
48
+ await ensureWasmInitialized();
49
+ return EncryptionKey.from_bytes(bytes);
50
+ }
51
+
52
+ /**
53
+ * Generate a new random nonce (16 bytes)
54
+ * This ensures WASM is initialized before calling
55
+ */
56
+ export async function generateNonce(): Promise<Nonce> {
57
+ await ensureWasmInitialized();
58
+ return Nonce.generate();
59
+ }
60
+
61
+ /**
62
+ * Create a nonce from raw bytes (must be 16 bytes)
63
+ * This ensures WASM is initialized before calling
64
+ */
65
+ export async function nonceFromBytes(bytes: Uint8Array): Promise<Nonce> {
66
+ await ensureWasmInitialized();
67
+ return Nonce.from_bytes(bytes);
68
+ }
69
+
70
+ /**
71
+ * Encrypt data using AES-256-SIV authenticated encryption
72
+ * This ensures WASM is initialized before calling
73
+ *
74
+ * @param key - The encryption key (64 bytes)
75
+ * @param nonce - The nonce (16 bytes, should be unique per encryption)
76
+ * @param plaintext - The data to encrypt
77
+ * @param aad - Additional authenticated data (not encrypted, but authenticated)
78
+ * @returns The ciphertext with authentication tag appended
79
+ */
80
+ export async function encryptAead(
81
+ key: EncryptionKey,
82
+ nonce: Nonce,
83
+ plaintext: Uint8Array,
84
+ aad: Uint8Array
85
+ ): Promise<Uint8Array> {
86
+ await ensureWasmInitialized();
87
+ return _aead_encrypt(key, nonce, plaintext, aad);
88
+ }
89
+
90
+ /**
91
+ * Decrypt data using AES-256-SIV authenticated encryption
92
+ * This ensures WASM is initialized before calling
93
+ *
94
+ * @param key - The encryption key (64 bytes, must match encryption key)
95
+ * @param nonce - The nonce (16 bytes, must match encryption nonce)
96
+ * @param ciphertext - The encrypted data with authentication tag
97
+ * @param aad - Additional authenticated data (must match encryption AAD)
98
+ * @returns The decrypted plaintext, or undefined if authentication fails
99
+ */
100
+ export async function decryptAead(
101
+ key: EncryptionKey,
102
+ nonce: Nonce,
103
+ ciphertext: Uint8Array,
104
+ aad: Uint8Array
105
+ ): Promise<Uint8Array | undefined> {
106
+ await ensureWasmInitialized();
107
+ return _aead_decrypt(key, nonce, ciphertext, aad);
108
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * WASM Module Exports
3
+ *
4
+ * This file provides a clean interface for importing WASM modules
5
+ * and related functionality.
6
+ */
7
+
8
+ // Export modules
9
+ export { SessionModule, sessionStatusToString } from './session';
10
+
11
+ // Export initialization functions
12
+ export {
13
+ initializeWasm,
14
+ ensureWasmInitialized,
15
+ startWasmInitialization,
16
+ } from './loader';
17
+
18
+ // Export specialized WASM functionality
19
+ export * from './encryption';
20
+ export * from './userKeys';
@@ -0,0 +1,123 @@
1
+ /**
2
+ * WASM Module Loader and Initialization Service
3
+ *
4
+ * This file handles both WASM core initialization and module loading.
5
+ * It ensures WASM modules are initialized once and properly throughout
6
+ * the application lifecycle.
7
+ */
8
+
9
+ import init from '../assets/generated/wasm/gossip_wasm';
10
+
11
+ /**
12
+ * Check if we're running in Node.js environment
13
+ */
14
+ function isNodeEnvironment(): boolean {
15
+ return (
16
+ typeof process !== 'undefined' &&
17
+ process.versions != null &&
18
+ process.versions.node != null
19
+ );
20
+ }
21
+
22
+ /**
23
+ * Load WASM module for Node.js environment using fs.readFileSync
24
+ */
25
+ async function loadWasmForNode(): Promise<WebAssembly.Module> {
26
+ // Dynamic import to avoid bundling Node.js modules in browser builds
27
+ const fs = await import('node:fs');
28
+ const path = await import('node:path');
29
+ const { fileURLToPath } = await import('node:url');
30
+
31
+ // Get the directory of the current module
32
+ const __filename = fileURLToPath(import.meta.url);
33
+ const __dirname = path.dirname(__filename);
34
+
35
+ // Resolve path to WASM file - WASM is in the SDK's generated folder
36
+ const wasmPath = path.resolve(
37
+ __dirname,
38
+ '../assets/generated/wasm/gossip_wasm_bg.wasm'
39
+ );
40
+
41
+ // Read WASM file as binary
42
+ const wasmBuffer = fs.readFileSync(wasmPath);
43
+
44
+ // Instantiate WASM module
45
+ const wasmModule = await WebAssembly.compile(wasmBuffer);
46
+ return wasmModule;
47
+ }
48
+
49
+ /**
50
+ * WASM Initialization State
51
+ */
52
+ let isInitializing = false;
53
+ let isInitialized = false;
54
+ let initializationPromise: Promise<void> | null = null;
55
+ let initError: Error | null = null;
56
+
57
+ /**
58
+ * Initialize WASM modules if not already initialized
59
+ * This function is idempotent - safe to call multiple times
60
+ */
61
+ export async function initializeWasm(): Promise<void> {
62
+ // If already initialized, return immediately
63
+ if (isInitialized) {
64
+ return;
65
+ }
66
+
67
+ // If initialization is in progress, wait for it to complete
68
+ if (isInitializing && initializationPromise) {
69
+ return initializationPromise;
70
+ }
71
+
72
+ // Start initialization
73
+ isInitializing = true;
74
+ initError = null;
75
+
76
+ initializationPromise = (async () => {
77
+ try {
78
+ // In Node.js environment, load WASM using fs.readFileSync
79
+ if (isNodeEnvironment()) {
80
+ const wasmModule = await loadWasmForNode();
81
+ await init(wasmModule);
82
+ } else {
83
+ // In browser/jsdom, use default init (which uses fetch)
84
+ await init();
85
+ }
86
+ isInitialized = true;
87
+ isInitializing = false;
88
+ } catch (error) {
89
+ initError = error as Error;
90
+ isInitializing = false;
91
+ console.error('[WASM] Failed to initialize WASM modules:', error);
92
+ throw error;
93
+ }
94
+ })();
95
+
96
+ return initializationPromise;
97
+ }
98
+
99
+ /**
100
+ * Ensure WASM is initialized, throwing an error if initialization failed
101
+ */
102
+ export async function ensureWasmInitialized(): Promise<void> {
103
+ await initializeWasm();
104
+
105
+ if (initError) {
106
+ throw new Error(`WASM initialization failed: ${initError.message}`);
107
+ }
108
+
109
+ if (!isInitialized) {
110
+ throw new Error('WASM not initialized');
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Start WASM initialization in the background.
116
+ * Call this early in the app lifecycle (for example in main.tsx).
117
+ */
118
+ export function startWasmInitialization(): void {
119
+ // Fire and forget - start initialization in background
120
+ initializeWasm().catch(error => {
121
+ console.error('[WASM] Background initialization error:', error);
122
+ });
123
+ }
@@ -0,0 +1,276 @@
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
+
8
+ import {
9
+ SessionManagerWrapper,
10
+ UserPublicKeys,
11
+ UserSecretKeys,
12
+ ReceiveMessageOutput,
13
+ SendMessageOutput,
14
+ SessionStatus,
15
+ EncryptionKey,
16
+ SessionConfig,
17
+ AnnouncementResult,
18
+ UserKeys,
19
+ } from '../assets/generated/wasm/gossip_wasm';
20
+ import { UserProfile } from '../db';
21
+ import { encodeUserId } from '../utils/userId';
22
+
23
+ export class SessionModule {
24
+ private sessionManager: SessionManagerWrapper | null = null;
25
+ private onPersist?: () => Promise<void>; // Async callback for persistence
26
+ public ourPk: UserPublicKeys;
27
+ public ourSk: UserSecretKeys;
28
+ public userId: Uint8Array;
29
+ public userIdEncoded: string;
30
+
31
+ constructor(
32
+ userKeys: UserKeys,
33
+ onPersist?: () => Promise<void>,
34
+ config?: SessionConfig
35
+ ) {
36
+ this.ourPk = userKeys.public_keys();
37
+ this.ourSk = userKeys.secret_keys();
38
+ this.userId = this.ourPk.derive_id();
39
+ this.userIdEncoded = encodeUserId(this.userId);
40
+
41
+ const sessionConfig = config ?? SessionConfig.new_default();
42
+ this.sessionManager = new SessionManagerWrapper(sessionConfig);
43
+ this.onPersist = onPersist;
44
+ }
45
+
46
+ /**
47
+ * Set the persistence callback
48
+ */
49
+ setOnPersist(callback: () => Promise<void>): void {
50
+ this.onPersist = callback;
51
+ }
52
+
53
+ /**
54
+ * Helper to trigger persistence after state changes.
55
+ * Returns a promise that resolves when persistence is complete.
56
+ * IMPORTANT: Callers should await this before sending data to network
57
+ * to prevent state loss on app crash.
58
+ */
59
+ private async persistIfNeeded(): Promise<void> {
60
+ if (this.onPersist) {
61
+ await this.onPersist();
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Trigger persistence explicitly and wait for completion.
67
+ * Use this when you need to ensure state is saved before proceeding.
68
+ */
69
+ async persist(): Promise<void> {
70
+ await this.persistIfNeeded();
71
+ }
72
+
73
+ /**
74
+ * Initialize session from an encrypted blob
75
+ */
76
+ load(profile: UserProfile, encryptionKey: EncryptionKey): void {
77
+ // Clean up existing session if any
78
+ this.cleanup();
79
+
80
+ this.sessionManager = SessionManagerWrapper.from_encrypted_blob(
81
+ profile.session,
82
+ encryptionKey
83
+ );
84
+ }
85
+
86
+ /**
87
+ * Serialize session to an encrypted blob
88
+ */
89
+ toEncryptedBlob(key: EncryptionKey): Uint8Array {
90
+ if (!this.sessionManager) {
91
+ throw new Error('Session manager is not initialized');
92
+ }
93
+
94
+ return this.sessionManager.to_encrypted_blob(key);
95
+ }
96
+
97
+ cleanup(): void {
98
+ this.sessionManager?.free();
99
+ this.sessionManager = null;
100
+ }
101
+
102
+ /**
103
+ * Establish an outgoing session with a peer via the underlying WASM wrapper
104
+ * @param peerPk - The peer's public keys
105
+ * @param userData - Optional user data to include in the announcement (defaults to empty array)
106
+ * @returns The announcement bytes to publish
107
+ */
108
+ async establishOutgoingSession(
109
+ peerPk: UserPublicKeys,
110
+ userData?: Uint8Array
111
+ ): Promise<Uint8Array> {
112
+ if (!this.sessionManager) {
113
+ throw new Error('Session manager is not initialized');
114
+ }
115
+
116
+ const userDataBytes = userData ?? new Uint8Array(0);
117
+ const result = this.sessionManager.establish_outgoing_session(
118
+ peerPk,
119
+ this.ourPk,
120
+ this.ourSk,
121
+ userDataBytes
122
+ );
123
+
124
+ if (result.length === 0) {
125
+ throw new Error(
126
+ 'Failed to establish outgoing session. Session manager returned empty announcement bytes.'
127
+ );
128
+ }
129
+
130
+ await this.persistIfNeeded();
131
+ return result;
132
+ }
133
+
134
+ /**
135
+ * Feed an incoming announcement into the session manager
136
+ * @returns AnnouncementResult containing the announcer's public keys, timestamp, and user data, or undefined if invalid
137
+ */
138
+ async feedIncomingAnnouncement(
139
+ announcementBytes: Uint8Array
140
+ ): Promise<AnnouncementResult | undefined> {
141
+ if (!this.sessionManager) {
142
+ throw new Error('Session manager is not initialized');
143
+ }
144
+
145
+ const result = this.sessionManager.feed_incoming_announcement(
146
+ announcementBytes,
147
+ this.ourPk,
148
+ this.ourSk
149
+ );
150
+
151
+ if (result) {
152
+ await this.persistIfNeeded();
153
+ }
154
+ return result;
155
+ }
156
+
157
+ /**
158
+ * Get the list of message board read keys (seekers) to monitor
159
+ */
160
+ getMessageBoardReadKeys(): Array<Uint8Array> {
161
+ if (!this.sessionManager) {
162
+ throw new Error('Session manager is not initialized');
163
+ }
164
+
165
+ return this.sessionManager.get_message_board_read_keys();
166
+ }
167
+
168
+ /**
169
+ * Process an incoming ciphertext from the message board
170
+ */
171
+ async feedIncomingMessageBoardRead(
172
+ seeker: Uint8Array,
173
+ ciphertext: Uint8Array
174
+ ): Promise<ReceiveMessageOutput | undefined> {
175
+ if (!this.sessionManager) {
176
+ throw new Error('Session manager is not initialized');
177
+ }
178
+
179
+ const result = this.sessionManager.feed_incoming_message_board_read(
180
+ seeker,
181
+ ciphertext,
182
+ this.ourSk
183
+ );
184
+
185
+ await this.persistIfNeeded();
186
+ return result;
187
+ }
188
+
189
+ /**
190
+ * Send a message to a peer.
191
+ * IMPORTANT: This persists session state before returning.
192
+ * The returned output should only be sent to network AFTER this resolves.
193
+ */
194
+ async sendMessage(
195
+ peerId: Uint8Array,
196
+ message: Uint8Array
197
+ ): Promise<SendMessageOutput | undefined> {
198
+ if (!this.sessionManager) {
199
+ throw new Error('Session manager is not initialized');
200
+ }
201
+
202
+ const result = this.sessionManager.send_message(peerId, message);
203
+ // CRITICAL: Persist session state BEFORE returning
204
+ // This ensures state is saved before the encrypted message goes on the network
205
+ await this.persistIfNeeded();
206
+ return result;
207
+ }
208
+
209
+ /**
210
+ * List all known peer IDs
211
+ */
212
+ peerList(): Array<Uint8Array> {
213
+ if (!this.sessionManager) {
214
+ throw new Error('Session manager is not initialized');
215
+ }
216
+
217
+ return this.sessionManager.peer_list();
218
+ }
219
+
220
+ /**
221
+ * Get the session status for a peer
222
+ */
223
+ peerSessionStatus(peerId: Uint8Array): SessionStatus {
224
+ if (!this.sessionManager) {
225
+ throw new Error('Session manager is not initialized');
226
+ }
227
+
228
+ return this.sessionManager.peer_session_status(peerId);
229
+ }
230
+
231
+ /**
232
+ * Discard a peer and all associated session state
233
+ */
234
+ async peerDiscard(peerId: Uint8Array): Promise<void> {
235
+ if (!this.sessionManager) {
236
+ throw new Error('Session manager is not initialized');
237
+ }
238
+
239
+ this.sessionManager.peer_discard(peerId);
240
+ await this.persistIfNeeded();
241
+ }
242
+
243
+ /**
244
+ * Refresh sessions, returning peer IDs that need keep-alive messages
245
+ */
246
+ async refresh(): Promise<Array<Uint8Array>> {
247
+ if (!this.sessionManager) {
248
+ throw new Error('Session manager is not initialized');
249
+ }
250
+
251
+ const result = this.sessionManager.refresh();
252
+ await this.persistIfNeeded();
253
+ return result;
254
+ }
255
+ }
256
+
257
+ export function sessionStatusToString(status: SessionStatus): string {
258
+ switch (status) {
259
+ case SessionStatus.Active:
260
+ return 'Active';
261
+ case SessionStatus.UnknownPeer:
262
+ return 'UnknownPeer';
263
+ case SessionStatus.NoSession:
264
+ return 'NoSession';
265
+ case SessionStatus.PeerRequested:
266
+ return 'PeerRequested';
267
+ case SessionStatus.SelfRequested:
268
+ return 'SelfRequested';
269
+ case SessionStatus.Killed:
270
+ return 'Killed';
271
+ case SessionStatus.Saturated:
272
+ return 'Saturated';
273
+ default:
274
+ throw new Error(`Unknown session status: ${status}`);
275
+ }
276
+ }
@@ -0,0 +1,31 @@
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
+
8
+ import { ensureWasmInitialized } from './loader';
9
+ import {
10
+ generate_user_keys as _generate_user_keys,
11
+ UserKeys,
12
+ } from '../assets/generated/wasm/gossip_wasm';
13
+
14
+ // Re-export classes
15
+ export { UserKeys };
16
+
17
+ /**
18
+ * Generate user keys from a passphrase using password-based key derivation
19
+ * This ensures WASM is initialized before calling
20
+ *
21
+ * @param passphrase - The user's passphrase
22
+ * @param secondaryKey - A 32-byte secondary public key
23
+ * @returns UserKeys object containing public and secret keys
24
+ */
25
+ export async function generateUserKeys(passphrase: string): Promise<UserKeys> {
26
+ await ensureWasmInitialized();
27
+ // The actual WASM function is synchronous, so we can call it directly
28
+ const keys = _generate_user_keys(passphrase);
29
+
30
+ return keys;
31
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Protocol config tests
3
+ */
4
+
5
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
6
+ import {
7
+ protocolConfig,
8
+ setProtocolBaseUrl,
9
+ resetProtocolBaseUrl,
10
+ } from '../../src/config/protocol';
11
+
12
+ describe('protocol config', () => {
13
+ beforeEach(() => {
14
+ resetProtocolBaseUrl();
15
+ });
16
+
17
+ afterEach(() => {
18
+ resetProtocolBaseUrl();
19
+ });
20
+
21
+ it('uses runtime override when provided', () => {
22
+ setProtocolBaseUrl('https://custom.example.com/api');
23
+ expect(protocolConfig.baseUrl).toBe('https://custom.example.com/api');
24
+ });
25
+
26
+ it('resets to default after override', () => {
27
+ setProtocolBaseUrl('https://custom.example.com/api');
28
+ resetProtocolBaseUrl();
29
+ expect(protocolConfig.baseUrl).toBe('https://api.usegossip.com/api');
30
+ });
31
+ });