@massalabs/gossip-sdk 0.0.2-dev.20260217151032 → 0.0.2-dev.20260218140506

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.
@@ -15,6 +15,7 @@ export declare class RestMessageProtocol implements IMessageProtocol {
15
15
  sendMessage(message: EncryptedMessage): Promise<void>;
16
16
  sendAnnouncement(announcement: Uint8Array): Promise<string>;
17
17
  fetchAnnouncements(limit?: number, cursor?: string): Promise<BulletinItem[]>;
18
+ fetchBulletinCounter(): Promise<string>;
18
19
  fetchPublicKeyByUserId(userId: Uint8Array): Promise<string>;
19
20
  postPublicKey(base64PublicKeys: string): Promise<string>;
20
21
  private makeRequest;
@@ -91,6 +91,16 @@ export class RestMessageProtocol {
91
91
  data: decodeFromBase64(item.data),
92
92
  }));
93
93
  }
94
+ async fetchBulletinCounter() {
95
+ const url = `${this.baseUrl}${BULLETIN_ENDPOINT}/counter`;
96
+ const response = await this.makeRequest(url, {
97
+ method: 'GET',
98
+ });
99
+ if (!response.success || !response.data) {
100
+ throw new Error(response.error || 'Failed to fetch bulletin counter');
101
+ }
102
+ return response.data.counter;
103
+ }
94
104
  async fetchPublicKeyByUserId(userId) {
95
105
  const response = await this.makeRequest(`${this.baseUrl}/auth/retrieve`, {
96
106
  method: 'POST',
@@ -40,6 +40,10 @@ export interface IMessageProtocol {
40
40
  * @param cursor - Optional cursor (counter) to fetch announcements after this value
41
41
  */
42
42
  fetchAnnouncements(limit?: number, cursor?: string): Promise<BulletinItem[]>;
43
+ /**
44
+ * Fetch the latest bulletin counter from the API without downloading announcement data.
45
+ */
46
+ fetchBulletinCounter(): Promise<string>;
43
47
  /**
44
48
  * Fetch public key by userId hash (base64 string)
45
49
  * @param userId - Decoded userId bytes
package/dist/gossip.d.ts CHANGED
@@ -196,6 +196,8 @@ interface DiscussionServiceAPI {
196
196
  interface AnnouncementServiceAPI {
197
197
  /** Fetch and process announcements from the protocol */
198
198
  fetch(): Promise<AnnouncementReceptionResult>;
199
+ /** Skip historical announcements for a new account. Call after profile creation. */
200
+ skipHistorical(): Promise<void>;
199
201
  }
200
202
  interface ContactsAPI {
201
203
  /** List all contacts for the owner */
package/dist/gossip.js CHANGED
@@ -230,6 +230,7 @@ class GossipSdk {
230
230
  this._refresh = new RefreshService(db, this._message, this._discussion, this._announcement, session, this.eventEmitter);
231
231
  // Publish gossip ID so the user is discoverable (fire-and-forget — don't block login)
232
232
  this._auth.ensurePublicKeyPublished(session.ourPk, session.userIdEncoded).catch(err => console.warn('[GossipSdk] Failed to publish public key:', err));
233
+ // Update SDK state to reflect the newly opened session.
233
234
  this.state = {
234
235
  status: SdkStatus.SESSION_OPEN,
235
236
  messageProtocol: this.state.messageProtocol,
@@ -304,6 +305,7 @@ class GossipSdk {
304
305
  await this._refresh?.stateUpdate();
305
306
  return result;
306
307
  },
308
+ skipHistorical: () => this._announcement.skipHistoricalAnnouncements(),
307
309
  };
308
310
  this._contactsAPI = {
309
311
  list: ownerUserId => getContacts(ownerUserId, db),
@@ -35,10 +35,16 @@ export declare class AnnouncementService {
35
35
  fetchAndProcessAnnouncements(): Promise<AnnouncementReceptionResult>;
36
36
  /**
37
37
  * Persist lastBulletinCounter for the current user.
38
- * Uses put() so the cursor is saved even when no profile row exists yet
39
- * (e.g. headless bot that never created a profile via app UI).
38
+ * Only updates an existing profile row never creates a partial one,
39
+ * since a row without `security`/`session` would crash downstream code.
40
40
  */
41
41
  private _upsertLastBulletinCounter;
42
+ /**
43
+ * Fetch the latest bulletin counter from the API and persist it so that
44
+ * historical announcements (undecryptable by a new account) are skipped.
45
+ * No-op if a counter already exists or if the profile hasn't been created yet.
46
+ */
47
+ skipHistoricalAnnouncements(): Promise<void>;
42
48
  private _fetchAnnouncements;
43
49
  private _generateTemporaryContactName;
44
50
  private _processIncomingAnnouncement;
@@ -267,30 +267,47 @@ export class AnnouncementService {
267
267
  }
268
268
  /**
269
269
  * Persist lastBulletinCounter for the current user.
270
- * Uses put() so the cursor is saved even when no profile row exists yet
271
- * (e.g. headless bot that never created a profile via app UI).
270
+ * Only updates an existing profile row never creates a partial one,
271
+ * since a row without `security`/`session` would crash downstream code.
272
272
  */
273
273
  async _upsertLastBulletinCounter(nextCounter) {
274
274
  const userId = this.session.userIdEncoded;
275
275
  const existing = await this.db.userProfile.get(userId);
276
- if (existing) {
277
- await this.db.userProfile.update(userId, {
278
- lastBulletinCounter: nextCounter,
279
- updatedAt: new Date(),
280
- });
276
+ if (!existing) {
277
+ logger
278
+ .forMethod('_upsertLastBulletinCounter')
279
+ .debug('no profile row yet — skipping counter write');
281
280
  return;
282
281
  }
283
- // Minimal profile so cursor persists; headless bots may never create a full profile
284
- await this.db.userProfile.put({
285
- userId,
286
- username: '',
287
- status: 'offline',
288
- lastSeen: new Date(),
289
- createdAt: new Date(),
290
- updatedAt: new Date(),
282
+ await this.db.userProfile.update(userId, {
291
283
  lastBulletinCounter: nextCounter,
284
+ updatedAt: new Date(),
292
285
  });
293
286
  }
287
+ /**
288
+ * Fetch the latest bulletin counter from the API and persist it so that
289
+ * historical announcements (undecryptable by a new account) are skipped.
290
+ * No-op if a counter already exists or if the profile hasn't been created yet.
291
+ */
292
+ async skipHistoricalAnnouncements() {
293
+ const log = logger.forMethod('skipHistoricalAnnouncements');
294
+ const existing = await this.db.userProfile.get(this.session.userIdEncoded);
295
+ if (!existing) {
296
+ log.debug('no profile row yet — skipping');
297
+ return;
298
+ }
299
+ if (existing.lastBulletinCounter !== undefined)
300
+ return;
301
+ try {
302
+ const counter = await this.messageProtocol.fetchBulletinCounter();
303
+ await this._upsertLastBulletinCounter(counter);
304
+ log.info('set initial bulletin counter for new account', { counter });
305
+ }
306
+ catch (err) {
307
+ // Non-critical — on failure the first fetch starts from the beginning.
308
+ log.warn('failed to initialize bulletin counter', { err });
309
+ }
310
+ }
294
311
  async _fetchAnnouncements(cursor, limit) {
295
312
  const fetchLimit = limit ?? this.config.announcements.fetchLimit;
296
313
  const log = logger.forMethod('_fetchAnnouncements');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@massalabs/gossip-sdk",
3
- "version": "0.0.2-dev.20260217151032",
3
+ "version": "0.0.2-dev.20260218140506",
4
4
  "description": "Gossip SDK for automation, chatbot, and integration use cases",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",