@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
|
@@ -350,9 +350,275 @@ function createLocalStorageProvider(config) {
|
|
|
350
350
|
return new LocalStorageProvider(config);
|
|
351
351
|
}
|
|
352
352
|
|
|
353
|
-
// impl/browser/storage/
|
|
354
|
-
var DB_NAME = "sphere-
|
|
353
|
+
// impl/browser/storage/IndexedDBStorageProvider.ts
|
|
354
|
+
var DB_NAME = "sphere-storage";
|
|
355
355
|
var DB_VERSION = 1;
|
|
356
|
+
var STORE_NAME = "kv";
|
|
357
|
+
var IndexedDBStorageProvider = class {
|
|
358
|
+
id = "indexeddb-storage";
|
|
359
|
+
name = "IndexedDB Storage";
|
|
360
|
+
type = "local";
|
|
361
|
+
description = "Browser IndexedDB for large-capacity persistence";
|
|
362
|
+
prefix;
|
|
363
|
+
dbName;
|
|
364
|
+
debug;
|
|
365
|
+
identity = null;
|
|
366
|
+
status = "disconnected";
|
|
367
|
+
db = null;
|
|
368
|
+
constructor(config) {
|
|
369
|
+
this.prefix = config?.prefix ?? "sphere_";
|
|
370
|
+
this.dbName = config?.dbName ?? DB_NAME;
|
|
371
|
+
this.debug = config?.debug ?? false;
|
|
372
|
+
}
|
|
373
|
+
// ===========================================================================
|
|
374
|
+
// BaseProvider Implementation
|
|
375
|
+
// ===========================================================================
|
|
376
|
+
async connect() {
|
|
377
|
+
if (this.status === "connected" && this.db) return;
|
|
378
|
+
this.status = "connecting";
|
|
379
|
+
console.log(`[IndexedDBStorage] connect: opening db=${this.dbName}`);
|
|
380
|
+
try {
|
|
381
|
+
this.db = await Promise.race([
|
|
382
|
+
this.openDatabase(),
|
|
383
|
+
new Promise(
|
|
384
|
+
(_, reject) => setTimeout(() => reject(new Error("IndexedDB open timed out after 5s")), 5e3)
|
|
385
|
+
)
|
|
386
|
+
]);
|
|
387
|
+
this.status = "connected";
|
|
388
|
+
console.log(`[IndexedDBStorage] connect: connected to db=${this.dbName}`);
|
|
389
|
+
} catch (error) {
|
|
390
|
+
this.status = "error";
|
|
391
|
+
throw new Error(`IndexedDB not available: ${error}`);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
async disconnect() {
|
|
395
|
+
console.log(`[IndexedDBStorage] disconnect: closing db=${this.dbName}, wasConnected=${!!this.db}`);
|
|
396
|
+
if (this.db) {
|
|
397
|
+
this.db.close();
|
|
398
|
+
this.db = null;
|
|
399
|
+
}
|
|
400
|
+
this.status = "disconnected";
|
|
401
|
+
}
|
|
402
|
+
isConnected() {
|
|
403
|
+
return this.status === "connected" && this.db !== null;
|
|
404
|
+
}
|
|
405
|
+
getStatus() {
|
|
406
|
+
return this.status;
|
|
407
|
+
}
|
|
408
|
+
// ===========================================================================
|
|
409
|
+
// StorageProvider Implementation
|
|
410
|
+
// ===========================================================================
|
|
411
|
+
setIdentity(identity) {
|
|
412
|
+
this.identity = identity;
|
|
413
|
+
this.log("Identity set:", identity.l1Address);
|
|
414
|
+
}
|
|
415
|
+
async get(key) {
|
|
416
|
+
this.ensureConnected();
|
|
417
|
+
const fullKey = this.getFullKey(key);
|
|
418
|
+
const result = await this.idbGet(fullKey);
|
|
419
|
+
return result?.v ?? null;
|
|
420
|
+
}
|
|
421
|
+
async set(key, value) {
|
|
422
|
+
this.ensureConnected();
|
|
423
|
+
const fullKey = this.getFullKey(key);
|
|
424
|
+
await this.idbPut({ k: fullKey, v: value });
|
|
425
|
+
}
|
|
426
|
+
async remove(key) {
|
|
427
|
+
this.ensureConnected();
|
|
428
|
+
const fullKey = this.getFullKey(key);
|
|
429
|
+
await this.idbDelete(fullKey);
|
|
430
|
+
}
|
|
431
|
+
async has(key) {
|
|
432
|
+
this.ensureConnected();
|
|
433
|
+
const fullKey = this.getFullKey(key);
|
|
434
|
+
const count = await this.idbCount(fullKey);
|
|
435
|
+
return count > 0;
|
|
436
|
+
}
|
|
437
|
+
async keys(prefix) {
|
|
438
|
+
this.ensureConnected();
|
|
439
|
+
const basePrefix = this.getFullKey("");
|
|
440
|
+
const searchPrefix = prefix ? this.getFullKey(prefix) : basePrefix;
|
|
441
|
+
const allEntries = await this.idbGetAll();
|
|
442
|
+
const result = [];
|
|
443
|
+
for (const entry of allEntries) {
|
|
444
|
+
if (entry.k.startsWith(searchPrefix)) {
|
|
445
|
+
result.push(entry.k.slice(basePrefix.length));
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
return result;
|
|
449
|
+
}
|
|
450
|
+
async clear(prefix) {
|
|
451
|
+
if (!prefix) {
|
|
452
|
+
console.log(`[IndexedDBStorage] clear: starting, db=${this.dbName}, wasConnected=${!!this.db}`);
|
|
453
|
+
if (this.db) {
|
|
454
|
+
this.db.close();
|
|
455
|
+
this.db = null;
|
|
456
|
+
}
|
|
457
|
+
this.status = "disconnected";
|
|
458
|
+
await new Promise((resolve) => {
|
|
459
|
+
try {
|
|
460
|
+
const req = indexedDB.deleteDatabase(this.dbName);
|
|
461
|
+
req.onsuccess = () => {
|
|
462
|
+
console.log(`[IndexedDBStorage] clear: deleted db=${this.dbName}`);
|
|
463
|
+
resolve();
|
|
464
|
+
};
|
|
465
|
+
req.onerror = () => {
|
|
466
|
+
console.warn(`[IndexedDBStorage] clear: error deleting db=${this.dbName}`, req.error);
|
|
467
|
+
resolve();
|
|
468
|
+
};
|
|
469
|
+
req.onblocked = () => {
|
|
470
|
+
console.warn(`[IndexedDBStorage] clear: deleteDatabase blocked for db=${this.dbName}`);
|
|
471
|
+
resolve();
|
|
472
|
+
};
|
|
473
|
+
} catch {
|
|
474
|
+
resolve();
|
|
475
|
+
}
|
|
476
|
+
});
|
|
477
|
+
this.log("Database deleted:", this.dbName);
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
this.ensureConnected();
|
|
481
|
+
const keysToRemove = await this.keys(prefix);
|
|
482
|
+
for (const key of keysToRemove) {
|
|
483
|
+
await this.remove(key);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
async saveTrackedAddresses(entries) {
|
|
487
|
+
await this.set(STORAGE_KEYS_GLOBAL.TRACKED_ADDRESSES, JSON.stringify({ version: 1, addresses: entries }));
|
|
488
|
+
}
|
|
489
|
+
async loadTrackedAddresses() {
|
|
490
|
+
const data = await this.get(STORAGE_KEYS_GLOBAL.TRACKED_ADDRESSES);
|
|
491
|
+
if (!data) return [];
|
|
492
|
+
try {
|
|
493
|
+
const parsed = JSON.parse(data);
|
|
494
|
+
return parsed.addresses ?? [];
|
|
495
|
+
} catch {
|
|
496
|
+
return [];
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
// ===========================================================================
|
|
500
|
+
// Helpers
|
|
501
|
+
// ===========================================================================
|
|
502
|
+
/**
|
|
503
|
+
* Get JSON data
|
|
504
|
+
*/
|
|
505
|
+
async getJSON(key) {
|
|
506
|
+
const value = await this.get(key);
|
|
507
|
+
if (!value) return null;
|
|
508
|
+
try {
|
|
509
|
+
return JSON.parse(value);
|
|
510
|
+
} catch {
|
|
511
|
+
return null;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Set JSON data
|
|
516
|
+
*/
|
|
517
|
+
async setJSON(key, value) {
|
|
518
|
+
await this.set(key, JSON.stringify(value));
|
|
519
|
+
}
|
|
520
|
+
// ===========================================================================
|
|
521
|
+
// Private: Key Scoping
|
|
522
|
+
// ===========================================================================
|
|
523
|
+
getFullKey(key) {
|
|
524
|
+
const isPerAddressKey = Object.values(STORAGE_KEYS_ADDRESS).includes(key);
|
|
525
|
+
if (isPerAddressKey && this.identity?.directAddress) {
|
|
526
|
+
const addressId = getAddressId(this.identity.directAddress);
|
|
527
|
+
return `${this.prefix}${addressId}_${key}`;
|
|
528
|
+
}
|
|
529
|
+
return `${this.prefix}${key}`;
|
|
530
|
+
}
|
|
531
|
+
ensureConnected() {
|
|
532
|
+
if (this.status !== "connected" || !this.db) {
|
|
533
|
+
throw new Error("IndexedDBStorageProvider not connected");
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
// ===========================================================================
|
|
537
|
+
// Private: IndexedDB Operations
|
|
538
|
+
// ===========================================================================
|
|
539
|
+
openDatabase() {
|
|
540
|
+
return new Promise((resolve, reject) => {
|
|
541
|
+
const request = indexedDB.open(this.dbName, DB_VERSION);
|
|
542
|
+
request.onerror = () => reject(request.error);
|
|
543
|
+
request.onsuccess = () => resolve(request.result);
|
|
544
|
+
request.onblocked = () => {
|
|
545
|
+
console.warn("[IndexedDBStorageProvider] open blocked by another connection");
|
|
546
|
+
};
|
|
547
|
+
request.onupgradeneeded = (event) => {
|
|
548
|
+
const db = event.target.result;
|
|
549
|
+
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
|
550
|
+
db.createObjectStore(STORE_NAME, { keyPath: "k" });
|
|
551
|
+
}
|
|
552
|
+
};
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
idbGet(key) {
|
|
556
|
+
return new Promise((resolve, reject) => {
|
|
557
|
+
const tx = this.db.transaction(STORE_NAME, "readonly");
|
|
558
|
+
const store = tx.objectStore(STORE_NAME);
|
|
559
|
+
const request = store.get(key);
|
|
560
|
+
request.onerror = () => reject(request.error);
|
|
561
|
+
request.onsuccess = () => resolve(request.result ?? void 0);
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
idbPut(entry) {
|
|
565
|
+
return new Promise((resolve, reject) => {
|
|
566
|
+
const tx = this.db.transaction(STORE_NAME, "readwrite");
|
|
567
|
+
const store = tx.objectStore(STORE_NAME);
|
|
568
|
+
const request = store.put(entry);
|
|
569
|
+
request.onerror = () => reject(request.error);
|
|
570
|
+
request.onsuccess = () => resolve();
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
idbDelete(key) {
|
|
574
|
+
return new Promise((resolve, reject) => {
|
|
575
|
+
const tx = this.db.transaction(STORE_NAME, "readwrite");
|
|
576
|
+
const store = tx.objectStore(STORE_NAME);
|
|
577
|
+
const request = store.delete(key);
|
|
578
|
+
request.onerror = () => reject(request.error);
|
|
579
|
+
request.onsuccess = () => resolve();
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
idbCount(key) {
|
|
583
|
+
return new Promise((resolve, reject) => {
|
|
584
|
+
const tx = this.db.transaction(STORE_NAME, "readonly");
|
|
585
|
+
const store = tx.objectStore(STORE_NAME);
|
|
586
|
+
const request = store.count(key);
|
|
587
|
+
request.onerror = () => reject(request.error);
|
|
588
|
+
request.onsuccess = () => resolve(request.result);
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
idbGetAll() {
|
|
592
|
+
return new Promise((resolve, reject) => {
|
|
593
|
+
const tx = this.db.transaction(STORE_NAME, "readonly");
|
|
594
|
+
const store = tx.objectStore(STORE_NAME);
|
|
595
|
+
const request = store.getAll();
|
|
596
|
+
request.onerror = () => reject(request.error);
|
|
597
|
+
request.onsuccess = () => resolve(request.result ?? []);
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
idbClear() {
|
|
601
|
+
return new Promise((resolve, reject) => {
|
|
602
|
+
const tx = this.db.transaction(STORE_NAME, "readwrite");
|
|
603
|
+
const store = tx.objectStore(STORE_NAME);
|
|
604
|
+
const request = store.clear();
|
|
605
|
+
request.onerror = () => reject(request.error);
|
|
606
|
+
request.onsuccess = () => resolve();
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
log(...args) {
|
|
610
|
+
if (this.debug) {
|
|
611
|
+
console.log("[IndexedDBStorageProvider]", ...args);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
};
|
|
615
|
+
function createIndexedDBStorageProvider(config) {
|
|
616
|
+
return new IndexedDBStorageProvider(config);
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// impl/browser/storage/IndexedDBTokenStorageProvider.ts
|
|
620
|
+
var DB_NAME2 = "sphere-token-storage";
|
|
621
|
+
var DB_VERSION2 = 1;
|
|
356
622
|
var STORE_TOKENS = "tokens";
|
|
357
623
|
var STORE_META = "meta";
|
|
358
624
|
var IndexedDBTokenStorageProvider = class {
|
|
@@ -365,7 +631,7 @@ var IndexedDBTokenStorageProvider = class {
|
|
|
365
631
|
status = "disconnected";
|
|
366
632
|
identity = null;
|
|
367
633
|
constructor(config) {
|
|
368
|
-
this.dbNamePrefix = config?.dbNamePrefix ??
|
|
634
|
+
this.dbNamePrefix = config?.dbNamePrefix ?? DB_NAME2;
|
|
369
635
|
this.dbName = this.dbNamePrefix;
|
|
370
636
|
}
|
|
371
637
|
setIdentity(identity) {
|
|
@@ -374,11 +640,19 @@ var IndexedDBTokenStorageProvider = class {
|
|
|
374
640
|
const addressId = getAddressId(identity.directAddress);
|
|
375
641
|
this.dbName = `${this.dbNamePrefix}-${addressId}`;
|
|
376
642
|
}
|
|
643
|
+
console.log(`[IndexedDBTokenStorage] setIdentity \u2192 db=${this.dbName}`);
|
|
377
644
|
}
|
|
378
645
|
async initialize() {
|
|
379
646
|
try {
|
|
647
|
+
if (this.db) {
|
|
648
|
+
console.log(`[IndexedDBTokenStorage] initialize: closing existing connection before re-open (db=${this.dbName})`);
|
|
649
|
+
this.db.close();
|
|
650
|
+
this.db = null;
|
|
651
|
+
}
|
|
652
|
+
console.log(`[IndexedDBTokenStorage] initialize: opening db=${this.dbName}`);
|
|
380
653
|
this.db = await this.openDatabase();
|
|
381
654
|
this.status = "connected";
|
|
655
|
+
console.log(`[IndexedDBTokenStorage] initialize: connected to db=${this.dbName}`);
|
|
382
656
|
return true;
|
|
383
657
|
} catch (error) {
|
|
384
658
|
console.error("[IndexedDBTokenStorage] Failed to initialize:", error);
|
|
@@ -387,6 +661,7 @@ var IndexedDBTokenStorageProvider = class {
|
|
|
387
661
|
}
|
|
388
662
|
}
|
|
389
663
|
async shutdown() {
|
|
664
|
+
console.log(`[IndexedDBTokenStorage] shutdown: closing db=${this.dbName}, wasConnected=${!!this.db}`);
|
|
390
665
|
if (this.db) {
|
|
391
666
|
this.db.close();
|
|
392
667
|
this.db = null;
|
|
@@ -407,6 +682,7 @@ var IndexedDBTokenStorageProvider = class {
|
|
|
407
682
|
}
|
|
408
683
|
async load() {
|
|
409
684
|
if (!this.db) {
|
|
685
|
+
console.warn(`[IndexedDBTokenStorage] load: db not initialized (db=${this.dbName})`);
|
|
410
686
|
return {
|
|
411
687
|
success: false,
|
|
412
688
|
error: "Database not initialized",
|
|
@@ -455,6 +731,8 @@ var IndexedDBTokenStorageProvider = class {
|
|
|
455
731
|
if (invalid) {
|
|
456
732
|
data._invalid = invalid;
|
|
457
733
|
}
|
|
734
|
+
const tokenKeys = Object.keys(data).filter((k) => k.startsWith("_") && !["_meta", "_tombstones", "_outbox", "_sent", "_invalid"].includes(k));
|
|
735
|
+
console.log(`[IndexedDBTokenStorage] load: db=${this.dbName}, tokens=${tokenKeys.length}`);
|
|
458
736
|
return {
|
|
459
737
|
success: true,
|
|
460
738
|
data,
|
|
@@ -462,6 +740,7 @@ var IndexedDBTokenStorageProvider = class {
|
|
|
462
740
|
timestamp: Date.now()
|
|
463
741
|
};
|
|
464
742
|
} catch (error) {
|
|
743
|
+
console.error(`[IndexedDBTokenStorage] load failed: db=${this.dbName}`, error);
|
|
465
744
|
return {
|
|
466
745
|
success: false,
|
|
467
746
|
error: error instanceof Error ? error.message : "Unknown error",
|
|
@@ -472,6 +751,7 @@ var IndexedDBTokenStorageProvider = class {
|
|
|
472
751
|
}
|
|
473
752
|
async save(data) {
|
|
474
753
|
if (!this.db) {
|
|
754
|
+
console.warn(`[IndexedDBTokenStorage] save: db not initialized (db=${this.dbName})`);
|
|
475
755
|
return {
|
|
476
756
|
success: false,
|
|
477
757
|
error: "Database not initialized",
|
|
@@ -479,6 +759,9 @@ var IndexedDBTokenStorageProvider = class {
|
|
|
479
759
|
};
|
|
480
760
|
}
|
|
481
761
|
try {
|
|
762
|
+
const tokenKeys = Object.keys(data).filter((k) => k.startsWith("_") && !["_meta", "_tombstones", "_outbox", "_sent", "_invalid"].includes(k));
|
|
763
|
+
const archivedKeys = Object.keys(data).filter((k) => k.startsWith("archived-"));
|
|
764
|
+
console.log(`[IndexedDBTokenStorage] save: db=${this.dbName}, tokens=${tokenKeys.length}, archived=${archivedKeys.length}, tombstones=${data._tombstones?.length ?? 0}`);
|
|
482
765
|
await this.putToStore(STORE_META, "meta", data._meta);
|
|
483
766
|
if (data._tombstones) {
|
|
484
767
|
await this.putToStore(STORE_META, "tombstones", data._tombstones);
|
|
@@ -536,15 +819,14 @@ var IndexedDBTokenStorageProvider = class {
|
|
|
536
819
|
return meta !== null;
|
|
537
820
|
}
|
|
538
821
|
async clear() {
|
|
539
|
-
const dbNames = [this.dbName];
|
|
540
822
|
try {
|
|
823
|
+
console.log(`[IndexedDBTokenStorage] clear: starting, db=${this.dbName}, wasConnected=${!!this.db}`);
|
|
541
824
|
if (this.db) {
|
|
542
|
-
await this.clearStore(STORE_TOKENS);
|
|
543
|
-
await this.clearStore(STORE_META);
|
|
544
825
|
this.db.close();
|
|
545
826
|
this.db = null;
|
|
546
827
|
}
|
|
547
828
|
this.status = "disconnected";
|
|
829
|
+
const dbNames = [this.dbName];
|
|
548
830
|
if (typeof indexedDB.databases === "function") {
|
|
549
831
|
try {
|
|
550
832
|
const dbs = await Promise.race([
|
|
@@ -556,42 +838,34 @@ var IndexedDBTokenStorageProvider = class {
|
|
|
556
838
|
for (const dbInfo of dbs) {
|
|
557
839
|
if (dbInfo.name && dbInfo.name.startsWith(this.dbNamePrefix) && dbInfo.name !== this.dbName) {
|
|
558
840
|
dbNames.push(dbInfo.name);
|
|
559
|
-
try {
|
|
560
|
-
const db = await new Promise((resolve, reject) => {
|
|
561
|
-
const req = indexedDB.open(dbInfo.name, DB_VERSION);
|
|
562
|
-
req.onsuccess = () => resolve(req.result);
|
|
563
|
-
req.onerror = () => reject(req.error);
|
|
564
|
-
req.onupgradeneeded = (e) => {
|
|
565
|
-
const d = e.target.result;
|
|
566
|
-
if (!d.objectStoreNames.contains(STORE_TOKENS)) d.createObjectStore(STORE_TOKENS, { keyPath: "id" });
|
|
567
|
-
if (!d.objectStoreNames.contains(STORE_META)) d.createObjectStore(STORE_META);
|
|
568
|
-
};
|
|
569
|
-
});
|
|
570
|
-
const clearTx = db.transaction([STORE_TOKENS, STORE_META], "readwrite");
|
|
571
|
-
clearTx.objectStore(STORE_TOKENS).clear();
|
|
572
|
-
clearTx.objectStore(STORE_META).clear();
|
|
573
|
-
await new Promise((resolve) => {
|
|
574
|
-
clearTx.oncomplete = () => resolve();
|
|
575
|
-
clearTx.onerror = () => resolve();
|
|
576
|
-
});
|
|
577
|
-
db.close();
|
|
578
|
-
} catch {
|
|
579
|
-
}
|
|
580
841
|
}
|
|
581
842
|
}
|
|
582
843
|
} catch {
|
|
583
844
|
}
|
|
584
845
|
}
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
846
|
+
console.log(`[IndexedDBTokenStorage] clear: deleting ${dbNames.length} database(s):`, dbNames);
|
|
847
|
+
await Promise.all(dbNames.map(
|
|
848
|
+
(name) => new Promise((resolve) => {
|
|
849
|
+
try {
|
|
850
|
+
const req = indexedDB.deleteDatabase(name);
|
|
851
|
+
req.onsuccess = () => {
|
|
852
|
+
console.log(`[IndexedDBTokenStorage] clear: deleted db=${name}`);
|
|
853
|
+
resolve();
|
|
854
|
+
};
|
|
855
|
+
req.onerror = () => {
|
|
856
|
+
console.warn(`[IndexedDBTokenStorage] clear: error deleting db=${name}`, req.error);
|
|
857
|
+
resolve();
|
|
858
|
+
};
|
|
859
|
+
req.onblocked = () => {
|
|
860
|
+
console.warn(`[IndexedDBTokenStorage] clear: deleteDatabase blocked for db=${name}`);
|
|
861
|
+
resolve();
|
|
862
|
+
};
|
|
863
|
+
} catch {
|
|
864
|
+
resolve();
|
|
865
|
+
}
|
|
866
|
+
})
|
|
867
|
+
));
|
|
868
|
+
console.log(`[IndexedDBTokenStorage] clear: done`);
|
|
595
869
|
return true;
|
|
596
870
|
} catch (err) {
|
|
597
871
|
console.warn("[IndexedDBTokenStorage] clear() failed:", err);
|
|
@@ -603,7 +877,7 @@ var IndexedDBTokenStorageProvider = class {
|
|
|
603
877
|
// =========================================================================
|
|
604
878
|
openDatabase() {
|
|
605
879
|
return new Promise((resolve, reject) => {
|
|
606
|
-
const request = indexedDB.open(this.dbName,
|
|
880
|
+
const request = indexedDB.open(this.dbName, DB_VERSION2);
|
|
607
881
|
request.onerror = () => {
|
|
608
882
|
reject(request.error);
|
|
609
883
|
};
|
|
@@ -746,6 +1020,52 @@ function createView(arr) {
|
|
|
746
1020
|
function rotr(word, shift) {
|
|
747
1021
|
return word << 32 - shift | word >>> shift;
|
|
748
1022
|
}
|
|
1023
|
+
var hasHexBuiltin = /* @__PURE__ */ (() => (
|
|
1024
|
+
// @ts-ignore
|
|
1025
|
+
typeof Uint8Array.from([]).toHex === "function" && typeof Uint8Array.fromHex === "function"
|
|
1026
|
+
))();
|
|
1027
|
+
var hexes = /* @__PURE__ */ Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, "0"));
|
|
1028
|
+
function bytesToHex(bytes) {
|
|
1029
|
+
abytes(bytes);
|
|
1030
|
+
if (hasHexBuiltin)
|
|
1031
|
+
return bytes.toHex();
|
|
1032
|
+
let hex = "";
|
|
1033
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
1034
|
+
hex += hexes[bytes[i]];
|
|
1035
|
+
}
|
|
1036
|
+
return hex;
|
|
1037
|
+
}
|
|
1038
|
+
var asciis = { _0: 48, _9: 57, A: 65, F: 70, a: 97, f: 102 };
|
|
1039
|
+
function asciiToBase16(ch) {
|
|
1040
|
+
if (ch >= asciis._0 && ch <= asciis._9)
|
|
1041
|
+
return ch - asciis._0;
|
|
1042
|
+
if (ch >= asciis.A && ch <= asciis.F)
|
|
1043
|
+
return ch - (asciis.A - 10);
|
|
1044
|
+
if (ch >= asciis.a && ch <= asciis.f)
|
|
1045
|
+
return ch - (asciis.a - 10);
|
|
1046
|
+
return;
|
|
1047
|
+
}
|
|
1048
|
+
function hexToBytes(hex) {
|
|
1049
|
+
if (typeof hex !== "string")
|
|
1050
|
+
throw new Error("hex string expected, got " + typeof hex);
|
|
1051
|
+
if (hasHexBuiltin)
|
|
1052
|
+
return Uint8Array.fromHex(hex);
|
|
1053
|
+
const hl = hex.length;
|
|
1054
|
+
const al = hl / 2;
|
|
1055
|
+
if (hl % 2)
|
|
1056
|
+
throw new Error("hex string expected, got unpadded hex of length " + hl);
|
|
1057
|
+
const array = new Uint8Array(al);
|
|
1058
|
+
for (let ai = 0, hi = 0; ai < al; ai++, hi += 2) {
|
|
1059
|
+
const n1 = asciiToBase16(hex.charCodeAt(hi));
|
|
1060
|
+
const n2 = asciiToBase16(hex.charCodeAt(hi + 1));
|
|
1061
|
+
if (n1 === void 0 || n2 === void 0) {
|
|
1062
|
+
const char = hex[hi] + hex[hi + 1];
|
|
1063
|
+
throw new Error('hex string expected, got non-hex character "' + char + '" at index ' + hi);
|
|
1064
|
+
}
|
|
1065
|
+
array[ai] = n1 * 16 + n2;
|
|
1066
|
+
}
|
|
1067
|
+
return array;
|
|
1068
|
+
}
|
|
749
1069
|
function createHasher(hashCons, info = {}) {
|
|
750
1070
|
const hashC = (msg, opts) => hashCons(opts).update(msg).digest();
|
|
751
1071
|
const tmp = hashCons(void 0);
|
|
@@ -1135,6 +1455,7 @@ import {
|
|
|
1135
1455
|
NostrKeyManager,
|
|
1136
1456
|
NIP04,
|
|
1137
1457
|
NIP17,
|
|
1458
|
+
NIP44,
|
|
1138
1459
|
Event as NostrEventClass,
|
|
1139
1460
|
EventKinds,
|
|
1140
1461
|
hashNametag,
|
|
@@ -1252,7 +1573,7 @@ function publicKeyToAddress(publicKey, prefix = "alpha", witnessVersion = 0) {
|
|
|
1252
1573
|
const programBytes = hash160ToBytes(pubKeyHash);
|
|
1253
1574
|
return encodeBech32(prefix, witnessVersion, programBytes);
|
|
1254
1575
|
}
|
|
1255
|
-
function
|
|
1576
|
+
function hexToBytes2(hex) {
|
|
1256
1577
|
const matches = hex.match(/../g);
|
|
1257
1578
|
if (!matches) {
|
|
1258
1579
|
return new Uint8Array(0);
|
|
@@ -1279,6 +1600,8 @@ function defaultUUIDGenerator() {
|
|
|
1279
1600
|
}
|
|
1280
1601
|
|
|
1281
1602
|
// transport/NostrTransportProvider.ts
|
|
1603
|
+
var COMPOSING_INDICATOR_KIND = 25050;
|
|
1604
|
+
var TIMESTAMP_RANDOMIZATION = 2 * 24 * 60 * 60;
|
|
1282
1605
|
var EVENT_KINDS = NOSTR_EVENT_KINDS;
|
|
1283
1606
|
function hashAddressForTag(address) {
|
|
1284
1607
|
const bytes = new TextEncoder().encode("unicity:address:" + address);
|
|
@@ -1361,6 +1684,8 @@ var NostrTransportProvider = class {
|
|
|
1361
1684
|
paymentRequestResponseHandlers = /* @__PURE__ */ new Set();
|
|
1362
1685
|
readReceiptHandlers = /* @__PURE__ */ new Set();
|
|
1363
1686
|
typingIndicatorHandlers = /* @__PURE__ */ new Set();
|
|
1687
|
+
composingHandlers = /* @__PURE__ */ new Set();
|
|
1688
|
+
pendingMessages = [];
|
|
1364
1689
|
broadcastHandlers = /* @__PURE__ */ new Map();
|
|
1365
1690
|
eventCallbacks = /* @__PURE__ */ new Set();
|
|
1366
1691
|
constructor(config) {
|
|
@@ -1633,6 +1958,18 @@ var NostrTransportProvider = class {
|
|
|
1633
1958
|
}
|
|
1634
1959
|
onMessage(handler) {
|
|
1635
1960
|
this.messageHandlers.add(handler);
|
|
1961
|
+
if (this.pendingMessages.length > 0) {
|
|
1962
|
+
const pending = this.pendingMessages;
|
|
1963
|
+
this.pendingMessages = [];
|
|
1964
|
+
this.log("Flushing", pending.length, "buffered messages to new handler");
|
|
1965
|
+
for (const message of pending) {
|
|
1966
|
+
try {
|
|
1967
|
+
handler(message);
|
|
1968
|
+
} catch (error) {
|
|
1969
|
+
this.log("Message handler error (buffered):", error);
|
|
1970
|
+
}
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1636
1973
|
return () => this.messageHandlers.delete(handler);
|
|
1637
1974
|
}
|
|
1638
1975
|
async sendTokenTransfer(recipientPubkey, payload) {
|
|
@@ -1753,6 +2090,19 @@ var NostrTransportProvider = class {
|
|
|
1753
2090
|
this.typingIndicatorHandlers.add(handler);
|
|
1754
2091
|
return () => this.typingIndicatorHandlers.delete(handler);
|
|
1755
2092
|
}
|
|
2093
|
+
// ===========================================================================
|
|
2094
|
+
// Composing Indicators (NIP-59 kind 25050)
|
|
2095
|
+
// ===========================================================================
|
|
2096
|
+
onComposing(handler) {
|
|
2097
|
+
this.composingHandlers.add(handler);
|
|
2098
|
+
return () => this.composingHandlers.delete(handler);
|
|
2099
|
+
}
|
|
2100
|
+
async sendComposingIndicator(recipientPubkey, content) {
|
|
2101
|
+
this.ensureReady();
|
|
2102
|
+
const nostrRecipient = recipientPubkey.length === 66 && (recipientPubkey.startsWith("02") || recipientPubkey.startsWith("03")) ? recipientPubkey.slice(2) : recipientPubkey;
|
|
2103
|
+
const giftWrap = this.createCustomKindGiftWrap(nostrRecipient, content, COMPOSING_INDICATOR_KIND);
|
|
2104
|
+
await this.publishEvent(giftWrap);
|
|
2105
|
+
}
|
|
1756
2106
|
/**
|
|
1757
2107
|
* Resolve any identifier to full peer information.
|
|
1758
2108
|
* Routes to the appropriate specific resolve method based on identifier format.
|
|
@@ -2252,6 +2602,30 @@ var NostrTransportProvider = class {
|
|
|
2252
2602
|
}
|
|
2253
2603
|
return;
|
|
2254
2604
|
}
|
|
2605
|
+
if (pm.kind === COMPOSING_INDICATOR_KIND) {
|
|
2606
|
+
let senderNametag2;
|
|
2607
|
+
let expiresIn = 3e4;
|
|
2608
|
+
try {
|
|
2609
|
+
const parsed = JSON.parse(pm.content);
|
|
2610
|
+
senderNametag2 = parsed.senderNametag || void 0;
|
|
2611
|
+
expiresIn = parsed.expiresIn ?? 3e4;
|
|
2612
|
+
} catch {
|
|
2613
|
+
}
|
|
2614
|
+
const indicator = {
|
|
2615
|
+
senderPubkey: pm.senderPubkey,
|
|
2616
|
+
senderNametag: senderNametag2,
|
|
2617
|
+
expiresIn
|
|
2618
|
+
};
|
|
2619
|
+
this.log("Composing indicator from:", indicator.senderNametag || pm.senderPubkey?.slice(0, 16));
|
|
2620
|
+
for (const handler of this.composingHandlers) {
|
|
2621
|
+
try {
|
|
2622
|
+
handler(indicator);
|
|
2623
|
+
} catch (e) {
|
|
2624
|
+
this.log("Composing handler error:", e);
|
|
2625
|
+
}
|
|
2626
|
+
}
|
|
2627
|
+
return;
|
|
2628
|
+
}
|
|
2255
2629
|
try {
|
|
2256
2630
|
const parsed = JSON.parse(pm.content);
|
|
2257
2631
|
if (parsed?.type === "typing") {
|
|
@@ -2298,12 +2672,17 @@ var NostrTransportProvider = class {
|
|
|
2298
2672
|
encrypted: true
|
|
2299
2673
|
};
|
|
2300
2674
|
this.emitEvent({ type: "message:received", timestamp: Date.now() });
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2675
|
+
if (this.messageHandlers.size === 0) {
|
|
2676
|
+
this.log("No message handlers registered, buffering message for later delivery");
|
|
2677
|
+
this.pendingMessages.push(message);
|
|
2678
|
+
} else {
|
|
2679
|
+
this.log("Dispatching to", this.messageHandlers.size, "handlers");
|
|
2680
|
+
for (const handler of this.messageHandlers) {
|
|
2681
|
+
try {
|
|
2682
|
+
handler(message);
|
|
2683
|
+
} catch (error) {
|
|
2684
|
+
this.log("Message handler error:", error);
|
|
2685
|
+
}
|
|
2307
2686
|
}
|
|
2308
2687
|
}
|
|
2309
2688
|
} catch (err) {
|
|
@@ -2707,6 +3086,39 @@ var NostrTransportProvider = class {
|
|
|
2707
3086
|
}
|
|
2708
3087
|
}
|
|
2709
3088
|
}
|
|
3089
|
+
/**
|
|
3090
|
+
* Create a NIP-17 gift wrap with a custom inner rumor kind.
|
|
3091
|
+
* Replicates the three-layer NIP-59 envelope (rumor → seal → gift wrap)
|
|
3092
|
+
* because NIP17.createGiftWrap hardcodes kind 14 for the inner rumor.
|
|
3093
|
+
*/
|
|
3094
|
+
createCustomKindGiftWrap(recipientPubkeyHex, content, rumorKind) {
|
|
3095
|
+
const senderPubkey = this.keyManager.getPublicKeyHex();
|
|
3096
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
3097
|
+
const rumorTags = [["p", recipientPubkeyHex]];
|
|
3098
|
+
const rumorSerialized = JSON.stringify([0, senderPubkey, now, rumorKind, rumorTags, content]);
|
|
3099
|
+
const rumorId = bytesToHex(sha256(new TextEncoder().encode(rumorSerialized)));
|
|
3100
|
+
const rumor = { id: rumorId, pubkey: senderPubkey, created_at: now, kind: rumorKind, tags: rumorTags, content };
|
|
3101
|
+
const recipientPubkeyBytes = hexToBytes(recipientPubkeyHex);
|
|
3102
|
+
const encryptedRumor = NIP44.encrypt(JSON.stringify(rumor), this.keyManager.getPrivateKey(), recipientPubkeyBytes);
|
|
3103
|
+
const sealTimestamp = now + Math.floor(Math.random() * 2 * TIMESTAMP_RANDOMIZATION) - TIMESTAMP_RANDOMIZATION;
|
|
3104
|
+
const seal = NostrEventClass.create(this.keyManager, {
|
|
3105
|
+
kind: EventKinds.SEAL,
|
|
3106
|
+
tags: [],
|
|
3107
|
+
content: encryptedRumor,
|
|
3108
|
+
created_at: sealTimestamp
|
|
3109
|
+
});
|
|
3110
|
+
const ephemeralKeys = NostrKeyManager.generate();
|
|
3111
|
+
const encryptedSeal = NIP44.encrypt(JSON.stringify(seal.toJSON()), ephemeralKeys.getPrivateKey(), recipientPubkeyBytes);
|
|
3112
|
+
const wrapTimestamp = now + Math.floor(Math.random() * 2 * TIMESTAMP_RANDOMIZATION) - TIMESTAMP_RANDOMIZATION;
|
|
3113
|
+
const giftWrap = NostrEventClass.create(ephemeralKeys, {
|
|
3114
|
+
kind: EventKinds.GIFT_WRAP,
|
|
3115
|
+
tags: [["p", recipientPubkeyHex]],
|
|
3116
|
+
content: encryptedSeal,
|
|
3117
|
+
created_at: wrapTimestamp
|
|
3118
|
+
});
|
|
3119
|
+
ephemeralKeys.clear();
|
|
3120
|
+
return giftWrap;
|
|
3121
|
+
}
|
|
2710
3122
|
log(...args) {
|
|
2711
3123
|
if (this.config.debug) {
|
|
2712
3124
|
console.log("[NostrTransportProvider]", ...args);
|
|
@@ -3336,7 +3748,7 @@ async function loadLibp2pModules() {
|
|
|
3336
3748
|
};
|
|
3337
3749
|
}
|
|
3338
3750
|
function deriveEd25519KeyMaterial(privateKeyHex, info = IPNS_HKDF_INFO) {
|
|
3339
|
-
const walletSecret =
|
|
3751
|
+
const walletSecret = hexToBytes2(privateKeyHex);
|
|
3340
3752
|
const infoBytes = new TextEncoder().encode(info);
|
|
3341
3753
|
return hkdf(sha256, walletSecret, void 0, infoBytes, 32);
|
|
3342
3754
|
}
|
|
@@ -5834,6 +6246,11 @@ function resolveGroupChatConfig(network, config) {
|
|
|
5834
6246
|
relays: config.relays ?? [...netConfig.groupRelays]
|
|
5835
6247
|
};
|
|
5836
6248
|
}
|
|
6249
|
+
function resolveMarketConfig(config) {
|
|
6250
|
+
if (!config) return void 0;
|
|
6251
|
+
if (config === true) return {};
|
|
6252
|
+
return { apiUrl: config.apiUrl, timeout: config.timeout };
|
|
6253
|
+
}
|
|
5837
6254
|
|
|
5838
6255
|
// impl/browser/index.ts
|
|
5839
6256
|
if (typeof globalThis.Buffer === "undefined") {
|
|
@@ -5891,7 +6308,7 @@ function createBrowserProviders(config) {
|
|
|
5891
6308
|
const oracleConfig = resolveOracleConfig(network, config?.oracle);
|
|
5892
6309
|
const l1Config = resolveL1Config(network, config?.l1);
|
|
5893
6310
|
const tokenSyncConfig = resolveTokenSyncConfig(network, config?.tokenSync);
|
|
5894
|
-
const storage =
|
|
6311
|
+
const storage = createIndexedDBStorageProvider(config?.storage);
|
|
5895
6312
|
const priceConfig = resolvePriceConfig(config?.price, storage);
|
|
5896
6313
|
const ipfsConfig = tokenSyncConfig?.ipfs;
|
|
5897
6314
|
const ipfsTokenStorage = ipfsConfig?.enabled ? createBrowserIpfsStorageProvider({
|
|
@@ -5900,11 +6317,13 @@ function createBrowserProviders(config) {
|
|
|
5900
6317
|
// reuse debug-like flag
|
|
5901
6318
|
}) : void 0;
|
|
5902
6319
|
const groupChat = resolveGroupChatConfig(network, config?.groupChat);
|
|
6320
|
+
const market = resolveMarketConfig(config?.market);
|
|
5903
6321
|
const networkConfig = getNetworkConfig(network);
|
|
5904
6322
|
TokenRegistry.configure({ remoteUrl: networkConfig.tokenRegistryUrl, storage });
|
|
5905
6323
|
return {
|
|
5906
6324
|
storage,
|
|
5907
6325
|
groupChat,
|
|
6326
|
+
market,
|
|
5908
6327
|
transport: createNostrTransportProvider({
|
|
5909
6328
|
relays: transportConfig.relays,
|
|
5910
6329
|
timeout: transportConfig.timeout,
|
|
@@ -5931,6 +6350,7 @@ function createBrowserProviders(config) {
|
|
|
5931
6350
|
}
|
|
5932
6351
|
export {
|
|
5933
6352
|
BrowserTrustBaseLoader,
|
|
6353
|
+
IndexedDBStorageProvider,
|
|
5934
6354
|
IndexedDBTokenStorageProvider,
|
|
5935
6355
|
LocalStorageProvider,
|
|
5936
6356
|
NostrTransportProvider,
|
|
@@ -5940,6 +6360,7 @@ export {
|
|
|
5940
6360
|
createBrowserProviders,
|
|
5941
6361
|
createBrowserTrustBaseLoader,
|
|
5942
6362
|
createBrowserWebSocket,
|
|
6363
|
+
createIndexedDBStorageProvider,
|
|
5943
6364
|
createIndexedDBTokenStorageProvider,
|
|
5944
6365
|
createLocalStorageProvider,
|
|
5945
6366
|
createNostrTransportProvider,
|