@unicitylabs/sphere-sdk 0.3.9 → 0.4.2
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/connect/index.cjs +79 -3
- package/dist/connect/index.cjs.map +1 -1
- package/dist/connect/index.d.cts +16 -0
- package/dist/connect/index.d.ts +16 -0
- package/dist/connect/index.js +79 -3
- package/dist/connect/index.js.map +1 -1
- package/dist/core/index.cjs +2686 -56
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +228 -3
- package/dist/core/index.d.ts +228 -3
- package/dist/core/index.js +2682 -52
- package/dist/core/index.js.map +1 -1
- package/dist/impl/browser/connect/index.cjs +11 -2
- package/dist/impl/browser/connect/index.cjs.map +1 -1
- package/dist/impl/browser/connect/index.js +11 -2
- package/dist/impl/browser/connect/index.js.map +1 -1
- package/dist/impl/browser/index.cjs +467 -47
- package/dist/impl/browser/index.cjs.map +1 -1
- package/dist/impl/browser/index.js +468 -47
- package/dist/impl/browser/index.js.map +1 -1
- package/dist/impl/nodejs/connect/index.cjs +11 -2
- package/dist/impl/nodejs/connect/index.cjs.map +1 -1
- package/dist/impl/nodejs/connect/index.js +11 -2
- package/dist/impl/nodejs/connect/index.js.map +1 -1
- package/dist/impl/nodejs/index.cjs +152 -8
- package/dist/impl/nodejs/index.cjs.map +1 -1
- package/dist/impl/nodejs/index.d.cts +47 -0
- package/dist/impl/nodejs/index.d.ts +47 -0
- package/dist/impl/nodejs/index.js +153 -8
- package/dist/impl/nodejs/index.js.map +1 -1
- package/dist/index.cjs +2703 -56
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +326 -5
- package/dist/index.d.ts +326 -5
- package/dist/index.js +2692 -52
- package/dist/index.js.map +1 -1
- package/dist/l1/index.cjs +5 -1
- package/dist/l1/index.cjs.map +1 -1
- package/dist/l1/index.d.cts +2 -1
- package/dist/l1/index.d.ts +2 -1
- package/dist/l1/index.js +5 -1
- package/dist/l1/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -31,6 +31,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
var browser_exports = {};
|
|
32
32
|
__export(browser_exports, {
|
|
33
33
|
BrowserTrustBaseLoader: () => BrowserTrustBaseLoader,
|
|
34
|
+
IndexedDBStorageProvider: () => IndexedDBStorageProvider,
|
|
34
35
|
IndexedDBTokenStorageProvider: () => IndexedDBTokenStorageProvider,
|
|
35
36
|
LocalStorageProvider: () => LocalStorageProvider,
|
|
36
37
|
NostrTransportProvider: () => NostrTransportProvider,
|
|
@@ -40,6 +41,7 @@ __export(browser_exports, {
|
|
|
40
41
|
createBrowserProviders: () => createBrowserProviders,
|
|
41
42
|
createBrowserTrustBaseLoader: () => createBrowserTrustBaseLoader,
|
|
42
43
|
createBrowserWebSocket: () => createBrowserWebSocket,
|
|
44
|
+
createIndexedDBStorageProvider: () => createIndexedDBStorageProvider,
|
|
43
45
|
createIndexedDBTokenStorageProvider: () => createIndexedDBTokenStorageProvider,
|
|
44
46
|
createLocalStorageProvider: () => createLocalStorageProvider,
|
|
45
47
|
createNostrTransportProvider: () => createNostrTransportProvider,
|
|
@@ -408,9 +410,275 @@ function createLocalStorageProvider(config) {
|
|
|
408
410
|
return new LocalStorageProvider(config);
|
|
409
411
|
}
|
|
410
412
|
|
|
411
|
-
// impl/browser/storage/
|
|
412
|
-
var DB_NAME = "sphere-
|
|
413
|
+
// impl/browser/storage/IndexedDBStorageProvider.ts
|
|
414
|
+
var DB_NAME = "sphere-storage";
|
|
413
415
|
var DB_VERSION = 1;
|
|
416
|
+
var STORE_NAME = "kv";
|
|
417
|
+
var IndexedDBStorageProvider = class {
|
|
418
|
+
id = "indexeddb-storage";
|
|
419
|
+
name = "IndexedDB Storage";
|
|
420
|
+
type = "local";
|
|
421
|
+
description = "Browser IndexedDB for large-capacity persistence";
|
|
422
|
+
prefix;
|
|
423
|
+
dbName;
|
|
424
|
+
debug;
|
|
425
|
+
identity = null;
|
|
426
|
+
status = "disconnected";
|
|
427
|
+
db = null;
|
|
428
|
+
constructor(config) {
|
|
429
|
+
this.prefix = config?.prefix ?? "sphere_";
|
|
430
|
+
this.dbName = config?.dbName ?? DB_NAME;
|
|
431
|
+
this.debug = config?.debug ?? false;
|
|
432
|
+
}
|
|
433
|
+
// ===========================================================================
|
|
434
|
+
// BaseProvider Implementation
|
|
435
|
+
// ===========================================================================
|
|
436
|
+
async connect() {
|
|
437
|
+
if (this.status === "connected" && this.db) return;
|
|
438
|
+
this.status = "connecting";
|
|
439
|
+
console.log(`[IndexedDBStorage] connect: opening db=${this.dbName}`);
|
|
440
|
+
try {
|
|
441
|
+
this.db = await Promise.race([
|
|
442
|
+
this.openDatabase(),
|
|
443
|
+
new Promise(
|
|
444
|
+
(_, reject) => setTimeout(() => reject(new Error("IndexedDB open timed out after 5s")), 5e3)
|
|
445
|
+
)
|
|
446
|
+
]);
|
|
447
|
+
this.status = "connected";
|
|
448
|
+
console.log(`[IndexedDBStorage] connect: connected to db=${this.dbName}`);
|
|
449
|
+
} catch (error) {
|
|
450
|
+
this.status = "error";
|
|
451
|
+
throw new Error(`IndexedDB not available: ${error}`);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
async disconnect() {
|
|
455
|
+
console.log(`[IndexedDBStorage] disconnect: closing db=${this.dbName}, wasConnected=${!!this.db}`);
|
|
456
|
+
if (this.db) {
|
|
457
|
+
this.db.close();
|
|
458
|
+
this.db = null;
|
|
459
|
+
}
|
|
460
|
+
this.status = "disconnected";
|
|
461
|
+
}
|
|
462
|
+
isConnected() {
|
|
463
|
+
return this.status === "connected" && this.db !== null;
|
|
464
|
+
}
|
|
465
|
+
getStatus() {
|
|
466
|
+
return this.status;
|
|
467
|
+
}
|
|
468
|
+
// ===========================================================================
|
|
469
|
+
// StorageProvider Implementation
|
|
470
|
+
// ===========================================================================
|
|
471
|
+
setIdentity(identity) {
|
|
472
|
+
this.identity = identity;
|
|
473
|
+
this.log("Identity set:", identity.l1Address);
|
|
474
|
+
}
|
|
475
|
+
async get(key) {
|
|
476
|
+
this.ensureConnected();
|
|
477
|
+
const fullKey = this.getFullKey(key);
|
|
478
|
+
const result = await this.idbGet(fullKey);
|
|
479
|
+
return result?.v ?? null;
|
|
480
|
+
}
|
|
481
|
+
async set(key, value) {
|
|
482
|
+
this.ensureConnected();
|
|
483
|
+
const fullKey = this.getFullKey(key);
|
|
484
|
+
await this.idbPut({ k: fullKey, v: value });
|
|
485
|
+
}
|
|
486
|
+
async remove(key) {
|
|
487
|
+
this.ensureConnected();
|
|
488
|
+
const fullKey = this.getFullKey(key);
|
|
489
|
+
await this.idbDelete(fullKey);
|
|
490
|
+
}
|
|
491
|
+
async has(key) {
|
|
492
|
+
this.ensureConnected();
|
|
493
|
+
const fullKey = this.getFullKey(key);
|
|
494
|
+
const count = await this.idbCount(fullKey);
|
|
495
|
+
return count > 0;
|
|
496
|
+
}
|
|
497
|
+
async keys(prefix) {
|
|
498
|
+
this.ensureConnected();
|
|
499
|
+
const basePrefix = this.getFullKey("");
|
|
500
|
+
const searchPrefix = prefix ? this.getFullKey(prefix) : basePrefix;
|
|
501
|
+
const allEntries = await this.idbGetAll();
|
|
502
|
+
const result = [];
|
|
503
|
+
for (const entry of allEntries) {
|
|
504
|
+
if (entry.k.startsWith(searchPrefix)) {
|
|
505
|
+
result.push(entry.k.slice(basePrefix.length));
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
return result;
|
|
509
|
+
}
|
|
510
|
+
async clear(prefix) {
|
|
511
|
+
if (!prefix) {
|
|
512
|
+
console.log(`[IndexedDBStorage] clear: starting, db=${this.dbName}, wasConnected=${!!this.db}`);
|
|
513
|
+
if (this.db) {
|
|
514
|
+
this.db.close();
|
|
515
|
+
this.db = null;
|
|
516
|
+
}
|
|
517
|
+
this.status = "disconnected";
|
|
518
|
+
await new Promise((resolve) => {
|
|
519
|
+
try {
|
|
520
|
+
const req = indexedDB.deleteDatabase(this.dbName);
|
|
521
|
+
req.onsuccess = () => {
|
|
522
|
+
console.log(`[IndexedDBStorage] clear: deleted db=${this.dbName}`);
|
|
523
|
+
resolve();
|
|
524
|
+
};
|
|
525
|
+
req.onerror = () => {
|
|
526
|
+
console.warn(`[IndexedDBStorage] clear: error deleting db=${this.dbName}`, req.error);
|
|
527
|
+
resolve();
|
|
528
|
+
};
|
|
529
|
+
req.onblocked = () => {
|
|
530
|
+
console.warn(`[IndexedDBStorage] clear: deleteDatabase blocked for db=${this.dbName}`);
|
|
531
|
+
resolve();
|
|
532
|
+
};
|
|
533
|
+
} catch {
|
|
534
|
+
resolve();
|
|
535
|
+
}
|
|
536
|
+
});
|
|
537
|
+
this.log("Database deleted:", this.dbName);
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
this.ensureConnected();
|
|
541
|
+
const keysToRemove = await this.keys(prefix);
|
|
542
|
+
for (const key of keysToRemove) {
|
|
543
|
+
await this.remove(key);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
async saveTrackedAddresses(entries) {
|
|
547
|
+
await this.set(STORAGE_KEYS_GLOBAL.TRACKED_ADDRESSES, JSON.stringify({ version: 1, addresses: entries }));
|
|
548
|
+
}
|
|
549
|
+
async loadTrackedAddresses() {
|
|
550
|
+
const data = await this.get(STORAGE_KEYS_GLOBAL.TRACKED_ADDRESSES);
|
|
551
|
+
if (!data) return [];
|
|
552
|
+
try {
|
|
553
|
+
const parsed = JSON.parse(data);
|
|
554
|
+
return parsed.addresses ?? [];
|
|
555
|
+
} catch {
|
|
556
|
+
return [];
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
// ===========================================================================
|
|
560
|
+
// Helpers
|
|
561
|
+
// ===========================================================================
|
|
562
|
+
/**
|
|
563
|
+
* Get JSON data
|
|
564
|
+
*/
|
|
565
|
+
async getJSON(key) {
|
|
566
|
+
const value = await this.get(key);
|
|
567
|
+
if (!value) return null;
|
|
568
|
+
try {
|
|
569
|
+
return JSON.parse(value);
|
|
570
|
+
} catch {
|
|
571
|
+
return null;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Set JSON data
|
|
576
|
+
*/
|
|
577
|
+
async setJSON(key, value) {
|
|
578
|
+
await this.set(key, JSON.stringify(value));
|
|
579
|
+
}
|
|
580
|
+
// ===========================================================================
|
|
581
|
+
// Private: Key Scoping
|
|
582
|
+
// ===========================================================================
|
|
583
|
+
getFullKey(key) {
|
|
584
|
+
const isPerAddressKey = Object.values(STORAGE_KEYS_ADDRESS).includes(key);
|
|
585
|
+
if (isPerAddressKey && this.identity?.directAddress) {
|
|
586
|
+
const addressId = getAddressId(this.identity.directAddress);
|
|
587
|
+
return `${this.prefix}${addressId}_${key}`;
|
|
588
|
+
}
|
|
589
|
+
return `${this.prefix}${key}`;
|
|
590
|
+
}
|
|
591
|
+
ensureConnected() {
|
|
592
|
+
if (this.status !== "connected" || !this.db) {
|
|
593
|
+
throw new Error("IndexedDBStorageProvider not connected");
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
// ===========================================================================
|
|
597
|
+
// Private: IndexedDB Operations
|
|
598
|
+
// ===========================================================================
|
|
599
|
+
openDatabase() {
|
|
600
|
+
return new Promise((resolve, reject) => {
|
|
601
|
+
const request = indexedDB.open(this.dbName, DB_VERSION);
|
|
602
|
+
request.onerror = () => reject(request.error);
|
|
603
|
+
request.onsuccess = () => resolve(request.result);
|
|
604
|
+
request.onblocked = () => {
|
|
605
|
+
console.warn("[IndexedDBStorageProvider] open blocked by another connection");
|
|
606
|
+
};
|
|
607
|
+
request.onupgradeneeded = (event) => {
|
|
608
|
+
const db = event.target.result;
|
|
609
|
+
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
|
610
|
+
db.createObjectStore(STORE_NAME, { keyPath: "k" });
|
|
611
|
+
}
|
|
612
|
+
};
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
idbGet(key) {
|
|
616
|
+
return new Promise((resolve, reject) => {
|
|
617
|
+
const tx = this.db.transaction(STORE_NAME, "readonly");
|
|
618
|
+
const store = tx.objectStore(STORE_NAME);
|
|
619
|
+
const request = store.get(key);
|
|
620
|
+
request.onerror = () => reject(request.error);
|
|
621
|
+
request.onsuccess = () => resolve(request.result ?? void 0);
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
idbPut(entry) {
|
|
625
|
+
return new Promise((resolve, reject) => {
|
|
626
|
+
const tx = this.db.transaction(STORE_NAME, "readwrite");
|
|
627
|
+
const store = tx.objectStore(STORE_NAME);
|
|
628
|
+
const request = store.put(entry);
|
|
629
|
+
request.onerror = () => reject(request.error);
|
|
630
|
+
request.onsuccess = () => resolve();
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
idbDelete(key) {
|
|
634
|
+
return new Promise((resolve, reject) => {
|
|
635
|
+
const tx = this.db.transaction(STORE_NAME, "readwrite");
|
|
636
|
+
const store = tx.objectStore(STORE_NAME);
|
|
637
|
+
const request = store.delete(key);
|
|
638
|
+
request.onerror = () => reject(request.error);
|
|
639
|
+
request.onsuccess = () => resolve();
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
idbCount(key) {
|
|
643
|
+
return new Promise((resolve, reject) => {
|
|
644
|
+
const tx = this.db.transaction(STORE_NAME, "readonly");
|
|
645
|
+
const store = tx.objectStore(STORE_NAME);
|
|
646
|
+
const request = store.count(key);
|
|
647
|
+
request.onerror = () => reject(request.error);
|
|
648
|
+
request.onsuccess = () => resolve(request.result);
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
idbGetAll() {
|
|
652
|
+
return new Promise((resolve, reject) => {
|
|
653
|
+
const tx = this.db.transaction(STORE_NAME, "readonly");
|
|
654
|
+
const store = tx.objectStore(STORE_NAME);
|
|
655
|
+
const request = store.getAll();
|
|
656
|
+
request.onerror = () => reject(request.error);
|
|
657
|
+
request.onsuccess = () => resolve(request.result ?? []);
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
idbClear() {
|
|
661
|
+
return new Promise((resolve, reject) => {
|
|
662
|
+
const tx = this.db.transaction(STORE_NAME, "readwrite");
|
|
663
|
+
const store = tx.objectStore(STORE_NAME);
|
|
664
|
+
const request = store.clear();
|
|
665
|
+
request.onerror = () => reject(request.error);
|
|
666
|
+
request.onsuccess = () => resolve();
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
log(...args) {
|
|
670
|
+
if (this.debug) {
|
|
671
|
+
console.log("[IndexedDBStorageProvider]", ...args);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
};
|
|
675
|
+
function createIndexedDBStorageProvider(config) {
|
|
676
|
+
return new IndexedDBStorageProvider(config);
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// impl/browser/storage/IndexedDBTokenStorageProvider.ts
|
|
680
|
+
var DB_NAME2 = "sphere-token-storage";
|
|
681
|
+
var DB_VERSION2 = 1;
|
|
414
682
|
var STORE_TOKENS = "tokens";
|
|
415
683
|
var STORE_META = "meta";
|
|
416
684
|
var IndexedDBTokenStorageProvider = class {
|
|
@@ -423,7 +691,7 @@ var IndexedDBTokenStorageProvider = class {
|
|
|
423
691
|
status = "disconnected";
|
|
424
692
|
identity = null;
|
|
425
693
|
constructor(config) {
|
|
426
|
-
this.dbNamePrefix = config?.dbNamePrefix ??
|
|
694
|
+
this.dbNamePrefix = config?.dbNamePrefix ?? DB_NAME2;
|
|
427
695
|
this.dbName = this.dbNamePrefix;
|
|
428
696
|
}
|
|
429
697
|
setIdentity(identity) {
|
|
@@ -432,11 +700,19 @@ var IndexedDBTokenStorageProvider = class {
|
|
|
432
700
|
const addressId = getAddressId(identity.directAddress);
|
|
433
701
|
this.dbName = `${this.dbNamePrefix}-${addressId}`;
|
|
434
702
|
}
|
|
703
|
+
console.log(`[IndexedDBTokenStorage] setIdentity \u2192 db=${this.dbName}`);
|
|
435
704
|
}
|
|
436
705
|
async initialize() {
|
|
437
706
|
try {
|
|
707
|
+
if (this.db) {
|
|
708
|
+
console.log(`[IndexedDBTokenStorage] initialize: closing existing connection before re-open (db=${this.dbName})`);
|
|
709
|
+
this.db.close();
|
|
710
|
+
this.db = null;
|
|
711
|
+
}
|
|
712
|
+
console.log(`[IndexedDBTokenStorage] initialize: opening db=${this.dbName}`);
|
|
438
713
|
this.db = await this.openDatabase();
|
|
439
714
|
this.status = "connected";
|
|
715
|
+
console.log(`[IndexedDBTokenStorage] initialize: connected to db=${this.dbName}`);
|
|
440
716
|
return true;
|
|
441
717
|
} catch (error) {
|
|
442
718
|
console.error("[IndexedDBTokenStorage] Failed to initialize:", error);
|
|
@@ -445,6 +721,7 @@ var IndexedDBTokenStorageProvider = class {
|
|
|
445
721
|
}
|
|
446
722
|
}
|
|
447
723
|
async shutdown() {
|
|
724
|
+
console.log(`[IndexedDBTokenStorage] shutdown: closing db=${this.dbName}, wasConnected=${!!this.db}`);
|
|
448
725
|
if (this.db) {
|
|
449
726
|
this.db.close();
|
|
450
727
|
this.db = null;
|
|
@@ -465,6 +742,7 @@ var IndexedDBTokenStorageProvider = class {
|
|
|
465
742
|
}
|
|
466
743
|
async load() {
|
|
467
744
|
if (!this.db) {
|
|
745
|
+
console.warn(`[IndexedDBTokenStorage] load: db not initialized (db=${this.dbName})`);
|
|
468
746
|
return {
|
|
469
747
|
success: false,
|
|
470
748
|
error: "Database not initialized",
|
|
@@ -513,6 +791,8 @@ var IndexedDBTokenStorageProvider = class {
|
|
|
513
791
|
if (invalid) {
|
|
514
792
|
data._invalid = invalid;
|
|
515
793
|
}
|
|
794
|
+
const tokenKeys = Object.keys(data).filter((k) => k.startsWith("_") && !["_meta", "_tombstones", "_outbox", "_sent", "_invalid"].includes(k));
|
|
795
|
+
console.log(`[IndexedDBTokenStorage] load: db=${this.dbName}, tokens=${tokenKeys.length}`);
|
|
516
796
|
return {
|
|
517
797
|
success: true,
|
|
518
798
|
data,
|
|
@@ -520,6 +800,7 @@ var IndexedDBTokenStorageProvider = class {
|
|
|
520
800
|
timestamp: Date.now()
|
|
521
801
|
};
|
|
522
802
|
} catch (error) {
|
|
803
|
+
console.error(`[IndexedDBTokenStorage] load failed: db=${this.dbName}`, error);
|
|
523
804
|
return {
|
|
524
805
|
success: false,
|
|
525
806
|
error: error instanceof Error ? error.message : "Unknown error",
|
|
@@ -530,6 +811,7 @@ var IndexedDBTokenStorageProvider = class {
|
|
|
530
811
|
}
|
|
531
812
|
async save(data) {
|
|
532
813
|
if (!this.db) {
|
|
814
|
+
console.warn(`[IndexedDBTokenStorage] save: db not initialized (db=${this.dbName})`);
|
|
533
815
|
return {
|
|
534
816
|
success: false,
|
|
535
817
|
error: "Database not initialized",
|
|
@@ -537,6 +819,9 @@ var IndexedDBTokenStorageProvider = class {
|
|
|
537
819
|
};
|
|
538
820
|
}
|
|
539
821
|
try {
|
|
822
|
+
const tokenKeys = Object.keys(data).filter((k) => k.startsWith("_") && !["_meta", "_tombstones", "_outbox", "_sent", "_invalid"].includes(k));
|
|
823
|
+
const archivedKeys = Object.keys(data).filter((k) => k.startsWith("archived-"));
|
|
824
|
+
console.log(`[IndexedDBTokenStorage] save: db=${this.dbName}, tokens=${tokenKeys.length}, archived=${archivedKeys.length}, tombstones=${data._tombstones?.length ?? 0}`);
|
|
540
825
|
await this.putToStore(STORE_META, "meta", data._meta);
|
|
541
826
|
if (data._tombstones) {
|
|
542
827
|
await this.putToStore(STORE_META, "tombstones", data._tombstones);
|
|
@@ -594,15 +879,14 @@ var IndexedDBTokenStorageProvider = class {
|
|
|
594
879
|
return meta !== null;
|
|
595
880
|
}
|
|
596
881
|
async clear() {
|
|
597
|
-
const dbNames = [this.dbName];
|
|
598
882
|
try {
|
|
883
|
+
console.log(`[IndexedDBTokenStorage] clear: starting, db=${this.dbName}, wasConnected=${!!this.db}`);
|
|
599
884
|
if (this.db) {
|
|
600
|
-
await this.clearStore(STORE_TOKENS);
|
|
601
|
-
await this.clearStore(STORE_META);
|
|
602
885
|
this.db.close();
|
|
603
886
|
this.db = null;
|
|
604
887
|
}
|
|
605
888
|
this.status = "disconnected";
|
|
889
|
+
const dbNames = [this.dbName];
|
|
606
890
|
if (typeof indexedDB.databases === "function") {
|
|
607
891
|
try {
|
|
608
892
|
const dbs = await Promise.race([
|
|
@@ -614,42 +898,34 @@ var IndexedDBTokenStorageProvider = class {
|
|
|
614
898
|
for (const dbInfo of dbs) {
|
|
615
899
|
if (dbInfo.name && dbInfo.name.startsWith(this.dbNamePrefix) && dbInfo.name !== this.dbName) {
|
|
616
900
|
dbNames.push(dbInfo.name);
|
|
617
|
-
try {
|
|
618
|
-
const db = await new Promise((resolve, reject) => {
|
|
619
|
-
const req = indexedDB.open(dbInfo.name, DB_VERSION);
|
|
620
|
-
req.onsuccess = () => resolve(req.result);
|
|
621
|
-
req.onerror = () => reject(req.error);
|
|
622
|
-
req.onupgradeneeded = (e) => {
|
|
623
|
-
const d = e.target.result;
|
|
624
|
-
if (!d.objectStoreNames.contains(STORE_TOKENS)) d.createObjectStore(STORE_TOKENS, { keyPath: "id" });
|
|
625
|
-
if (!d.objectStoreNames.contains(STORE_META)) d.createObjectStore(STORE_META);
|
|
626
|
-
};
|
|
627
|
-
});
|
|
628
|
-
const clearTx = db.transaction([STORE_TOKENS, STORE_META], "readwrite");
|
|
629
|
-
clearTx.objectStore(STORE_TOKENS).clear();
|
|
630
|
-
clearTx.objectStore(STORE_META).clear();
|
|
631
|
-
await new Promise((resolve) => {
|
|
632
|
-
clearTx.oncomplete = () => resolve();
|
|
633
|
-
clearTx.onerror = () => resolve();
|
|
634
|
-
});
|
|
635
|
-
db.close();
|
|
636
|
-
} catch {
|
|
637
|
-
}
|
|
638
901
|
}
|
|
639
902
|
}
|
|
640
903
|
} catch {
|
|
641
904
|
}
|
|
642
905
|
}
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
906
|
+
console.log(`[IndexedDBTokenStorage] clear: deleting ${dbNames.length} database(s):`, dbNames);
|
|
907
|
+
await Promise.all(dbNames.map(
|
|
908
|
+
(name) => new Promise((resolve) => {
|
|
909
|
+
try {
|
|
910
|
+
const req = indexedDB.deleteDatabase(name);
|
|
911
|
+
req.onsuccess = () => {
|
|
912
|
+
console.log(`[IndexedDBTokenStorage] clear: deleted db=${name}`);
|
|
913
|
+
resolve();
|
|
914
|
+
};
|
|
915
|
+
req.onerror = () => {
|
|
916
|
+
console.warn(`[IndexedDBTokenStorage] clear: error deleting db=${name}`, req.error);
|
|
917
|
+
resolve();
|
|
918
|
+
};
|
|
919
|
+
req.onblocked = () => {
|
|
920
|
+
console.warn(`[IndexedDBTokenStorage] clear: deleteDatabase blocked for db=${name}`);
|
|
921
|
+
resolve();
|
|
922
|
+
};
|
|
923
|
+
} catch {
|
|
924
|
+
resolve();
|
|
925
|
+
}
|
|
926
|
+
})
|
|
927
|
+
));
|
|
928
|
+
console.log(`[IndexedDBTokenStorage] clear: done`);
|
|
653
929
|
return true;
|
|
654
930
|
} catch (err) {
|
|
655
931
|
console.warn("[IndexedDBTokenStorage] clear() failed:", err);
|
|
@@ -661,7 +937,7 @@ var IndexedDBTokenStorageProvider = class {
|
|
|
661
937
|
// =========================================================================
|
|
662
938
|
openDatabase() {
|
|
663
939
|
return new Promise((resolve, reject) => {
|
|
664
|
-
const request = indexedDB.open(this.dbName,
|
|
940
|
+
const request = indexedDB.open(this.dbName, DB_VERSION2);
|
|
665
941
|
request.onerror = () => {
|
|
666
942
|
reject(request.error);
|
|
667
943
|
};
|
|
@@ -804,6 +1080,52 @@ function createView(arr) {
|
|
|
804
1080
|
function rotr(word, shift) {
|
|
805
1081
|
return word << 32 - shift | word >>> shift;
|
|
806
1082
|
}
|
|
1083
|
+
var hasHexBuiltin = /* @__PURE__ */ (() => (
|
|
1084
|
+
// @ts-ignore
|
|
1085
|
+
typeof Uint8Array.from([]).toHex === "function" && typeof Uint8Array.fromHex === "function"
|
|
1086
|
+
))();
|
|
1087
|
+
var hexes = /* @__PURE__ */ Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, "0"));
|
|
1088
|
+
function bytesToHex(bytes) {
|
|
1089
|
+
abytes(bytes);
|
|
1090
|
+
if (hasHexBuiltin)
|
|
1091
|
+
return bytes.toHex();
|
|
1092
|
+
let hex = "";
|
|
1093
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
1094
|
+
hex += hexes[bytes[i]];
|
|
1095
|
+
}
|
|
1096
|
+
return hex;
|
|
1097
|
+
}
|
|
1098
|
+
var asciis = { _0: 48, _9: 57, A: 65, F: 70, a: 97, f: 102 };
|
|
1099
|
+
function asciiToBase16(ch) {
|
|
1100
|
+
if (ch >= asciis._0 && ch <= asciis._9)
|
|
1101
|
+
return ch - asciis._0;
|
|
1102
|
+
if (ch >= asciis.A && ch <= asciis.F)
|
|
1103
|
+
return ch - (asciis.A - 10);
|
|
1104
|
+
if (ch >= asciis.a && ch <= asciis.f)
|
|
1105
|
+
return ch - (asciis.a - 10);
|
|
1106
|
+
return;
|
|
1107
|
+
}
|
|
1108
|
+
function hexToBytes(hex) {
|
|
1109
|
+
if (typeof hex !== "string")
|
|
1110
|
+
throw new Error("hex string expected, got " + typeof hex);
|
|
1111
|
+
if (hasHexBuiltin)
|
|
1112
|
+
return Uint8Array.fromHex(hex);
|
|
1113
|
+
const hl = hex.length;
|
|
1114
|
+
const al = hl / 2;
|
|
1115
|
+
if (hl % 2)
|
|
1116
|
+
throw new Error("hex string expected, got unpadded hex of length " + hl);
|
|
1117
|
+
const array = new Uint8Array(al);
|
|
1118
|
+
for (let ai = 0, hi = 0; ai < al; ai++, hi += 2) {
|
|
1119
|
+
const n1 = asciiToBase16(hex.charCodeAt(hi));
|
|
1120
|
+
const n2 = asciiToBase16(hex.charCodeAt(hi + 1));
|
|
1121
|
+
if (n1 === void 0 || n2 === void 0) {
|
|
1122
|
+
const char = hex[hi] + hex[hi + 1];
|
|
1123
|
+
throw new Error('hex string expected, got non-hex character "' + char + '" at index ' + hi);
|
|
1124
|
+
}
|
|
1125
|
+
array[ai] = n1 * 16 + n2;
|
|
1126
|
+
}
|
|
1127
|
+
return array;
|
|
1128
|
+
}
|
|
807
1129
|
function createHasher(hashCons, info = {}) {
|
|
808
1130
|
const hashC = (msg, opts) => hashCons(opts).update(msg).digest();
|
|
809
1131
|
const tmp = hashCons(void 0);
|
|
@@ -1299,7 +1621,7 @@ function publicKeyToAddress(publicKey, prefix = "alpha", witnessVersion = 0) {
|
|
|
1299
1621
|
const programBytes = hash160ToBytes(pubKeyHash);
|
|
1300
1622
|
return encodeBech32(prefix, witnessVersion, programBytes);
|
|
1301
1623
|
}
|
|
1302
|
-
function
|
|
1624
|
+
function hexToBytes2(hex) {
|
|
1303
1625
|
const matches = hex.match(/../g);
|
|
1304
1626
|
if (!matches) {
|
|
1305
1627
|
return new Uint8Array(0);
|
|
@@ -1326,6 +1648,8 @@ function defaultUUIDGenerator() {
|
|
|
1326
1648
|
}
|
|
1327
1649
|
|
|
1328
1650
|
// transport/NostrTransportProvider.ts
|
|
1651
|
+
var COMPOSING_INDICATOR_KIND = 25050;
|
|
1652
|
+
var TIMESTAMP_RANDOMIZATION = 2 * 24 * 60 * 60;
|
|
1329
1653
|
var EVENT_KINDS = NOSTR_EVENT_KINDS;
|
|
1330
1654
|
function hashAddressForTag(address) {
|
|
1331
1655
|
const bytes = new TextEncoder().encode("unicity:address:" + address);
|
|
@@ -1408,6 +1732,8 @@ var NostrTransportProvider = class {
|
|
|
1408
1732
|
paymentRequestResponseHandlers = /* @__PURE__ */ new Set();
|
|
1409
1733
|
readReceiptHandlers = /* @__PURE__ */ new Set();
|
|
1410
1734
|
typingIndicatorHandlers = /* @__PURE__ */ new Set();
|
|
1735
|
+
composingHandlers = /* @__PURE__ */ new Set();
|
|
1736
|
+
pendingMessages = [];
|
|
1411
1737
|
broadcastHandlers = /* @__PURE__ */ new Map();
|
|
1412
1738
|
eventCallbacks = /* @__PURE__ */ new Set();
|
|
1413
1739
|
constructor(config) {
|
|
@@ -1680,6 +2006,18 @@ var NostrTransportProvider = class {
|
|
|
1680
2006
|
}
|
|
1681
2007
|
onMessage(handler) {
|
|
1682
2008
|
this.messageHandlers.add(handler);
|
|
2009
|
+
if (this.pendingMessages.length > 0) {
|
|
2010
|
+
const pending = this.pendingMessages;
|
|
2011
|
+
this.pendingMessages = [];
|
|
2012
|
+
this.log("Flushing", pending.length, "buffered messages to new handler");
|
|
2013
|
+
for (const message of pending) {
|
|
2014
|
+
try {
|
|
2015
|
+
handler(message);
|
|
2016
|
+
} catch (error) {
|
|
2017
|
+
this.log("Message handler error (buffered):", error);
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
1683
2021
|
return () => this.messageHandlers.delete(handler);
|
|
1684
2022
|
}
|
|
1685
2023
|
async sendTokenTransfer(recipientPubkey, payload) {
|
|
@@ -1800,6 +2138,19 @@ var NostrTransportProvider = class {
|
|
|
1800
2138
|
this.typingIndicatorHandlers.add(handler);
|
|
1801
2139
|
return () => this.typingIndicatorHandlers.delete(handler);
|
|
1802
2140
|
}
|
|
2141
|
+
// ===========================================================================
|
|
2142
|
+
// Composing Indicators (NIP-59 kind 25050)
|
|
2143
|
+
// ===========================================================================
|
|
2144
|
+
onComposing(handler) {
|
|
2145
|
+
this.composingHandlers.add(handler);
|
|
2146
|
+
return () => this.composingHandlers.delete(handler);
|
|
2147
|
+
}
|
|
2148
|
+
async sendComposingIndicator(recipientPubkey, content) {
|
|
2149
|
+
this.ensureReady();
|
|
2150
|
+
const nostrRecipient = recipientPubkey.length === 66 && (recipientPubkey.startsWith("02") || recipientPubkey.startsWith("03")) ? recipientPubkey.slice(2) : recipientPubkey;
|
|
2151
|
+
const giftWrap = this.createCustomKindGiftWrap(nostrRecipient, content, COMPOSING_INDICATOR_KIND);
|
|
2152
|
+
await this.publishEvent(giftWrap);
|
|
2153
|
+
}
|
|
1803
2154
|
/**
|
|
1804
2155
|
* Resolve any identifier to full peer information.
|
|
1805
2156
|
* Routes to the appropriate specific resolve method based on identifier format.
|
|
@@ -2299,6 +2650,30 @@ var NostrTransportProvider = class {
|
|
|
2299
2650
|
}
|
|
2300
2651
|
return;
|
|
2301
2652
|
}
|
|
2653
|
+
if (pm.kind === COMPOSING_INDICATOR_KIND) {
|
|
2654
|
+
let senderNametag2;
|
|
2655
|
+
let expiresIn = 3e4;
|
|
2656
|
+
try {
|
|
2657
|
+
const parsed = JSON.parse(pm.content);
|
|
2658
|
+
senderNametag2 = parsed.senderNametag || void 0;
|
|
2659
|
+
expiresIn = parsed.expiresIn ?? 3e4;
|
|
2660
|
+
} catch {
|
|
2661
|
+
}
|
|
2662
|
+
const indicator = {
|
|
2663
|
+
senderPubkey: pm.senderPubkey,
|
|
2664
|
+
senderNametag: senderNametag2,
|
|
2665
|
+
expiresIn
|
|
2666
|
+
};
|
|
2667
|
+
this.log("Composing indicator from:", indicator.senderNametag || pm.senderPubkey?.slice(0, 16));
|
|
2668
|
+
for (const handler of this.composingHandlers) {
|
|
2669
|
+
try {
|
|
2670
|
+
handler(indicator);
|
|
2671
|
+
} catch (e) {
|
|
2672
|
+
this.log("Composing handler error:", e);
|
|
2673
|
+
}
|
|
2674
|
+
}
|
|
2675
|
+
return;
|
|
2676
|
+
}
|
|
2302
2677
|
try {
|
|
2303
2678
|
const parsed = JSON.parse(pm.content);
|
|
2304
2679
|
if (parsed?.type === "typing") {
|
|
@@ -2345,12 +2720,17 @@ var NostrTransportProvider = class {
|
|
|
2345
2720
|
encrypted: true
|
|
2346
2721
|
};
|
|
2347
2722
|
this.emitEvent({ type: "message:received", timestamp: Date.now() });
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2723
|
+
if (this.messageHandlers.size === 0) {
|
|
2724
|
+
this.log("No message handlers registered, buffering message for later delivery");
|
|
2725
|
+
this.pendingMessages.push(message);
|
|
2726
|
+
} else {
|
|
2727
|
+
this.log("Dispatching to", this.messageHandlers.size, "handlers");
|
|
2728
|
+
for (const handler of this.messageHandlers) {
|
|
2729
|
+
try {
|
|
2730
|
+
handler(message);
|
|
2731
|
+
} catch (error) {
|
|
2732
|
+
this.log("Message handler error:", error);
|
|
2733
|
+
}
|
|
2354
2734
|
}
|
|
2355
2735
|
}
|
|
2356
2736
|
} catch (err) {
|
|
@@ -2754,6 +3134,39 @@ var NostrTransportProvider = class {
|
|
|
2754
3134
|
}
|
|
2755
3135
|
}
|
|
2756
3136
|
}
|
|
3137
|
+
/**
|
|
3138
|
+
* Create a NIP-17 gift wrap with a custom inner rumor kind.
|
|
3139
|
+
* Replicates the three-layer NIP-59 envelope (rumor → seal → gift wrap)
|
|
3140
|
+
* because NIP17.createGiftWrap hardcodes kind 14 for the inner rumor.
|
|
3141
|
+
*/
|
|
3142
|
+
createCustomKindGiftWrap(recipientPubkeyHex, content, rumorKind) {
|
|
3143
|
+
const senderPubkey = this.keyManager.getPublicKeyHex();
|
|
3144
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
3145
|
+
const rumorTags = [["p", recipientPubkeyHex]];
|
|
3146
|
+
const rumorSerialized = JSON.stringify([0, senderPubkey, now, rumorKind, rumorTags, content]);
|
|
3147
|
+
const rumorId = bytesToHex(sha256(new TextEncoder().encode(rumorSerialized)));
|
|
3148
|
+
const rumor = { id: rumorId, pubkey: senderPubkey, created_at: now, kind: rumorKind, tags: rumorTags, content };
|
|
3149
|
+
const recipientPubkeyBytes = hexToBytes(recipientPubkeyHex);
|
|
3150
|
+
const encryptedRumor = import_nostr_js_sdk.NIP44.encrypt(JSON.stringify(rumor), this.keyManager.getPrivateKey(), recipientPubkeyBytes);
|
|
3151
|
+
const sealTimestamp = now + Math.floor(Math.random() * 2 * TIMESTAMP_RANDOMIZATION) - TIMESTAMP_RANDOMIZATION;
|
|
3152
|
+
const seal = import_nostr_js_sdk.Event.create(this.keyManager, {
|
|
3153
|
+
kind: import_nostr_js_sdk.EventKinds.SEAL,
|
|
3154
|
+
tags: [],
|
|
3155
|
+
content: encryptedRumor,
|
|
3156
|
+
created_at: sealTimestamp
|
|
3157
|
+
});
|
|
3158
|
+
const ephemeralKeys = import_nostr_js_sdk.NostrKeyManager.generate();
|
|
3159
|
+
const encryptedSeal = import_nostr_js_sdk.NIP44.encrypt(JSON.stringify(seal.toJSON()), ephemeralKeys.getPrivateKey(), recipientPubkeyBytes);
|
|
3160
|
+
const wrapTimestamp = now + Math.floor(Math.random() * 2 * TIMESTAMP_RANDOMIZATION) - TIMESTAMP_RANDOMIZATION;
|
|
3161
|
+
const giftWrap = import_nostr_js_sdk.Event.create(ephemeralKeys, {
|
|
3162
|
+
kind: import_nostr_js_sdk.EventKinds.GIFT_WRAP,
|
|
3163
|
+
tags: [["p", recipientPubkeyHex]],
|
|
3164
|
+
content: encryptedSeal,
|
|
3165
|
+
created_at: wrapTimestamp
|
|
3166
|
+
});
|
|
3167
|
+
ephemeralKeys.clear();
|
|
3168
|
+
return giftWrap;
|
|
3169
|
+
}
|
|
2757
3170
|
log(...args) {
|
|
2758
3171
|
if (this.config.debug) {
|
|
2759
3172
|
console.log("[NostrTransportProvider]", ...args);
|
|
@@ -3383,7 +3796,7 @@ async function loadLibp2pModules() {
|
|
|
3383
3796
|
};
|
|
3384
3797
|
}
|
|
3385
3798
|
function deriveEd25519KeyMaterial(privateKeyHex, info = IPNS_HKDF_INFO) {
|
|
3386
|
-
const walletSecret =
|
|
3799
|
+
const walletSecret = hexToBytes2(privateKeyHex);
|
|
3387
3800
|
const infoBytes = new TextEncoder().encode(info);
|
|
3388
3801
|
return hkdf(sha256, walletSecret, void 0, infoBytes, 32);
|
|
3389
3802
|
}
|
|
@@ -5881,6 +6294,11 @@ function resolveGroupChatConfig(network, config) {
|
|
|
5881
6294
|
relays: config.relays ?? [...netConfig.groupRelays]
|
|
5882
6295
|
};
|
|
5883
6296
|
}
|
|
6297
|
+
function resolveMarketConfig(config) {
|
|
6298
|
+
if (!config) return void 0;
|
|
6299
|
+
if (config === true) return {};
|
|
6300
|
+
return { apiUrl: config.apiUrl, timeout: config.timeout };
|
|
6301
|
+
}
|
|
5884
6302
|
|
|
5885
6303
|
// impl/browser/index.ts
|
|
5886
6304
|
if (typeof globalThis.Buffer === "undefined") {
|
|
@@ -5938,7 +6356,7 @@ function createBrowserProviders(config) {
|
|
|
5938
6356
|
const oracleConfig = resolveOracleConfig(network, config?.oracle);
|
|
5939
6357
|
const l1Config = resolveL1Config(network, config?.l1);
|
|
5940
6358
|
const tokenSyncConfig = resolveTokenSyncConfig(network, config?.tokenSync);
|
|
5941
|
-
const storage =
|
|
6359
|
+
const storage = createIndexedDBStorageProvider(config?.storage);
|
|
5942
6360
|
const priceConfig = resolvePriceConfig(config?.price, storage);
|
|
5943
6361
|
const ipfsConfig = tokenSyncConfig?.ipfs;
|
|
5944
6362
|
const ipfsTokenStorage = ipfsConfig?.enabled ? createBrowserIpfsStorageProvider({
|
|
@@ -5947,11 +6365,13 @@ function createBrowserProviders(config) {
|
|
|
5947
6365
|
// reuse debug-like flag
|
|
5948
6366
|
}) : void 0;
|
|
5949
6367
|
const groupChat = resolveGroupChatConfig(network, config?.groupChat);
|
|
6368
|
+
const market = resolveMarketConfig(config?.market);
|
|
5950
6369
|
const networkConfig = getNetworkConfig(network);
|
|
5951
6370
|
TokenRegistry.configure({ remoteUrl: networkConfig.tokenRegistryUrl, storage });
|
|
5952
6371
|
return {
|
|
5953
6372
|
storage,
|
|
5954
6373
|
groupChat,
|
|
6374
|
+
market,
|
|
5955
6375
|
transport: createNostrTransportProvider({
|
|
5956
6376
|
relays: transportConfig.relays,
|
|
5957
6377
|
timeout: transportConfig.timeout,
|