@massalabs/gossip-sdk 0.0.2-dev.20260220053835 → 0.0.2-dev.20260220083849

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.
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Auth Protocol — public key publishing & retrieval
3
+ */
4
+ import { RestClient } from './restClient';
5
+ export interface IAuthProtocol {
6
+ /** Fetches the base64-encoded public key for the given user ID. */
7
+ fetchPublicKeyByUserId(userId: Uint8Array): Promise<string>;
8
+ /** Publishes the given base64-encoded public key to the server. */
9
+ postPublicKey(base64PublicKeys: string): Promise<string>;
10
+ }
11
+ export declare function createAuthProtocol(config?: Partial<{
12
+ baseUrl: string;
13
+ timeout: number;
14
+ retryAttempts: number;
15
+ }>): IAuthProtocol;
16
+ export declare class RestAuthProtocol extends RestClient implements IAuthProtocol {
17
+ fetchPublicKeyByUserId(userId: Uint8Array): Promise<string>;
18
+ postPublicKey(base64PublicKeys: string): Promise<string>;
19
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Auth Protocol — public key publishing & retrieval
3
+ */
4
+ import { RestClient } from './restClient';
5
+ import { encodeToBase64 } from '../utils/base64';
6
+ import { protocolConfig } from '../config/protocol';
7
+ export function createAuthProtocol(config) {
8
+ return new RestAuthProtocol(config?.baseUrl ?? protocolConfig.baseUrl, config?.timeout ?? protocolConfig.timeout, config?.retryAttempts ?? protocolConfig.retryAttempts);
9
+ }
10
+ export class RestAuthProtocol extends RestClient {
11
+ async fetchPublicKeyByUserId(userId) {
12
+ const response = await this.makeRequest(`${this.baseUrl}/auth/retrieve`, {
13
+ method: 'POST',
14
+ headers: { 'Content-Type': 'application/json' },
15
+ body: JSON.stringify({ key: encodeToBase64(userId) }),
16
+ });
17
+ if (!response.success || !response.data) {
18
+ throw new Error(response.error || 'Failed to fetch public key');
19
+ }
20
+ if (!response.data.value) {
21
+ throw new Error('Public key not found');
22
+ }
23
+ return response.data.value;
24
+ }
25
+ async postPublicKey(base64PublicKeys) {
26
+ const url = `${this.baseUrl}/auth`;
27
+ const response = await this.makeRequest(url, {
28
+ method: 'POST',
29
+ headers: { 'Content-Type': 'application/json' },
30
+ body: JSON.stringify({ value: base64PublicKeys }),
31
+ });
32
+ if (!response.success || !response.data) {
33
+ throw new Error(response.error || 'Failed to store public key');
34
+ }
35
+ return response.data.value;
36
+ }
37
+ }
@@ -2,22 +2,16 @@
2
2
  * REST API implementation of the message protocol
3
3
  */
4
4
  import { BulletinItem, EncryptedMessage, IMessageProtocol, MessageProtocolResponse } from './types';
5
+ import { RestClient } from '../restClient';
5
6
  export type BulletinsPage = {
6
7
  counter: string;
7
8
  data: string;
8
9
  }[];
9
- export declare class RestMessageProtocol implements IMessageProtocol {
10
- private baseUrl;
11
- private timeout;
12
- private retryAttempts;
13
- constructor(baseUrl: string, timeout?: number, retryAttempts?: number);
10
+ export declare class RestMessageProtocol extends RestClient implements IMessageProtocol {
14
11
  fetchMessages(seekers: Uint8Array[]): Promise<EncryptedMessage[]>;
15
12
  sendMessage(message: EncryptedMessage): Promise<void>;
16
13
  sendAnnouncement(announcement: Uint8Array): Promise<string>;
17
14
  fetchAnnouncements(limit?: number, cursor?: string): Promise<BulletinItem[]>;
18
15
  fetchBulletinCounter(): Promise<string>;
19
- fetchPublicKeyByUserId(userId: Uint8Array): Promise<string>;
20
- postPublicKey(base64PublicKeys: string): Promise<string>;
21
- private makeRequest;
22
16
  changeNode(nodeUrl?: string): Promise<MessageProtocolResponse>;
23
17
  }
@@ -1,30 +1,11 @@
1
1
  /**
2
2
  * REST API implementation of the message protocol
3
3
  */
4
+ import { RestClient } from '../restClient';
4
5
  import { encodeToBase64, decodeFromBase64 } from '../../utils/base64';
5
6
  const BULLETIN_ENDPOINT = '/bulletin';
6
7
  const MESSAGES_ENDPOINT = '/messages';
7
- export class RestMessageProtocol {
8
- constructor(baseUrl, timeout = 10000, retryAttempts = 3) {
9
- Object.defineProperty(this, "baseUrl", {
10
- enumerable: true,
11
- configurable: true,
12
- writable: true,
13
- value: baseUrl
14
- });
15
- Object.defineProperty(this, "timeout", {
16
- enumerable: true,
17
- configurable: true,
18
- writable: true,
19
- value: timeout
20
- });
21
- Object.defineProperty(this, "retryAttempts", {
22
- enumerable: true,
23
- configurable: true,
24
- writable: true,
25
- value: retryAttempts
26
- });
27
- }
8
+ export class RestMessageProtocol extends RestClient {
28
9
  // TODO: Implement a fetch with pagination to avoid fetching all messages at once
29
10
  async fetchMessages(seekers) {
30
11
  const url = `${this.baseUrl}${MESSAGES_ENDPOINT}/fetch`;
@@ -101,65 +82,6 @@ export class RestMessageProtocol {
101
82
  }
102
83
  return response.data.counter;
103
84
  }
104
- async fetchPublicKeyByUserId(userId) {
105
- const response = await this.makeRequest(`${this.baseUrl}/auth/retrieve`, {
106
- method: 'POST',
107
- headers: { 'Content-Type': 'application/json' },
108
- body: JSON.stringify({ key: encodeToBase64(userId) }),
109
- });
110
- if (!response.success || !response.data) {
111
- throw new Error(response.error || 'Failed to fetch public key');
112
- }
113
- if (!response.data.value) {
114
- throw new Error('Public key not found');
115
- }
116
- return response.data.value;
117
- }
118
- async postPublicKey(base64PublicKeys) {
119
- const url = `${this.baseUrl}/auth`;
120
- const response = await this.makeRequest(url, {
121
- method: 'POST',
122
- headers: { 'Content-Type': 'application/json' },
123
- body: JSON.stringify({ value: base64PublicKeys }),
124
- });
125
- if (!response.success || !response.data) {
126
- const errorMessage = response.error || 'Failed to store public key';
127
- throw new Error(errorMessage);
128
- }
129
- return response.data.value;
130
- }
131
- async makeRequest(url, options) {
132
- let lastError = null;
133
- for (let attempt = 1; attempt <= this.retryAttempts; attempt++) {
134
- try {
135
- const controller = new AbortController();
136
- const timeoutId = setTimeout(() => controller.abort(), this.timeout);
137
- const response = await fetch(url, {
138
- ...options,
139
- signal: controller.signal,
140
- });
141
- clearTimeout(timeoutId);
142
- if (!response.ok) {
143
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
144
- }
145
- const data = await response.json();
146
- return { success: true, data };
147
- }
148
- catch (error) {
149
- lastError = error;
150
- console.warn(`Request attempt ${attempt} failed:`, error);
151
- if (attempt < this.retryAttempts) {
152
- // Exponential backoff
153
- const delay = Math.pow(2, attempt) * 1000;
154
- await new Promise(resolve => setTimeout(resolve, delay));
155
- }
156
- }
157
- }
158
- return {
159
- success: false,
160
- error: lastError?.message || 'Request failed after all retry attempts',
161
- };
162
- }
163
85
  async changeNode(nodeUrl) {
164
86
  return {
165
87
  success: true,
@@ -44,18 +44,6 @@ export interface IMessageProtocol {
44
44
  * Fetch the latest bulletin counter from the API without downloading announcement data.
45
45
  */
46
46
  fetchBulletinCounter(): Promise<string>;
47
- /**
48
- * Fetch public key by userId hash (base64 string)
49
- * @param userId - Decoded userId bytes
50
- * @returns Base64-encoded public keys
51
- */
52
- fetchPublicKeyByUserId(userId: Uint8Array): Promise<string>;
53
- /**
54
- * Store public key in the auth API
55
- * @param base64PublicKeys - Base64-encoded public keys
56
- * @returns The hash key (hex string) returned by the API
57
- */
58
- postPublicKey(base64PublicKeys: string): Promise<string>;
59
47
  /**
60
48
  * Change the current node provider
61
49
  * @param nodeUrl - The URL of the new node
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Base REST client with retry and timeout support.
3
+ * Shared by RestMessageProtocol and RestAuthProtocol.
4
+ */
5
+ export interface RestResponse<T = unknown> {
6
+ success: boolean;
7
+ data?: T;
8
+ error?: string;
9
+ }
10
+ export declare class RestClient {
11
+ protected baseUrl: string;
12
+ protected timeout: number;
13
+ protected retryAttempts: number;
14
+ constructor(baseUrl: string, timeout?: number, retryAttempts?: number);
15
+ protected makeRequest<T>(url: string, options: RequestInit): Promise<RestResponse<T>>;
16
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Base REST client with retry and timeout support.
3
+ * Shared by RestMessageProtocol and RestAuthProtocol.
4
+ */
5
+ export class RestClient {
6
+ constructor(baseUrl, timeout = 10000, retryAttempts = 3) {
7
+ Object.defineProperty(this, "baseUrl", {
8
+ enumerable: true,
9
+ configurable: true,
10
+ writable: true,
11
+ value: baseUrl
12
+ });
13
+ Object.defineProperty(this, "timeout", {
14
+ enumerable: true,
15
+ configurable: true,
16
+ writable: true,
17
+ value: timeout
18
+ });
19
+ Object.defineProperty(this, "retryAttempts", {
20
+ enumerable: true,
21
+ configurable: true,
22
+ writable: true,
23
+ value: retryAttempts
24
+ });
25
+ }
26
+ async makeRequest(url, options) {
27
+ let lastError = null;
28
+ for (let attempt = 1; attempt <= this.retryAttempts; attempt++) {
29
+ try {
30
+ const controller = new AbortController();
31
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
32
+ const response = await fetch(url, {
33
+ ...options,
34
+ signal: controller.signal,
35
+ });
36
+ clearTimeout(timeoutId);
37
+ if (!response.ok) {
38
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
39
+ }
40
+ const data = await response.json();
41
+ return { success: true, data };
42
+ }
43
+ catch (error) {
44
+ lastError = error;
45
+ console.warn(`Request attempt ${attempt} failed:`, error);
46
+ if (attempt < this.retryAttempts) {
47
+ const delay = Math.pow(2, attempt) * 1000;
48
+ await new Promise(resolve => setTimeout(resolve, delay));
49
+ }
50
+ }
51
+ }
52
+ return {
53
+ success: false,
54
+ error: lastError?.message || 'Request failed after all retry attempts',
55
+ };
56
+ }
57
+ }
package/dist/gossip.js CHANGED
@@ -41,6 +41,7 @@
41
41
  import { MessageStatus, } from './db';
42
42
  import { toDiscussion, toSortedDiscussions } from './utils/discussions';
43
43
  import { createMessageProtocol } from './api/messageProtocol';
44
+ import { createAuthProtocol } from './api/authProtocol';
44
45
  import { setProtocolBaseUrl } from './config/protocol';
45
46
  import { defaultSdkConfig, mergeConfig, } from './config/sdk';
46
47
  import { startWasmInitialization, ensureWasmInitialized } from './wasm/loader';
@@ -180,8 +181,8 @@ class GossipSdk {
180
181
  console.log('[GossipSdk] SQLite initialized');
181
182
  // Create message protocol
182
183
  const messageProtocol = createMessageProtocol();
183
- // Create auth service (doesn't need session)
184
- this._auth = new AuthService(messageProtocol);
184
+ // Create auth protocol + service (doesn't need session)
185
+ this._auth = new AuthService(createAuthProtocol());
185
186
  this.state = {
186
187
  status: SdkStatus.INITIALIZED,
187
188
  messageProtocol,
@@ -4,18 +4,17 @@
4
4
  * Handles storing and retrieving public keys by userId hash via the auth API.
5
5
  */
6
6
  import { UserPublicKeys } from '../wasm/bindings';
7
- import { IMessageProtocol } from '../api/messageProtocol/types';
7
+ import { IAuthProtocol } from '../api/authProtocol';
8
8
  export declare class AuthService {
9
- messageProtocol: IMessageProtocol;
10
- constructor(messageProtocol: IMessageProtocol);
9
+ authProtocol: IAuthProtocol;
10
+ constructor(authProtocol: IAuthProtocol);
11
11
  /**
12
12
  * Fetch public key by userId
13
13
  * @param userId - Bech32-encoded userId (e.g., "gossip1...")
14
14
  */
15
15
  fetchPublicKeyByUserId(userId: string): Promise<UserPublicKeys>;
16
16
  /**
17
- * Ensure public key is published (check first, then publish if needed).
18
- * If no user profile exists, the key is still published so the gossip ID is discoverable.
17
+ * Ensure public key is published. Checks the server first, only publishes if not found.
19
18
  * @param publicKeys - UserPublicKeys instance
20
19
  * @param userId - Bech32-encoded userId (e.g., "gossip1...")
21
20
  */
@@ -6,14 +6,13 @@
6
6
  import { UserPublicKeys } from '../wasm/bindings';
7
7
  import { decodeUserId } from '../utils/userId';
8
8
  import { encodeToBase64, decodeFromBase64 } from '../utils/base64';
9
- import { getUserProfileField, updateUserProfileById } from '../queries';
10
9
  export class AuthService {
11
- constructor(messageProtocol) {
12
- Object.defineProperty(this, "messageProtocol", {
10
+ constructor(authProtocol) {
11
+ Object.defineProperty(this, "authProtocol", {
13
12
  enumerable: true,
14
13
  configurable: true,
15
14
  writable: true,
16
- value: messageProtocol
15
+ value: authProtocol
17
16
  });
18
17
  }
19
18
  /**
@@ -22,7 +21,7 @@ export class AuthService {
22
21
  */
23
22
  async fetchPublicKeyByUserId(userId) {
24
23
  try {
25
- const base64PublicKey = await this.messageProtocol.fetchPublicKeyByUserId(decodeUserId(userId));
24
+ const base64PublicKey = await this.authProtocol.fetchPublicKeyByUserId(decodeUserId(userId));
26
25
  return UserPublicKeys.from_bytes(decodeFromBase64(base64PublicKey));
27
26
  }
28
27
  catch (err) {
@@ -30,29 +29,22 @@ export class AuthService {
30
29
  }
31
30
  }
32
31
  /**
33
- * Ensure public key is published (check first, then publish if needed).
34
- * If no user profile exists, the key is still published so the gossip ID is discoverable.
32
+ * Ensure public key is published. Checks the server first, only publishes if not found.
35
33
  * @param publicKeys - UserPublicKeys instance
36
34
  * @param userId - Bech32-encoded userId (e.g., "gossip1...")
37
35
  */
38
36
  async ensurePublicKeyPublished(publicKeys, userId) {
39
- const profile = await getUserProfileField(userId);
40
- if (profile) {
41
- const lastPush = profile.lastPublicKeyPush;
42
- if (lastPush && !moreThanOneWeekAgo(lastPush)) {
43
- return;
44
- }
37
+ // Check if our key is already on the server
38
+ try {
39
+ await this.authProtocol.fetchPublicKeyByUserId(decodeUserId(userId));
40
+ return; // Key exists on server, nothing to do
45
41
  }
46
- await this.messageProtocol.postPublicKey(encodeToBase64(publicKeys.to_bytes()));
47
- if (profile) {
48
- await updateUserProfileById(userId, { lastPublicKeyPush: new Date() });
42
+ catch {
43
+ // Key not found on server — publish it
49
44
  }
45
+ await this.authProtocol.postPublicKey(encodeToBase64(publicKeys.to_bytes()));
50
46
  }
51
47
  }
52
- const ONE_WEEK_IN_MILLIS = 7 * 24 * 60 * 60 * 1000;
53
- function moreThanOneWeekAgo(date) {
54
- return Date.now() - date.getTime() >= ONE_WEEK_IN_MILLIS;
55
- }
56
48
  export const PUBLIC_KEY_NOT_FOUND_ERROR = 'Public key not found';
57
49
  export const PUBLIC_KEY_NOT_FOUND_MESSAGE = 'Contact public key not found. It may not be published yet.';
58
50
  export const FAILED_TO_FETCH_ERROR = 'Failed to fetch';
@@ -483,7 +483,7 @@ export class MessageService {
483
483
  };
484
484
  }
485
485
  if (message.replyTo) {
486
- const originalMessage = await this.findMessageByMsgId(message.replyTo.originalMsgId, message.ownerUserId);
486
+ const originalMessage = await this.findMessageByMsgId(message.replyTo.originalMsgId, message.ownerUserId, message.contactUserId);
487
487
  if (!originalMessage) {
488
488
  return {
489
489
  success: false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@massalabs/gossip-sdk",
3
- "version": "0.0.2-dev.20260220053835",
3
+ "version": "0.0.2-dev.20260220083849",
4
4
  "description": "Gossip SDK for automation, chatbot, and integration use cases",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",