@massalabs/gossip-sdk 0.2.1-dev.20260904090819 → 0.2.1-dev.20260904110601

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.
package/dist/gossip.d.ts CHANGED
@@ -118,6 +118,8 @@ declare class GossipSdk {
118
118
  private _persistInFlightPromise;
119
119
  /** Back-off state after a failed persist; reset on every success. */
120
120
  private _persistBackoffMs;
121
+ /** Single-flight latch for `init()` — see `init` for the rationale. */
122
+ private _initPromise;
121
123
  private _portableExportActive;
122
124
  private _portableExportTerminal;
123
125
  private _portableImportActive;
@@ -131,6 +133,7 @@ declare class GossipSdk {
131
133
  * Initialize the SDK. Call once at app startup.
132
134
  */
133
135
  init(options: GossipSdkInitOptions): Promise<GossipSdk>;
136
+ private _doInit;
134
137
  /**
135
138
  * Open a session (login).
136
139
  * Generates keys from mnemonic and initializes session.
package/dist/gossip.js CHANGED
@@ -251,6 +251,13 @@ class GossipSdk {
251
251
  writable: true,
252
252
  value: 0
253
253
  });
254
+ /** Single-flight latch for `init()` — see `init` for the rationale. */
255
+ Object.defineProperty(this, "_initPromise", {
256
+ enumerable: true,
257
+ configurable: true,
258
+ writable: true,
259
+ value: null
260
+ });
254
261
  Object.defineProperty(this, "_portableExportActive", {
255
262
  enumerable: true,
256
263
  configurable: true,
@@ -344,6 +351,39 @@ class GossipSdk {
344
351
  }
345
352
  return this;
346
353
  }
354
+ // Single-flight latch. `this.state.status` only flips to INITIALIZED
355
+ // after the `await DatabaseConnection.create()` in `_doInit`, so two
356
+ // init() calls racing on the SAME instance would both pass the guard
357
+ // above and each open the DB — which native secure-storage rejects via
358
+ // redb's exclusive lock (DatabaseAlreadyOpen). Assigning the promise
359
+ // synchronously, before any await, closes that window; concurrent
360
+ // callers await the one init.
361
+ //
362
+ // Scope: this only dedupes concurrent inits on one instance. It does
363
+ // NOT cover a fresh WebView/Activity reusing a still-open
364
+ // process-global native handle (the notification-relaunch case) — a new
365
+ // instance has its own latch. That case is handled in the native
366
+ // layer's idempotent `init_native`.
367
+ if (this._initPromise) {
368
+ return this._initPromise;
369
+ }
370
+ const initPromise = this._doInit(options);
371
+ this._initPromise = initPromise;
372
+ try {
373
+ return await initPromise;
374
+ }
375
+ catch (err) {
376
+ // A failed init must not poison retries — clear the latch. Guarded
377
+ // by identity: if a destroy() + fresh init() interleaved between
378
+ // the rejection and this catch, the latch now belongs to the newer
379
+ // init and must survive.
380
+ if (this._initPromise === initPromise) {
381
+ this._initPromise = null;
382
+ }
383
+ throw err;
384
+ }
385
+ }
386
+ async _doInit(options) {
347
387
  if (import.meta.env?.DEV) {
348
388
  logger.info('[GossipSdk] Initializing SDK');
349
389
  }
@@ -360,26 +400,50 @@ class GossipSdk {
360
400
  logger.info('[GossipSdk] Initializing SQLite');
361
401
  }
362
402
  this._conn = await DatabaseConnection.create({ storage: options.storage });
363
- // Defer queries/profile when the database needs an unlock first.
364
- if (this._conn.isOpen) {
365
- this._queries = new Queries(this._conn);
403
+ try {
404
+ // Defer queries/profile when the database needs an unlock first.
405
+ if (this._conn.isOpen) {
406
+ this._queries = new Queries(this._conn);
407
+ }
408
+ if (import.meta.env?.DEV) {
409
+ logger.info('[GossipSdk] SQLite initialized');
410
+ }
411
+ // Create message protocol
412
+ const messageProtocol = createMessageProtocol();
413
+ // Create services that don't need a session
414
+ this._auth = new AuthService(createAuthProtocol());
415
+ this._profile = this._queries
416
+ ? this.createProfileService(this._queries)
417
+ : null;
418
+ this.state = {
419
+ status: SdkStatus.INITIALIZED,
420
+ messageProtocol,
421
+ config,
422
+ };
423
+ return this;
424
+ }
425
+ catch (err) {
426
+ // A failure after the DB opened must release the handle before the
427
+ // error propagates: `init()` clears its latch on failure, and the
428
+ // retry's `DatabaseConnection.create()` would otherwise hit the
429
+ // still-open handle — the same DatabaseAlreadyOpen boot error the
430
+ // single-flight latch exists to prevent. `_queries` is reset too:
431
+ // a retry on locked storage (`isOpen === false`) skips reassigning
432
+ // it and must not keep queries bound to the released connection.
433
+ const conn = this._conn;
434
+ this._conn = null;
435
+ this._queries = null;
436
+ if (conn) {
437
+ try {
438
+ await conn.close();
439
+ }
440
+ catch {
441
+ // Best effort — the original init failure is the actionable
442
+ // error, not the close's.
443
+ }
444
+ }
445
+ throw err;
366
446
  }
367
- if (import.meta.env?.DEV) {
368
- logger.info('[GossipSdk] SQLite initialized');
369
- }
370
- // Create message protocol
371
- const messageProtocol = createMessageProtocol();
372
- // Create services that don't need a session
373
- this._auth = new AuthService(createAuthProtocol());
374
- this._profile = this._queries
375
- ? this.createProfileService(this._queries)
376
- : null;
377
- this.state = {
378
- status: SdkStatus.INITIALIZED,
379
- messageProtocol,
380
- config,
381
- };
382
- return this;
383
447
  }
384
448
  /**
385
449
  * Open a session (login).
@@ -751,6 +815,7 @@ class GossipSdk {
751
815
  finally {
752
816
  this._lifecycleCloseActive = false;
753
817
  }
818
+ this._initPromise = null;
754
819
  }
755
820
  // ─────────────────────────────────────────────────────────────────
756
821
  // Session Info
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@massalabs/gossip-sdk",
3
- "version": "0.2.1-dev.20260904090819",
3
+ "version": "0.2.1-dev.20260904110601",
4
4
  "description": "Gossip SDK for automation, chatbot, and integration use cases",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",