@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.
Files changed (148) hide show
  1. package/dist/api/messageProtocol/index.d.ts +19 -0
  2. package/dist/api/messageProtocol/index.js +26 -0
  3. package/dist/api/messageProtocol/mock.d.ts +12 -0
  4. package/{src/api/messageProtocol/mock.ts → dist/api/messageProtocol/mock.js} +2 -3
  5. package/dist/api/messageProtocol/rest.d.ts +22 -0
  6. package/dist/api/messageProtocol/rest.js +161 -0
  7. package/dist/api/messageProtocol/types.d.ts +61 -0
  8. package/dist/api/messageProtocol/types.js +6 -0
  9. package/dist/assets/generated/wasm/README.md +281 -0
  10. package/dist/assets/generated/wasm/gossip_wasm.d.ts +638 -0
  11. package/dist/assets/generated/wasm/gossip_wasm.js +1557 -0
  12. package/dist/assets/generated/wasm/gossip_wasm_bg.wasm +0 -0
  13. package/dist/assets/generated/wasm/gossip_wasm_bg.wasm.d.ts +164 -0
  14. package/dist/assets/generated/wasm/package.json +15 -0
  15. package/dist/assets/generated/wasm-node/README.md +281 -0
  16. package/dist/assets/generated/wasm-node/gossip_wasm.d.ts +443 -0
  17. package/dist/assets/generated/wasm-node/gossip_wasm.js +1488 -0
  18. package/dist/assets/generated/wasm-node/gossip_wasm_bg.wasm +0 -0
  19. package/dist/assets/generated/wasm-node/gossip_wasm_bg.wasm.d.ts +164 -0
  20. package/dist/assets/generated/wasm-node/package.json +11 -0
  21. package/dist/config/protocol.d.ts +36 -0
  22. package/dist/config/protocol.js +77 -0
  23. package/dist/config/sdk.d.ts +82 -0
  24. package/dist/config/sdk.js +55 -0
  25. package/{src/contacts.ts → dist/contacts.d.ts} +11 -95
  26. package/dist/contacts.js +166 -0
  27. package/dist/core/SdkEventEmitter.d.ts +36 -0
  28. package/dist/core/SdkEventEmitter.js +59 -0
  29. package/dist/core/SdkPolling.d.ts +35 -0
  30. package/dist/core/SdkPolling.js +100 -0
  31. package/{src/core/index.ts → dist/core/index.d.ts} +0 -2
  32. package/dist/core/index.js +5 -0
  33. package/dist/crypto/bip39.d.ts +34 -0
  34. package/dist/crypto/bip39.js +62 -0
  35. package/dist/crypto/encryption.d.ts +37 -0
  36. package/dist/crypto/encryption.js +46 -0
  37. package/dist/db.d.ts +190 -0
  38. package/dist/db.js +311 -0
  39. package/dist/gossipSdk.d.ts +274 -0
  40. package/dist/gossipSdk.js +690 -0
  41. package/dist/index.d.ts +59 -0
  42. package/dist/index.js +61 -0
  43. package/dist/services/announcement.d.ts +43 -0
  44. package/dist/services/announcement.js +491 -0
  45. package/dist/services/auth.d.ts +37 -0
  46. package/dist/services/auth.js +76 -0
  47. package/dist/services/discussion.d.ts +63 -0
  48. package/dist/services/discussion.js +297 -0
  49. package/dist/services/message.d.ts +74 -0
  50. package/dist/services/message.js +826 -0
  51. package/dist/services/refresh.d.ts +41 -0
  52. package/dist/services/refresh.js +205 -0
  53. package/{src/sw.ts → dist/sw.d.ts} +1 -8
  54. package/dist/sw.js +10 -0
  55. package/dist/types/events.d.ts +80 -0
  56. package/dist/types/events.js +7 -0
  57. package/dist/types.d.ts +32 -0
  58. package/dist/types.js +7 -0
  59. package/dist/utils/base64.d.ts +10 -0
  60. package/dist/utils/base64.js +30 -0
  61. package/dist/utils/contacts.d.ts +42 -0
  62. package/dist/utils/contacts.js +113 -0
  63. package/dist/utils/discussions.d.ts +24 -0
  64. package/dist/utils/discussions.js +38 -0
  65. package/dist/utils/logs.d.ts +19 -0
  66. package/dist/utils/logs.js +89 -0
  67. package/dist/utils/messageSerialization.d.ts +64 -0
  68. package/dist/utils/messageSerialization.js +184 -0
  69. package/dist/utils/queue.d.ts +50 -0
  70. package/dist/utils/queue.js +110 -0
  71. package/dist/utils/type.d.ts +10 -0
  72. package/dist/utils/type.js +4 -0
  73. package/dist/utils/userId.d.ts +40 -0
  74. package/dist/utils/userId.js +90 -0
  75. package/dist/utils/validation.d.ts +50 -0
  76. package/dist/utils/validation.js +112 -0
  77. package/dist/utils.d.ts +30 -0
  78. package/{src/utils.ts → dist/utils.js} +9 -19
  79. package/dist/wasm/encryption.d.ts +56 -0
  80. package/{src/wasm/encryption.ts → dist/wasm/encryption.js} +22 -51
  81. package/dist/wasm/index.d.ts +10 -0
  82. package/{src/wasm/index.ts → dist/wasm/index.js} +1 -8
  83. package/dist/wasm/loader.d.ts +22 -0
  84. package/dist/wasm/loader.js +78 -0
  85. package/dist/wasm/session.d.ts +85 -0
  86. package/dist/wasm/session.js +226 -0
  87. package/dist/wasm/userKeys.d.ts +17 -0
  88. package/{src/wasm/userKeys.ts → dist/wasm/userKeys.js} +6 -13
  89. package/package.json +15 -2
  90. package/src/api/messageProtocol/index.ts +0 -53
  91. package/src/api/messageProtocol/rest.ts +0 -209
  92. package/src/api/messageProtocol/types.ts +0 -70
  93. package/src/config/protocol.ts +0 -97
  94. package/src/config/sdk.ts +0 -131
  95. package/src/core/SdkEventEmitter.ts +0 -91
  96. package/src/core/SdkPolling.ts +0 -134
  97. package/src/crypto/bip39.ts +0 -84
  98. package/src/crypto/encryption.ts +0 -77
  99. package/src/db.ts +0 -465
  100. package/src/gossipSdk.ts +0 -994
  101. package/src/index.ts +0 -211
  102. package/src/services/announcement.ts +0 -653
  103. package/src/services/auth.ts +0 -95
  104. package/src/services/discussion.ts +0 -380
  105. package/src/services/message.ts +0 -1055
  106. package/src/services/refresh.ts +0 -234
  107. package/src/types/events.ts +0 -108
  108. package/src/types.ts +0 -70
  109. package/src/utils/base64.ts +0 -39
  110. package/src/utils/contacts.ts +0 -161
  111. package/src/utils/discussions.ts +0 -55
  112. package/src/utils/logs.ts +0 -86
  113. package/src/utils/messageSerialization.ts +0 -257
  114. package/src/utils/queue.ts +0 -106
  115. package/src/utils/type.ts +0 -7
  116. package/src/utils/userId.ts +0 -114
  117. package/src/utils/validation.ts +0 -144
  118. package/src/wasm/loader.ts +0 -123
  119. package/src/wasm/session.ts +0 -276
  120. package/test/config/protocol.spec.ts +0 -31
  121. package/test/config/sdk.spec.ts +0 -163
  122. package/test/db/helpers.spec.ts +0 -142
  123. package/test/db/operations.spec.ts +0 -128
  124. package/test/db/states.spec.ts +0 -535
  125. package/test/integration/discussion-flow.spec.ts +0 -422
  126. package/test/integration/messaging-flow.spec.ts +0 -708
  127. package/test/integration/sdk-lifecycle.spec.ts +0 -325
  128. package/test/mocks/index.ts +0 -9
  129. package/test/mocks/mockMessageProtocol.ts +0 -100
  130. package/test/services/auth.spec.ts +0 -311
  131. package/test/services/discussion.spec.ts +0 -279
  132. package/test/services/message-deduplication.spec.ts +0 -299
  133. package/test/services/message-startup.spec.ts +0 -331
  134. package/test/services/message.spec.ts +0 -817
  135. package/test/services/refresh.spec.ts +0 -199
  136. package/test/services/session-status.spec.ts +0 -349
  137. package/test/session/wasm.spec.ts +0 -227
  138. package/test/setup.ts +0 -52
  139. package/test/utils/contacts.spec.ts +0 -156
  140. package/test/utils/discussions.spec.ts +0 -66
  141. package/test/utils/queue.spec.ts +0 -52
  142. package/test/utils/serialization.spec.ts +0 -120
  143. package/test/utils/userId.spec.ts +0 -120
  144. package/test/utils/validation.spec.ts +0 -223
  145. package/test/utils.ts +0 -212
  146. package/tsconfig.json +0 -26
  147. package/tsconfig.tsbuildinfo +0 -1
  148. 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: 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;
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.20260128094509",
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
- "build": "tsc && mkdir -p dist/assets/generated/wasm && cp -R src/assets/generated/wasm/* dist/assets/generated/wasm/",
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
- }