@massalabs/gossip-sdk 0.0.1 → 0.0.2-dev.20260128111120

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