@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 +3 -0
- package/dist/gossip.js +84 -19
- package/package.json +1 -1
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
|
-
|
|
364
|
-
|
|
365
|
-
|
|
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