@unicitylabs/sphere-sdk 0.3.9 → 0.4.0
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 +1 -1
- package/dist/connect/index.cjs.map +1 -1
- package/dist/connect/index.js +1 -1
- package/dist/connect/index.js.map +1 -1
- package/dist/core/index.cjs +2657 -49
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +221 -3
- package/dist/core/index.d.ts +221 -3
- package/dist/core/index.js +2653 -45
- package/dist/core/index.js.map +1 -1
- package/dist/impl/browser/index.cjs +450 -47
- package/dist/impl/browser/index.cjs.map +1 -1
- package/dist/impl/browser/index.js +451 -47
- package/dist/impl/browser/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 +2674 -49
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +319 -5
- package/dist/index.d.ts +319 -5
- package/dist/index.js +2663 -45
- 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,264 @@ 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
|
+
try {
|
|
440
|
+
this.db = await Promise.race([
|
|
441
|
+
this.openDatabase(),
|
|
442
|
+
new Promise(
|
|
443
|
+
(_, reject) => setTimeout(() => reject(new Error("IndexedDB open timed out after 5s")), 5e3)
|
|
444
|
+
)
|
|
445
|
+
]);
|
|
446
|
+
this.status = "connected";
|
|
447
|
+
this.log("Connected to IndexedDB");
|
|
448
|
+
} catch (error) {
|
|
449
|
+
this.status = "error";
|
|
450
|
+
throw new Error(`IndexedDB not available: ${error}`);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
async disconnect() {
|
|
454
|
+
if (this.db) {
|
|
455
|
+
this.db.close();
|
|
456
|
+
this.db = null;
|
|
457
|
+
}
|
|
458
|
+
this.status = "disconnected";
|
|
459
|
+
this.log("Disconnected from IndexedDB");
|
|
460
|
+
}
|
|
461
|
+
isConnected() {
|
|
462
|
+
return this.status === "connected" && this.db !== null;
|
|
463
|
+
}
|
|
464
|
+
getStatus() {
|
|
465
|
+
return this.status;
|
|
466
|
+
}
|
|
467
|
+
// ===========================================================================
|
|
468
|
+
// StorageProvider Implementation
|
|
469
|
+
// ===========================================================================
|
|
470
|
+
setIdentity(identity) {
|
|
471
|
+
this.identity = identity;
|
|
472
|
+
this.log("Identity set:", identity.l1Address);
|
|
473
|
+
}
|
|
474
|
+
async get(key) {
|
|
475
|
+
this.ensureConnected();
|
|
476
|
+
const fullKey = this.getFullKey(key);
|
|
477
|
+
const result = await this.idbGet(fullKey);
|
|
478
|
+
return result?.v ?? null;
|
|
479
|
+
}
|
|
480
|
+
async set(key, value) {
|
|
481
|
+
this.ensureConnected();
|
|
482
|
+
const fullKey = this.getFullKey(key);
|
|
483
|
+
await this.idbPut({ k: fullKey, v: value });
|
|
484
|
+
}
|
|
485
|
+
async remove(key) {
|
|
486
|
+
this.ensureConnected();
|
|
487
|
+
const fullKey = this.getFullKey(key);
|
|
488
|
+
await this.idbDelete(fullKey);
|
|
489
|
+
}
|
|
490
|
+
async has(key) {
|
|
491
|
+
this.ensureConnected();
|
|
492
|
+
const fullKey = this.getFullKey(key);
|
|
493
|
+
const count = await this.idbCount(fullKey);
|
|
494
|
+
return count > 0;
|
|
495
|
+
}
|
|
496
|
+
async keys(prefix) {
|
|
497
|
+
this.ensureConnected();
|
|
498
|
+
const basePrefix = this.getFullKey("");
|
|
499
|
+
const searchPrefix = prefix ? this.getFullKey(prefix) : basePrefix;
|
|
500
|
+
const allEntries = await this.idbGetAll();
|
|
501
|
+
const result = [];
|
|
502
|
+
for (const entry of allEntries) {
|
|
503
|
+
if (entry.k.startsWith(searchPrefix)) {
|
|
504
|
+
result.push(entry.k.slice(basePrefix.length));
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
return result;
|
|
508
|
+
}
|
|
509
|
+
async clear(prefix) {
|
|
510
|
+
if (!prefix) {
|
|
511
|
+
if (this.db) {
|
|
512
|
+
this.db.close();
|
|
513
|
+
this.db = null;
|
|
514
|
+
}
|
|
515
|
+
this.status = "disconnected";
|
|
516
|
+
await new Promise((resolve) => {
|
|
517
|
+
try {
|
|
518
|
+
const req = indexedDB.deleteDatabase(this.dbName);
|
|
519
|
+
req.onsuccess = () => resolve();
|
|
520
|
+
req.onerror = () => resolve();
|
|
521
|
+
req.onblocked = () => resolve();
|
|
522
|
+
} catch {
|
|
523
|
+
resolve();
|
|
524
|
+
}
|
|
525
|
+
});
|
|
526
|
+
this.log("Database deleted:", this.dbName);
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
this.ensureConnected();
|
|
530
|
+
const keysToRemove = await this.keys(prefix);
|
|
531
|
+
for (const key of keysToRemove) {
|
|
532
|
+
await this.remove(key);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
async saveTrackedAddresses(entries) {
|
|
536
|
+
await this.set(STORAGE_KEYS_GLOBAL.TRACKED_ADDRESSES, JSON.stringify({ version: 1, addresses: entries }));
|
|
537
|
+
}
|
|
538
|
+
async loadTrackedAddresses() {
|
|
539
|
+
const data = await this.get(STORAGE_KEYS_GLOBAL.TRACKED_ADDRESSES);
|
|
540
|
+
if (!data) return [];
|
|
541
|
+
try {
|
|
542
|
+
const parsed = JSON.parse(data);
|
|
543
|
+
return parsed.addresses ?? [];
|
|
544
|
+
} catch {
|
|
545
|
+
return [];
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
// ===========================================================================
|
|
549
|
+
// Helpers
|
|
550
|
+
// ===========================================================================
|
|
551
|
+
/**
|
|
552
|
+
* Get JSON data
|
|
553
|
+
*/
|
|
554
|
+
async getJSON(key) {
|
|
555
|
+
const value = await this.get(key);
|
|
556
|
+
if (!value) return null;
|
|
557
|
+
try {
|
|
558
|
+
return JSON.parse(value);
|
|
559
|
+
} catch {
|
|
560
|
+
return null;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* Set JSON data
|
|
565
|
+
*/
|
|
566
|
+
async setJSON(key, value) {
|
|
567
|
+
await this.set(key, JSON.stringify(value));
|
|
568
|
+
}
|
|
569
|
+
// ===========================================================================
|
|
570
|
+
// Private: Key Scoping
|
|
571
|
+
// ===========================================================================
|
|
572
|
+
getFullKey(key) {
|
|
573
|
+
const isPerAddressKey = Object.values(STORAGE_KEYS_ADDRESS).includes(key);
|
|
574
|
+
if (isPerAddressKey && this.identity?.directAddress) {
|
|
575
|
+
const addressId = getAddressId(this.identity.directAddress);
|
|
576
|
+
return `${this.prefix}${addressId}_${key}`;
|
|
577
|
+
}
|
|
578
|
+
return `${this.prefix}${key}`;
|
|
579
|
+
}
|
|
580
|
+
ensureConnected() {
|
|
581
|
+
if (this.status !== "connected" || !this.db) {
|
|
582
|
+
throw new Error("IndexedDBStorageProvider not connected");
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
// ===========================================================================
|
|
586
|
+
// Private: IndexedDB Operations
|
|
587
|
+
// ===========================================================================
|
|
588
|
+
openDatabase() {
|
|
589
|
+
return new Promise((resolve, reject) => {
|
|
590
|
+
const request = indexedDB.open(this.dbName, DB_VERSION);
|
|
591
|
+
request.onerror = () => reject(request.error);
|
|
592
|
+
request.onsuccess = () => resolve(request.result);
|
|
593
|
+
request.onblocked = () => {
|
|
594
|
+
console.warn("[IndexedDBStorageProvider] open blocked by another connection");
|
|
595
|
+
};
|
|
596
|
+
request.onupgradeneeded = (event) => {
|
|
597
|
+
const db = event.target.result;
|
|
598
|
+
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
|
599
|
+
db.createObjectStore(STORE_NAME, { keyPath: "k" });
|
|
600
|
+
}
|
|
601
|
+
};
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
idbGet(key) {
|
|
605
|
+
return new Promise((resolve, reject) => {
|
|
606
|
+
const tx = this.db.transaction(STORE_NAME, "readonly");
|
|
607
|
+
const store = tx.objectStore(STORE_NAME);
|
|
608
|
+
const request = store.get(key);
|
|
609
|
+
request.onerror = () => reject(request.error);
|
|
610
|
+
request.onsuccess = () => resolve(request.result ?? void 0);
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
idbPut(entry) {
|
|
614
|
+
return new Promise((resolve, reject) => {
|
|
615
|
+
const tx = this.db.transaction(STORE_NAME, "readwrite");
|
|
616
|
+
const store = tx.objectStore(STORE_NAME);
|
|
617
|
+
const request = store.put(entry);
|
|
618
|
+
request.onerror = () => reject(request.error);
|
|
619
|
+
request.onsuccess = () => resolve();
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
idbDelete(key) {
|
|
623
|
+
return new Promise((resolve, reject) => {
|
|
624
|
+
const tx = this.db.transaction(STORE_NAME, "readwrite");
|
|
625
|
+
const store = tx.objectStore(STORE_NAME);
|
|
626
|
+
const request = store.delete(key);
|
|
627
|
+
request.onerror = () => reject(request.error);
|
|
628
|
+
request.onsuccess = () => resolve();
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
idbCount(key) {
|
|
632
|
+
return new Promise((resolve, reject) => {
|
|
633
|
+
const tx = this.db.transaction(STORE_NAME, "readonly");
|
|
634
|
+
const store = tx.objectStore(STORE_NAME);
|
|
635
|
+
const request = store.count(key);
|
|
636
|
+
request.onerror = () => reject(request.error);
|
|
637
|
+
request.onsuccess = () => resolve(request.result);
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
idbGetAll() {
|
|
641
|
+
return new Promise((resolve, reject) => {
|
|
642
|
+
const tx = this.db.transaction(STORE_NAME, "readonly");
|
|
643
|
+
const store = tx.objectStore(STORE_NAME);
|
|
644
|
+
const request = store.getAll();
|
|
645
|
+
request.onerror = () => reject(request.error);
|
|
646
|
+
request.onsuccess = () => resolve(request.result ?? []);
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
idbClear() {
|
|
650
|
+
return new Promise((resolve, reject) => {
|
|
651
|
+
const tx = this.db.transaction(STORE_NAME, "readwrite");
|
|
652
|
+
const store = tx.objectStore(STORE_NAME);
|
|
653
|
+
const request = store.clear();
|
|
654
|
+
request.onerror = () => reject(request.error);
|
|
655
|
+
request.onsuccess = () => resolve();
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
log(...args) {
|
|
659
|
+
if (this.debug) {
|
|
660
|
+
console.log("[IndexedDBStorageProvider]", ...args);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
};
|
|
664
|
+
function createIndexedDBStorageProvider(config) {
|
|
665
|
+
return new IndexedDBStorageProvider(config);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// impl/browser/storage/IndexedDBTokenStorageProvider.ts
|
|
669
|
+
var DB_NAME2 = "sphere-token-storage";
|
|
670
|
+
var DB_VERSION2 = 1;
|
|
414
671
|
var STORE_TOKENS = "tokens";
|
|
415
672
|
var STORE_META = "meta";
|
|
416
673
|
var IndexedDBTokenStorageProvider = class {
|
|
@@ -423,7 +680,7 @@ var IndexedDBTokenStorageProvider = class {
|
|
|
423
680
|
status = "disconnected";
|
|
424
681
|
identity = null;
|
|
425
682
|
constructor(config) {
|
|
426
|
-
this.dbNamePrefix = config?.dbNamePrefix ??
|
|
683
|
+
this.dbNamePrefix = config?.dbNamePrefix ?? DB_NAME2;
|
|
427
684
|
this.dbName = this.dbNamePrefix;
|
|
428
685
|
}
|
|
429
686
|
setIdentity(identity) {
|
|
@@ -437,6 +694,7 @@ var IndexedDBTokenStorageProvider = class {
|
|
|
437
694
|
try {
|
|
438
695
|
this.db = await this.openDatabase();
|
|
439
696
|
this.status = "connected";
|
|
697
|
+
this.cleanupStaleDatabases();
|
|
440
698
|
return true;
|
|
441
699
|
} catch (error) {
|
|
442
700
|
console.error("[IndexedDBTokenStorage] Failed to initialize:", error);
|
|
@@ -594,15 +852,13 @@ var IndexedDBTokenStorageProvider = class {
|
|
|
594
852
|
return meta !== null;
|
|
595
853
|
}
|
|
596
854
|
async clear() {
|
|
597
|
-
const dbNames = [this.dbName];
|
|
598
855
|
try {
|
|
599
856
|
if (this.db) {
|
|
600
|
-
await this.clearStore(STORE_TOKENS);
|
|
601
|
-
await this.clearStore(STORE_META);
|
|
602
857
|
this.db.close();
|
|
603
858
|
this.db = null;
|
|
604
859
|
}
|
|
605
860
|
this.status = "disconnected";
|
|
861
|
+
const dbNames = [this.dbName];
|
|
606
862
|
if (typeof indexedDB.databases === "function") {
|
|
607
863
|
try {
|
|
608
864
|
const dbs = await Promise.race([
|
|
@@ -614,42 +870,25 @@ var IndexedDBTokenStorageProvider = class {
|
|
|
614
870
|
for (const dbInfo of dbs) {
|
|
615
871
|
if (dbInfo.name && dbInfo.name.startsWith(this.dbNamePrefix) && dbInfo.name !== this.dbName) {
|
|
616
872
|
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
873
|
}
|
|
639
874
|
}
|
|
640
875
|
} catch {
|
|
641
876
|
}
|
|
642
877
|
}
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
878
|
+
await Promise.all(dbNames.map(
|
|
879
|
+
(name) => new Promise((resolve) => {
|
|
880
|
+
try {
|
|
881
|
+
const req = indexedDB.deleteDatabase(name);
|
|
882
|
+
req.onsuccess = () => resolve();
|
|
883
|
+
req.onerror = () => resolve();
|
|
884
|
+
req.onblocked = () => {
|
|
885
|
+
resolve();
|
|
886
|
+
};
|
|
887
|
+
} catch {
|
|
888
|
+
resolve();
|
|
889
|
+
}
|
|
890
|
+
})
|
|
891
|
+
));
|
|
653
892
|
return true;
|
|
654
893
|
} catch (err) {
|
|
655
894
|
console.warn("[IndexedDBTokenStorage] clear() failed:", err);
|
|
@@ -659,9 +898,29 @@ var IndexedDBTokenStorageProvider = class {
|
|
|
659
898
|
// =========================================================================
|
|
660
899
|
// Private IndexedDB helpers
|
|
661
900
|
// =========================================================================
|
|
901
|
+
/**
|
|
902
|
+
* Delete stale databases from other addresses (fire-and-forget, background).
|
|
903
|
+
* Called after the current database is already open, so deleteDatabase
|
|
904
|
+
* on other databases won't block anything.
|
|
905
|
+
*/
|
|
906
|
+
cleanupStaleDatabases() {
|
|
907
|
+
if (typeof indexedDB.databases !== "function") return;
|
|
908
|
+
indexedDB.databases().then((dbs) => {
|
|
909
|
+
for (const dbInfo of dbs) {
|
|
910
|
+
if (dbInfo.name && dbInfo.name.startsWith(this.dbNamePrefix) && dbInfo.name !== this.dbName) {
|
|
911
|
+
const req = indexedDB.deleteDatabase(dbInfo.name);
|
|
912
|
+
req.onerror = () => {
|
|
913
|
+
};
|
|
914
|
+
req.onblocked = () => {
|
|
915
|
+
};
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
}).catch(() => {
|
|
919
|
+
});
|
|
920
|
+
}
|
|
662
921
|
openDatabase() {
|
|
663
922
|
return new Promise((resolve, reject) => {
|
|
664
|
-
const request = indexedDB.open(this.dbName,
|
|
923
|
+
const request = indexedDB.open(this.dbName, DB_VERSION2);
|
|
665
924
|
request.onerror = () => {
|
|
666
925
|
reject(request.error);
|
|
667
926
|
};
|
|
@@ -804,6 +1063,52 @@ function createView(arr) {
|
|
|
804
1063
|
function rotr(word, shift) {
|
|
805
1064
|
return word << 32 - shift | word >>> shift;
|
|
806
1065
|
}
|
|
1066
|
+
var hasHexBuiltin = /* @__PURE__ */ (() => (
|
|
1067
|
+
// @ts-ignore
|
|
1068
|
+
typeof Uint8Array.from([]).toHex === "function" && typeof Uint8Array.fromHex === "function"
|
|
1069
|
+
))();
|
|
1070
|
+
var hexes = /* @__PURE__ */ Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, "0"));
|
|
1071
|
+
function bytesToHex(bytes) {
|
|
1072
|
+
abytes(bytes);
|
|
1073
|
+
if (hasHexBuiltin)
|
|
1074
|
+
return bytes.toHex();
|
|
1075
|
+
let hex = "";
|
|
1076
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
1077
|
+
hex += hexes[bytes[i]];
|
|
1078
|
+
}
|
|
1079
|
+
return hex;
|
|
1080
|
+
}
|
|
1081
|
+
var asciis = { _0: 48, _9: 57, A: 65, F: 70, a: 97, f: 102 };
|
|
1082
|
+
function asciiToBase16(ch) {
|
|
1083
|
+
if (ch >= asciis._0 && ch <= asciis._9)
|
|
1084
|
+
return ch - asciis._0;
|
|
1085
|
+
if (ch >= asciis.A && ch <= asciis.F)
|
|
1086
|
+
return ch - (asciis.A - 10);
|
|
1087
|
+
if (ch >= asciis.a && ch <= asciis.f)
|
|
1088
|
+
return ch - (asciis.a - 10);
|
|
1089
|
+
return;
|
|
1090
|
+
}
|
|
1091
|
+
function hexToBytes(hex) {
|
|
1092
|
+
if (typeof hex !== "string")
|
|
1093
|
+
throw new Error("hex string expected, got " + typeof hex);
|
|
1094
|
+
if (hasHexBuiltin)
|
|
1095
|
+
return Uint8Array.fromHex(hex);
|
|
1096
|
+
const hl = hex.length;
|
|
1097
|
+
const al = hl / 2;
|
|
1098
|
+
if (hl % 2)
|
|
1099
|
+
throw new Error("hex string expected, got unpadded hex of length " + hl);
|
|
1100
|
+
const array = new Uint8Array(al);
|
|
1101
|
+
for (let ai = 0, hi = 0; ai < al; ai++, hi += 2) {
|
|
1102
|
+
const n1 = asciiToBase16(hex.charCodeAt(hi));
|
|
1103
|
+
const n2 = asciiToBase16(hex.charCodeAt(hi + 1));
|
|
1104
|
+
if (n1 === void 0 || n2 === void 0) {
|
|
1105
|
+
const char = hex[hi] + hex[hi + 1];
|
|
1106
|
+
throw new Error('hex string expected, got non-hex character "' + char + '" at index ' + hi);
|
|
1107
|
+
}
|
|
1108
|
+
array[ai] = n1 * 16 + n2;
|
|
1109
|
+
}
|
|
1110
|
+
return array;
|
|
1111
|
+
}
|
|
807
1112
|
function createHasher(hashCons, info = {}) {
|
|
808
1113
|
const hashC = (msg, opts) => hashCons(opts).update(msg).digest();
|
|
809
1114
|
const tmp = hashCons(void 0);
|
|
@@ -1299,7 +1604,7 @@ function publicKeyToAddress(publicKey, prefix = "alpha", witnessVersion = 0) {
|
|
|
1299
1604
|
const programBytes = hash160ToBytes(pubKeyHash);
|
|
1300
1605
|
return encodeBech32(prefix, witnessVersion, programBytes);
|
|
1301
1606
|
}
|
|
1302
|
-
function
|
|
1607
|
+
function hexToBytes2(hex) {
|
|
1303
1608
|
const matches = hex.match(/../g);
|
|
1304
1609
|
if (!matches) {
|
|
1305
1610
|
return new Uint8Array(0);
|
|
@@ -1326,6 +1631,8 @@ function defaultUUIDGenerator() {
|
|
|
1326
1631
|
}
|
|
1327
1632
|
|
|
1328
1633
|
// transport/NostrTransportProvider.ts
|
|
1634
|
+
var COMPOSING_INDICATOR_KIND = 25050;
|
|
1635
|
+
var TIMESTAMP_RANDOMIZATION = 2 * 24 * 60 * 60;
|
|
1329
1636
|
var EVENT_KINDS = NOSTR_EVENT_KINDS;
|
|
1330
1637
|
function hashAddressForTag(address) {
|
|
1331
1638
|
const bytes = new TextEncoder().encode("unicity:address:" + address);
|
|
@@ -1408,6 +1715,8 @@ var NostrTransportProvider = class {
|
|
|
1408
1715
|
paymentRequestResponseHandlers = /* @__PURE__ */ new Set();
|
|
1409
1716
|
readReceiptHandlers = /* @__PURE__ */ new Set();
|
|
1410
1717
|
typingIndicatorHandlers = /* @__PURE__ */ new Set();
|
|
1718
|
+
composingHandlers = /* @__PURE__ */ new Set();
|
|
1719
|
+
pendingMessages = [];
|
|
1411
1720
|
broadcastHandlers = /* @__PURE__ */ new Map();
|
|
1412
1721
|
eventCallbacks = /* @__PURE__ */ new Set();
|
|
1413
1722
|
constructor(config) {
|
|
@@ -1680,6 +1989,18 @@ var NostrTransportProvider = class {
|
|
|
1680
1989
|
}
|
|
1681
1990
|
onMessage(handler) {
|
|
1682
1991
|
this.messageHandlers.add(handler);
|
|
1992
|
+
if (this.pendingMessages.length > 0) {
|
|
1993
|
+
const pending = this.pendingMessages;
|
|
1994
|
+
this.pendingMessages = [];
|
|
1995
|
+
this.log("Flushing", pending.length, "buffered messages to new handler");
|
|
1996
|
+
for (const message of pending) {
|
|
1997
|
+
try {
|
|
1998
|
+
handler(message);
|
|
1999
|
+
} catch (error) {
|
|
2000
|
+
this.log("Message handler error (buffered):", error);
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
2003
|
+
}
|
|
1683
2004
|
return () => this.messageHandlers.delete(handler);
|
|
1684
2005
|
}
|
|
1685
2006
|
async sendTokenTransfer(recipientPubkey, payload) {
|
|
@@ -1800,6 +2121,19 @@ var NostrTransportProvider = class {
|
|
|
1800
2121
|
this.typingIndicatorHandlers.add(handler);
|
|
1801
2122
|
return () => this.typingIndicatorHandlers.delete(handler);
|
|
1802
2123
|
}
|
|
2124
|
+
// ===========================================================================
|
|
2125
|
+
// Composing Indicators (NIP-59 kind 25050)
|
|
2126
|
+
// ===========================================================================
|
|
2127
|
+
onComposing(handler) {
|
|
2128
|
+
this.composingHandlers.add(handler);
|
|
2129
|
+
return () => this.composingHandlers.delete(handler);
|
|
2130
|
+
}
|
|
2131
|
+
async sendComposingIndicator(recipientPubkey, content) {
|
|
2132
|
+
this.ensureReady();
|
|
2133
|
+
const nostrRecipient = recipientPubkey.length === 66 && (recipientPubkey.startsWith("02") || recipientPubkey.startsWith("03")) ? recipientPubkey.slice(2) : recipientPubkey;
|
|
2134
|
+
const giftWrap = this.createCustomKindGiftWrap(nostrRecipient, content, COMPOSING_INDICATOR_KIND);
|
|
2135
|
+
await this.publishEvent(giftWrap);
|
|
2136
|
+
}
|
|
1803
2137
|
/**
|
|
1804
2138
|
* Resolve any identifier to full peer information.
|
|
1805
2139
|
* Routes to the appropriate specific resolve method based on identifier format.
|
|
@@ -2299,6 +2633,30 @@ var NostrTransportProvider = class {
|
|
|
2299
2633
|
}
|
|
2300
2634
|
return;
|
|
2301
2635
|
}
|
|
2636
|
+
if (pm.kind === COMPOSING_INDICATOR_KIND) {
|
|
2637
|
+
let senderNametag2;
|
|
2638
|
+
let expiresIn = 3e4;
|
|
2639
|
+
try {
|
|
2640
|
+
const parsed = JSON.parse(pm.content);
|
|
2641
|
+
senderNametag2 = parsed.senderNametag || void 0;
|
|
2642
|
+
expiresIn = parsed.expiresIn ?? 3e4;
|
|
2643
|
+
} catch {
|
|
2644
|
+
}
|
|
2645
|
+
const indicator = {
|
|
2646
|
+
senderPubkey: pm.senderPubkey,
|
|
2647
|
+
senderNametag: senderNametag2,
|
|
2648
|
+
expiresIn
|
|
2649
|
+
};
|
|
2650
|
+
this.log("Composing indicator from:", indicator.senderNametag || pm.senderPubkey?.slice(0, 16));
|
|
2651
|
+
for (const handler of this.composingHandlers) {
|
|
2652
|
+
try {
|
|
2653
|
+
handler(indicator);
|
|
2654
|
+
} catch (e) {
|
|
2655
|
+
this.log("Composing handler error:", e);
|
|
2656
|
+
}
|
|
2657
|
+
}
|
|
2658
|
+
return;
|
|
2659
|
+
}
|
|
2302
2660
|
try {
|
|
2303
2661
|
const parsed = JSON.parse(pm.content);
|
|
2304
2662
|
if (parsed?.type === "typing") {
|
|
@@ -2345,12 +2703,17 @@ var NostrTransportProvider = class {
|
|
|
2345
2703
|
encrypted: true
|
|
2346
2704
|
};
|
|
2347
2705
|
this.emitEvent({ type: "message:received", timestamp: Date.now() });
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2706
|
+
if (this.messageHandlers.size === 0) {
|
|
2707
|
+
this.log("No message handlers registered, buffering message for later delivery");
|
|
2708
|
+
this.pendingMessages.push(message);
|
|
2709
|
+
} else {
|
|
2710
|
+
this.log("Dispatching to", this.messageHandlers.size, "handlers");
|
|
2711
|
+
for (const handler of this.messageHandlers) {
|
|
2712
|
+
try {
|
|
2713
|
+
handler(message);
|
|
2714
|
+
} catch (error) {
|
|
2715
|
+
this.log("Message handler error:", error);
|
|
2716
|
+
}
|
|
2354
2717
|
}
|
|
2355
2718
|
}
|
|
2356
2719
|
} catch (err) {
|
|
@@ -2754,6 +3117,39 @@ var NostrTransportProvider = class {
|
|
|
2754
3117
|
}
|
|
2755
3118
|
}
|
|
2756
3119
|
}
|
|
3120
|
+
/**
|
|
3121
|
+
* Create a NIP-17 gift wrap with a custom inner rumor kind.
|
|
3122
|
+
* Replicates the three-layer NIP-59 envelope (rumor → seal → gift wrap)
|
|
3123
|
+
* because NIP17.createGiftWrap hardcodes kind 14 for the inner rumor.
|
|
3124
|
+
*/
|
|
3125
|
+
createCustomKindGiftWrap(recipientPubkeyHex, content, rumorKind) {
|
|
3126
|
+
const senderPubkey = this.keyManager.getPublicKeyHex();
|
|
3127
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
3128
|
+
const rumorTags = [["p", recipientPubkeyHex]];
|
|
3129
|
+
const rumorSerialized = JSON.stringify([0, senderPubkey, now, rumorKind, rumorTags, content]);
|
|
3130
|
+
const rumorId = bytesToHex(sha256(new TextEncoder().encode(rumorSerialized)));
|
|
3131
|
+
const rumor = { id: rumorId, pubkey: senderPubkey, created_at: now, kind: rumorKind, tags: rumorTags, content };
|
|
3132
|
+
const recipientPubkeyBytes = hexToBytes(recipientPubkeyHex);
|
|
3133
|
+
const encryptedRumor = import_nostr_js_sdk.NIP44.encrypt(JSON.stringify(rumor), this.keyManager.getPrivateKey(), recipientPubkeyBytes);
|
|
3134
|
+
const sealTimestamp = now + Math.floor(Math.random() * 2 * TIMESTAMP_RANDOMIZATION) - TIMESTAMP_RANDOMIZATION;
|
|
3135
|
+
const seal = import_nostr_js_sdk.Event.create(this.keyManager, {
|
|
3136
|
+
kind: import_nostr_js_sdk.EventKinds.SEAL,
|
|
3137
|
+
tags: [],
|
|
3138
|
+
content: encryptedRumor,
|
|
3139
|
+
created_at: sealTimestamp
|
|
3140
|
+
});
|
|
3141
|
+
const ephemeralKeys = import_nostr_js_sdk.NostrKeyManager.generate();
|
|
3142
|
+
const encryptedSeal = import_nostr_js_sdk.NIP44.encrypt(JSON.stringify(seal.toJSON()), ephemeralKeys.getPrivateKey(), recipientPubkeyBytes);
|
|
3143
|
+
const wrapTimestamp = now + Math.floor(Math.random() * 2 * TIMESTAMP_RANDOMIZATION) - TIMESTAMP_RANDOMIZATION;
|
|
3144
|
+
const giftWrap = import_nostr_js_sdk.Event.create(ephemeralKeys, {
|
|
3145
|
+
kind: import_nostr_js_sdk.EventKinds.GIFT_WRAP,
|
|
3146
|
+
tags: [["p", recipientPubkeyHex]],
|
|
3147
|
+
content: encryptedSeal,
|
|
3148
|
+
created_at: wrapTimestamp
|
|
3149
|
+
});
|
|
3150
|
+
ephemeralKeys.clear();
|
|
3151
|
+
return giftWrap;
|
|
3152
|
+
}
|
|
2757
3153
|
log(...args) {
|
|
2758
3154
|
if (this.config.debug) {
|
|
2759
3155
|
console.log("[NostrTransportProvider]", ...args);
|
|
@@ -3383,7 +3779,7 @@ async function loadLibp2pModules() {
|
|
|
3383
3779
|
};
|
|
3384
3780
|
}
|
|
3385
3781
|
function deriveEd25519KeyMaterial(privateKeyHex, info = IPNS_HKDF_INFO) {
|
|
3386
|
-
const walletSecret =
|
|
3782
|
+
const walletSecret = hexToBytes2(privateKeyHex);
|
|
3387
3783
|
const infoBytes = new TextEncoder().encode(info);
|
|
3388
3784
|
return hkdf(sha256, walletSecret, void 0, infoBytes, 32);
|
|
3389
3785
|
}
|
|
@@ -5881,6 +6277,11 @@ function resolveGroupChatConfig(network, config) {
|
|
|
5881
6277
|
relays: config.relays ?? [...netConfig.groupRelays]
|
|
5882
6278
|
};
|
|
5883
6279
|
}
|
|
6280
|
+
function resolveMarketConfig(config) {
|
|
6281
|
+
if (!config) return void 0;
|
|
6282
|
+
if (config === true) return {};
|
|
6283
|
+
return { apiUrl: config.apiUrl, timeout: config.timeout };
|
|
6284
|
+
}
|
|
5884
6285
|
|
|
5885
6286
|
// impl/browser/index.ts
|
|
5886
6287
|
if (typeof globalThis.Buffer === "undefined") {
|
|
@@ -5938,7 +6339,7 @@ function createBrowserProviders(config) {
|
|
|
5938
6339
|
const oracleConfig = resolveOracleConfig(network, config?.oracle);
|
|
5939
6340
|
const l1Config = resolveL1Config(network, config?.l1);
|
|
5940
6341
|
const tokenSyncConfig = resolveTokenSyncConfig(network, config?.tokenSync);
|
|
5941
|
-
const storage =
|
|
6342
|
+
const storage = createIndexedDBStorageProvider(config?.storage);
|
|
5942
6343
|
const priceConfig = resolvePriceConfig(config?.price, storage);
|
|
5943
6344
|
const ipfsConfig = tokenSyncConfig?.ipfs;
|
|
5944
6345
|
const ipfsTokenStorage = ipfsConfig?.enabled ? createBrowserIpfsStorageProvider({
|
|
@@ -5947,11 +6348,13 @@ function createBrowserProviders(config) {
|
|
|
5947
6348
|
// reuse debug-like flag
|
|
5948
6349
|
}) : void 0;
|
|
5949
6350
|
const groupChat = resolveGroupChatConfig(network, config?.groupChat);
|
|
6351
|
+
const market = resolveMarketConfig(config?.market);
|
|
5950
6352
|
const networkConfig = getNetworkConfig(network);
|
|
5951
6353
|
TokenRegistry.configure({ remoteUrl: networkConfig.tokenRegistryUrl, storage });
|
|
5952
6354
|
return {
|
|
5953
6355
|
storage,
|
|
5954
6356
|
groupChat,
|
|
6357
|
+
market,
|
|
5955
6358
|
transport: createNostrTransportProvider({
|
|
5956
6359
|
relays: transportConfig.relays,
|
|
5957
6360
|
timeout: transportConfig.timeout,
|