@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
|
@@ -350,9 +350,264 @@ 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
|
+
try {
|
|
380
|
+
this.db = await Promise.race([
|
|
381
|
+
this.openDatabase(),
|
|
382
|
+
new Promise(
|
|
383
|
+
(_, reject) => setTimeout(() => reject(new Error("IndexedDB open timed out after 5s")), 5e3)
|
|
384
|
+
)
|
|
385
|
+
]);
|
|
386
|
+
this.status = "connected";
|
|
387
|
+
this.log("Connected to IndexedDB");
|
|
388
|
+
} catch (error) {
|
|
389
|
+
this.status = "error";
|
|
390
|
+
throw new Error(`IndexedDB not available: ${error}`);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
async disconnect() {
|
|
394
|
+
if (this.db) {
|
|
395
|
+
this.db.close();
|
|
396
|
+
this.db = null;
|
|
397
|
+
}
|
|
398
|
+
this.status = "disconnected";
|
|
399
|
+
this.log("Disconnected from IndexedDB");
|
|
400
|
+
}
|
|
401
|
+
isConnected() {
|
|
402
|
+
return this.status === "connected" && this.db !== null;
|
|
403
|
+
}
|
|
404
|
+
getStatus() {
|
|
405
|
+
return this.status;
|
|
406
|
+
}
|
|
407
|
+
// ===========================================================================
|
|
408
|
+
// StorageProvider Implementation
|
|
409
|
+
// ===========================================================================
|
|
410
|
+
setIdentity(identity) {
|
|
411
|
+
this.identity = identity;
|
|
412
|
+
this.log("Identity set:", identity.l1Address);
|
|
413
|
+
}
|
|
414
|
+
async get(key) {
|
|
415
|
+
this.ensureConnected();
|
|
416
|
+
const fullKey = this.getFullKey(key);
|
|
417
|
+
const result = await this.idbGet(fullKey);
|
|
418
|
+
return result?.v ?? null;
|
|
419
|
+
}
|
|
420
|
+
async set(key, value) {
|
|
421
|
+
this.ensureConnected();
|
|
422
|
+
const fullKey = this.getFullKey(key);
|
|
423
|
+
await this.idbPut({ k: fullKey, v: value });
|
|
424
|
+
}
|
|
425
|
+
async remove(key) {
|
|
426
|
+
this.ensureConnected();
|
|
427
|
+
const fullKey = this.getFullKey(key);
|
|
428
|
+
await this.idbDelete(fullKey);
|
|
429
|
+
}
|
|
430
|
+
async has(key) {
|
|
431
|
+
this.ensureConnected();
|
|
432
|
+
const fullKey = this.getFullKey(key);
|
|
433
|
+
const count = await this.idbCount(fullKey);
|
|
434
|
+
return count > 0;
|
|
435
|
+
}
|
|
436
|
+
async keys(prefix) {
|
|
437
|
+
this.ensureConnected();
|
|
438
|
+
const basePrefix = this.getFullKey("");
|
|
439
|
+
const searchPrefix = prefix ? this.getFullKey(prefix) : basePrefix;
|
|
440
|
+
const allEntries = await this.idbGetAll();
|
|
441
|
+
const result = [];
|
|
442
|
+
for (const entry of allEntries) {
|
|
443
|
+
if (entry.k.startsWith(searchPrefix)) {
|
|
444
|
+
result.push(entry.k.slice(basePrefix.length));
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
return result;
|
|
448
|
+
}
|
|
449
|
+
async clear(prefix) {
|
|
450
|
+
if (!prefix) {
|
|
451
|
+
if (this.db) {
|
|
452
|
+
this.db.close();
|
|
453
|
+
this.db = null;
|
|
454
|
+
}
|
|
455
|
+
this.status = "disconnected";
|
|
456
|
+
await new Promise((resolve) => {
|
|
457
|
+
try {
|
|
458
|
+
const req = indexedDB.deleteDatabase(this.dbName);
|
|
459
|
+
req.onsuccess = () => resolve();
|
|
460
|
+
req.onerror = () => resolve();
|
|
461
|
+
req.onblocked = () => resolve();
|
|
462
|
+
} catch {
|
|
463
|
+
resolve();
|
|
464
|
+
}
|
|
465
|
+
});
|
|
466
|
+
this.log("Database deleted:", this.dbName);
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
this.ensureConnected();
|
|
470
|
+
const keysToRemove = await this.keys(prefix);
|
|
471
|
+
for (const key of keysToRemove) {
|
|
472
|
+
await this.remove(key);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
async saveTrackedAddresses(entries) {
|
|
476
|
+
await this.set(STORAGE_KEYS_GLOBAL.TRACKED_ADDRESSES, JSON.stringify({ version: 1, addresses: entries }));
|
|
477
|
+
}
|
|
478
|
+
async loadTrackedAddresses() {
|
|
479
|
+
const data = await this.get(STORAGE_KEYS_GLOBAL.TRACKED_ADDRESSES);
|
|
480
|
+
if (!data) return [];
|
|
481
|
+
try {
|
|
482
|
+
const parsed = JSON.parse(data);
|
|
483
|
+
return parsed.addresses ?? [];
|
|
484
|
+
} catch {
|
|
485
|
+
return [];
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
// ===========================================================================
|
|
489
|
+
// Helpers
|
|
490
|
+
// ===========================================================================
|
|
491
|
+
/**
|
|
492
|
+
* Get JSON data
|
|
493
|
+
*/
|
|
494
|
+
async getJSON(key) {
|
|
495
|
+
const value = await this.get(key);
|
|
496
|
+
if (!value) return null;
|
|
497
|
+
try {
|
|
498
|
+
return JSON.parse(value);
|
|
499
|
+
} catch {
|
|
500
|
+
return null;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* Set JSON data
|
|
505
|
+
*/
|
|
506
|
+
async setJSON(key, value) {
|
|
507
|
+
await this.set(key, JSON.stringify(value));
|
|
508
|
+
}
|
|
509
|
+
// ===========================================================================
|
|
510
|
+
// Private: Key Scoping
|
|
511
|
+
// ===========================================================================
|
|
512
|
+
getFullKey(key) {
|
|
513
|
+
const isPerAddressKey = Object.values(STORAGE_KEYS_ADDRESS).includes(key);
|
|
514
|
+
if (isPerAddressKey && this.identity?.directAddress) {
|
|
515
|
+
const addressId = getAddressId(this.identity.directAddress);
|
|
516
|
+
return `${this.prefix}${addressId}_${key}`;
|
|
517
|
+
}
|
|
518
|
+
return `${this.prefix}${key}`;
|
|
519
|
+
}
|
|
520
|
+
ensureConnected() {
|
|
521
|
+
if (this.status !== "connected" || !this.db) {
|
|
522
|
+
throw new Error("IndexedDBStorageProvider not connected");
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
// ===========================================================================
|
|
526
|
+
// Private: IndexedDB Operations
|
|
527
|
+
// ===========================================================================
|
|
528
|
+
openDatabase() {
|
|
529
|
+
return new Promise((resolve, reject) => {
|
|
530
|
+
const request = indexedDB.open(this.dbName, DB_VERSION);
|
|
531
|
+
request.onerror = () => reject(request.error);
|
|
532
|
+
request.onsuccess = () => resolve(request.result);
|
|
533
|
+
request.onblocked = () => {
|
|
534
|
+
console.warn("[IndexedDBStorageProvider] open blocked by another connection");
|
|
535
|
+
};
|
|
536
|
+
request.onupgradeneeded = (event) => {
|
|
537
|
+
const db = event.target.result;
|
|
538
|
+
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
|
539
|
+
db.createObjectStore(STORE_NAME, { keyPath: "k" });
|
|
540
|
+
}
|
|
541
|
+
};
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
idbGet(key) {
|
|
545
|
+
return new Promise((resolve, reject) => {
|
|
546
|
+
const tx = this.db.transaction(STORE_NAME, "readonly");
|
|
547
|
+
const store = tx.objectStore(STORE_NAME);
|
|
548
|
+
const request = store.get(key);
|
|
549
|
+
request.onerror = () => reject(request.error);
|
|
550
|
+
request.onsuccess = () => resolve(request.result ?? void 0);
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
idbPut(entry) {
|
|
554
|
+
return new Promise((resolve, reject) => {
|
|
555
|
+
const tx = this.db.transaction(STORE_NAME, "readwrite");
|
|
556
|
+
const store = tx.objectStore(STORE_NAME);
|
|
557
|
+
const request = store.put(entry);
|
|
558
|
+
request.onerror = () => reject(request.error);
|
|
559
|
+
request.onsuccess = () => resolve();
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
idbDelete(key) {
|
|
563
|
+
return new Promise((resolve, reject) => {
|
|
564
|
+
const tx = this.db.transaction(STORE_NAME, "readwrite");
|
|
565
|
+
const store = tx.objectStore(STORE_NAME);
|
|
566
|
+
const request = store.delete(key);
|
|
567
|
+
request.onerror = () => reject(request.error);
|
|
568
|
+
request.onsuccess = () => resolve();
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
idbCount(key) {
|
|
572
|
+
return new Promise((resolve, reject) => {
|
|
573
|
+
const tx = this.db.transaction(STORE_NAME, "readonly");
|
|
574
|
+
const store = tx.objectStore(STORE_NAME);
|
|
575
|
+
const request = store.count(key);
|
|
576
|
+
request.onerror = () => reject(request.error);
|
|
577
|
+
request.onsuccess = () => resolve(request.result);
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
idbGetAll() {
|
|
581
|
+
return new Promise((resolve, reject) => {
|
|
582
|
+
const tx = this.db.transaction(STORE_NAME, "readonly");
|
|
583
|
+
const store = tx.objectStore(STORE_NAME);
|
|
584
|
+
const request = store.getAll();
|
|
585
|
+
request.onerror = () => reject(request.error);
|
|
586
|
+
request.onsuccess = () => resolve(request.result ?? []);
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
idbClear() {
|
|
590
|
+
return new Promise((resolve, reject) => {
|
|
591
|
+
const tx = this.db.transaction(STORE_NAME, "readwrite");
|
|
592
|
+
const store = tx.objectStore(STORE_NAME);
|
|
593
|
+
const request = store.clear();
|
|
594
|
+
request.onerror = () => reject(request.error);
|
|
595
|
+
request.onsuccess = () => resolve();
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
log(...args) {
|
|
599
|
+
if (this.debug) {
|
|
600
|
+
console.log("[IndexedDBStorageProvider]", ...args);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
};
|
|
604
|
+
function createIndexedDBStorageProvider(config) {
|
|
605
|
+
return new IndexedDBStorageProvider(config);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// impl/browser/storage/IndexedDBTokenStorageProvider.ts
|
|
609
|
+
var DB_NAME2 = "sphere-token-storage";
|
|
610
|
+
var DB_VERSION2 = 1;
|
|
356
611
|
var STORE_TOKENS = "tokens";
|
|
357
612
|
var STORE_META = "meta";
|
|
358
613
|
var IndexedDBTokenStorageProvider = class {
|
|
@@ -365,7 +620,7 @@ var IndexedDBTokenStorageProvider = class {
|
|
|
365
620
|
status = "disconnected";
|
|
366
621
|
identity = null;
|
|
367
622
|
constructor(config) {
|
|
368
|
-
this.dbNamePrefix = config?.dbNamePrefix ??
|
|
623
|
+
this.dbNamePrefix = config?.dbNamePrefix ?? DB_NAME2;
|
|
369
624
|
this.dbName = this.dbNamePrefix;
|
|
370
625
|
}
|
|
371
626
|
setIdentity(identity) {
|
|
@@ -379,6 +634,7 @@ var IndexedDBTokenStorageProvider = class {
|
|
|
379
634
|
try {
|
|
380
635
|
this.db = await this.openDatabase();
|
|
381
636
|
this.status = "connected";
|
|
637
|
+
this.cleanupStaleDatabases();
|
|
382
638
|
return true;
|
|
383
639
|
} catch (error) {
|
|
384
640
|
console.error("[IndexedDBTokenStorage] Failed to initialize:", error);
|
|
@@ -536,15 +792,13 @@ var IndexedDBTokenStorageProvider = class {
|
|
|
536
792
|
return meta !== null;
|
|
537
793
|
}
|
|
538
794
|
async clear() {
|
|
539
|
-
const dbNames = [this.dbName];
|
|
540
795
|
try {
|
|
541
796
|
if (this.db) {
|
|
542
|
-
await this.clearStore(STORE_TOKENS);
|
|
543
|
-
await this.clearStore(STORE_META);
|
|
544
797
|
this.db.close();
|
|
545
798
|
this.db = null;
|
|
546
799
|
}
|
|
547
800
|
this.status = "disconnected";
|
|
801
|
+
const dbNames = [this.dbName];
|
|
548
802
|
if (typeof indexedDB.databases === "function") {
|
|
549
803
|
try {
|
|
550
804
|
const dbs = await Promise.race([
|
|
@@ -556,42 +810,25 @@ var IndexedDBTokenStorageProvider = class {
|
|
|
556
810
|
for (const dbInfo of dbs) {
|
|
557
811
|
if (dbInfo.name && dbInfo.name.startsWith(this.dbNamePrefix) && dbInfo.name !== this.dbName) {
|
|
558
812
|
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
813
|
}
|
|
581
814
|
}
|
|
582
815
|
} catch {
|
|
583
816
|
}
|
|
584
817
|
}
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
818
|
+
await Promise.all(dbNames.map(
|
|
819
|
+
(name) => new Promise((resolve) => {
|
|
820
|
+
try {
|
|
821
|
+
const req = indexedDB.deleteDatabase(name);
|
|
822
|
+
req.onsuccess = () => resolve();
|
|
823
|
+
req.onerror = () => resolve();
|
|
824
|
+
req.onblocked = () => {
|
|
825
|
+
resolve();
|
|
826
|
+
};
|
|
827
|
+
} catch {
|
|
828
|
+
resolve();
|
|
829
|
+
}
|
|
830
|
+
})
|
|
831
|
+
));
|
|
595
832
|
return true;
|
|
596
833
|
} catch (err) {
|
|
597
834
|
console.warn("[IndexedDBTokenStorage] clear() failed:", err);
|
|
@@ -601,9 +838,29 @@ var IndexedDBTokenStorageProvider = class {
|
|
|
601
838
|
// =========================================================================
|
|
602
839
|
// Private IndexedDB helpers
|
|
603
840
|
// =========================================================================
|
|
841
|
+
/**
|
|
842
|
+
* Delete stale databases from other addresses (fire-and-forget, background).
|
|
843
|
+
* Called after the current database is already open, so deleteDatabase
|
|
844
|
+
* on other databases won't block anything.
|
|
845
|
+
*/
|
|
846
|
+
cleanupStaleDatabases() {
|
|
847
|
+
if (typeof indexedDB.databases !== "function") return;
|
|
848
|
+
indexedDB.databases().then((dbs) => {
|
|
849
|
+
for (const dbInfo of dbs) {
|
|
850
|
+
if (dbInfo.name && dbInfo.name.startsWith(this.dbNamePrefix) && dbInfo.name !== this.dbName) {
|
|
851
|
+
const req = indexedDB.deleteDatabase(dbInfo.name);
|
|
852
|
+
req.onerror = () => {
|
|
853
|
+
};
|
|
854
|
+
req.onblocked = () => {
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
}).catch(() => {
|
|
859
|
+
});
|
|
860
|
+
}
|
|
604
861
|
openDatabase() {
|
|
605
862
|
return new Promise((resolve, reject) => {
|
|
606
|
-
const request = indexedDB.open(this.dbName,
|
|
863
|
+
const request = indexedDB.open(this.dbName, DB_VERSION2);
|
|
607
864
|
request.onerror = () => {
|
|
608
865
|
reject(request.error);
|
|
609
866
|
};
|
|
@@ -746,6 +1003,52 @@ function createView(arr) {
|
|
|
746
1003
|
function rotr(word, shift) {
|
|
747
1004
|
return word << 32 - shift | word >>> shift;
|
|
748
1005
|
}
|
|
1006
|
+
var hasHexBuiltin = /* @__PURE__ */ (() => (
|
|
1007
|
+
// @ts-ignore
|
|
1008
|
+
typeof Uint8Array.from([]).toHex === "function" && typeof Uint8Array.fromHex === "function"
|
|
1009
|
+
))();
|
|
1010
|
+
var hexes = /* @__PURE__ */ Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, "0"));
|
|
1011
|
+
function bytesToHex(bytes) {
|
|
1012
|
+
abytes(bytes);
|
|
1013
|
+
if (hasHexBuiltin)
|
|
1014
|
+
return bytes.toHex();
|
|
1015
|
+
let hex = "";
|
|
1016
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
1017
|
+
hex += hexes[bytes[i]];
|
|
1018
|
+
}
|
|
1019
|
+
return hex;
|
|
1020
|
+
}
|
|
1021
|
+
var asciis = { _0: 48, _9: 57, A: 65, F: 70, a: 97, f: 102 };
|
|
1022
|
+
function asciiToBase16(ch) {
|
|
1023
|
+
if (ch >= asciis._0 && ch <= asciis._9)
|
|
1024
|
+
return ch - asciis._0;
|
|
1025
|
+
if (ch >= asciis.A && ch <= asciis.F)
|
|
1026
|
+
return ch - (asciis.A - 10);
|
|
1027
|
+
if (ch >= asciis.a && ch <= asciis.f)
|
|
1028
|
+
return ch - (asciis.a - 10);
|
|
1029
|
+
return;
|
|
1030
|
+
}
|
|
1031
|
+
function hexToBytes(hex) {
|
|
1032
|
+
if (typeof hex !== "string")
|
|
1033
|
+
throw new Error("hex string expected, got " + typeof hex);
|
|
1034
|
+
if (hasHexBuiltin)
|
|
1035
|
+
return Uint8Array.fromHex(hex);
|
|
1036
|
+
const hl = hex.length;
|
|
1037
|
+
const al = hl / 2;
|
|
1038
|
+
if (hl % 2)
|
|
1039
|
+
throw new Error("hex string expected, got unpadded hex of length " + hl);
|
|
1040
|
+
const array = new Uint8Array(al);
|
|
1041
|
+
for (let ai = 0, hi = 0; ai < al; ai++, hi += 2) {
|
|
1042
|
+
const n1 = asciiToBase16(hex.charCodeAt(hi));
|
|
1043
|
+
const n2 = asciiToBase16(hex.charCodeAt(hi + 1));
|
|
1044
|
+
if (n1 === void 0 || n2 === void 0) {
|
|
1045
|
+
const char = hex[hi] + hex[hi + 1];
|
|
1046
|
+
throw new Error('hex string expected, got non-hex character "' + char + '" at index ' + hi);
|
|
1047
|
+
}
|
|
1048
|
+
array[ai] = n1 * 16 + n2;
|
|
1049
|
+
}
|
|
1050
|
+
return array;
|
|
1051
|
+
}
|
|
749
1052
|
function createHasher(hashCons, info = {}) {
|
|
750
1053
|
const hashC = (msg, opts) => hashCons(opts).update(msg).digest();
|
|
751
1054
|
const tmp = hashCons(void 0);
|
|
@@ -1135,6 +1438,7 @@ import {
|
|
|
1135
1438
|
NostrKeyManager,
|
|
1136
1439
|
NIP04,
|
|
1137
1440
|
NIP17,
|
|
1441
|
+
NIP44,
|
|
1138
1442
|
Event as NostrEventClass,
|
|
1139
1443
|
EventKinds,
|
|
1140
1444
|
hashNametag,
|
|
@@ -1252,7 +1556,7 @@ function publicKeyToAddress(publicKey, prefix = "alpha", witnessVersion = 0) {
|
|
|
1252
1556
|
const programBytes = hash160ToBytes(pubKeyHash);
|
|
1253
1557
|
return encodeBech32(prefix, witnessVersion, programBytes);
|
|
1254
1558
|
}
|
|
1255
|
-
function
|
|
1559
|
+
function hexToBytes2(hex) {
|
|
1256
1560
|
const matches = hex.match(/../g);
|
|
1257
1561
|
if (!matches) {
|
|
1258
1562
|
return new Uint8Array(0);
|
|
@@ -1279,6 +1583,8 @@ function defaultUUIDGenerator() {
|
|
|
1279
1583
|
}
|
|
1280
1584
|
|
|
1281
1585
|
// transport/NostrTransportProvider.ts
|
|
1586
|
+
var COMPOSING_INDICATOR_KIND = 25050;
|
|
1587
|
+
var TIMESTAMP_RANDOMIZATION = 2 * 24 * 60 * 60;
|
|
1282
1588
|
var EVENT_KINDS = NOSTR_EVENT_KINDS;
|
|
1283
1589
|
function hashAddressForTag(address) {
|
|
1284
1590
|
const bytes = new TextEncoder().encode("unicity:address:" + address);
|
|
@@ -1361,6 +1667,8 @@ var NostrTransportProvider = class {
|
|
|
1361
1667
|
paymentRequestResponseHandlers = /* @__PURE__ */ new Set();
|
|
1362
1668
|
readReceiptHandlers = /* @__PURE__ */ new Set();
|
|
1363
1669
|
typingIndicatorHandlers = /* @__PURE__ */ new Set();
|
|
1670
|
+
composingHandlers = /* @__PURE__ */ new Set();
|
|
1671
|
+
pendingMessages = [];
|
|
1364
1672
|
broadcastHandlers = /* @__PURE__ */ new Map();
|
|
1365
1673
|
eventCallbacks = /* @__PURE__ */ new Set();
|
|
1366
1674
|
constructor(config) {
|
|
@@ -1633,6 +1941,18 @@ var NostrTransportProvider = class {
|
|
|
1633
1941
|
}
|
|
1634
1942
|
onMessage(handler) {
|
|
1635
1943
|
this.messageHandlers.add(handler);
|
|
1944
|
+
if (this.pendingMessages.length > 0) {
|
|
1945
|
+
const pending = this.pendingMessages;
|
|
1946
|
+
this.pendingMessages = [];
|
|
1947
|
+
this.log("Flushing", pending.length, "buffered messages to new handler");
|
|
1948
|
+
for (const message of pending) {
|
|
1949
|
+
try {
|
|
1950
|
+
handler(message);
|
|
1951
|
+
} catch (error) {
|
|
1952
|
+
this.log("Message handler error (buffered):", error);
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1955
|
+
}
|
|
1636
1956
|
return () => this.messageHandlers.delete(handler);
|
|
1637
1957
|
}
|
|
1638
1958
|
async sendTokenTransfer(recipientPubkey, payload) {
|
|
@@ -1753,6 +2073,19 @@ var NostrTransportProvider = class {
|
|
|
1753
2073
|
this.typingIndicatorHandlers.add(handler);
|
|
1754
2074
|
return () => this.typingIndicatorHandlers.delete(handler);
|
|
1755
2075
|
}
|
|
2076
|
+
// ===========================================================================
|
|
2077
|
+
// Composing Indicators (NIP-59 kind 25050)
|
|
2078
|
+
// ===========================================================================
|
|
2079
|
+
onComposing(handler) {
|
|
2080
|
+
this.composingHandlers.add(handler);
|
|
2081
|
+
return () => this.composingHandlers.delete(handler);
|
|
2082
|
+
}
|
|
2083
|
+
async sendComposingIndicator(recipientPubkey, content) {
|
|
2084
|
+
this.ensureReady();
|
|
2085
|
+
const nostrRecipient = recipientPubkey.length === 66 && (recipientPubkey.startsWith("02") || recipientPubkey.startsWith("03")) ? recipientPubkey.slice(2) : recipientPubkey;
|
|
2086
|
+
const giftWrap = this.createCustomKindGiftWrap(nostrRecipient, content, COMPOSING_INDICATOR_KIND);
|
|
2087
|
+
await this.publishEvent(giftWrap);
|
|
2088
|
+
}
|
|
1756
2089
|
/**
|
|
1757
2090
|
* Resolve any identifier to full peer information.
|
|
1758
2091
|
* Routes to the appropriate specific resolve method based on identifier format.
|
|
@@ -2252,6 +2585,30 @@ var NostrTransportProvider = class {
|
|
|
2252
2585
|
}
|
|
2253
2586
|
return;
|
|
2254
2587
|
}
|
|
2588
|
+
if (pm.kind === COMPOSING_INDICATOR_KIND) {
|
|
2589
|
+
let senderNametag2;
|
|
2590
|
+
let expiresIn = 3e4;
|
|
2591
|
+
try {
|
|
2592
|
+
const parsed = JSON.parse(pm.content);
|
|
2593
|
+
senderNametag2 = parsed.senderNametag || void 0;
|
|
2594
|
+
expiresIn = parsed.expiresIn ?? 3e4;
|
|
2595
|
+
} catch {
|
|
2596
|
+
}
|
|
2597
|
+
const indicator = {
|
|
2598
|
+
senderPubkey: pm.senderPubkey,
|
|
2599
|
+
senderNametag: senderNametag2,
|
|
2600
|
+
expiresIn
|
|
2601
|
+
};
|
|
2602
|
+
this.log("Composing indicator from:", indicator.senderNametag || pm.senderPubkey?.slice(0, 16));
|
|
2603
|
+
for (const handler of this.composingHandlers) {
|
|
2604
|
+
try {
|
|
2605
|
+
handler(indicator);
|
|
2606
|
+
} catch (e) {
|
|
2607
|
+
this.log("Composing handler error:", e);
|
|
2608
|
+
}
|
|
2609
|
+
}
|
|
2610
|
+
return;
|
|
2611
|
+
}
|
|
2255
2612
|
try {
|
|
2256
2613
|
const parsed = JSON.parse(pm.content);
|
|
2257
2614
|
if (parsed?.type === "typing") {
|
|
@@ -2298,12 +2655,17 @@ var NostrTransportProvider = class {
|
|
|
2298
2655
|
encrypted: true
|
|
2299
2656
|
};
|
|
2300
2657
|
this.emitEvent({ type: "message:received", timestamp: Date.now() });
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2658
|
+
if (this.messageHandlers.size === 0) {
|
|
2659
|
+
this.log("No message handlers registered, buffering message for later delivery");
|
|
2660
|
+
this.pendingMessages.push(message);
|
|
2661
|
+
} else {
|
|
2662
|
+
this.log("Dispatching to", this.messageHandlers.size, "handlers");
|
|
2663
|
+
for (const handler of this.messageHandlers) {
|
|
2664
|
+
try {
|
|
2665
|
+
handler(message);
|
|
2666
|
+
} catch (error) {
|
|
2667
|
+
this.log("Message handler error:", error);
|
|
2668
|
+
}
|
|
2307
2669
|
}
|
|
2308
2670
|
}
|
|
2309
2671
|
} catch (err) {
|
|
@@ -2707,6 +3069,39 @@ var NostrTransportProvider = class {
|
|
|
2707
3069
|
}
|
|
2708
3070
|
}
|
|
2709
3071
|
}
|
|
3072
|
+
/**
|
|
3073
|
+
* Create a NIP-17 gift wrap with a custom inner rumor kind.
|
|
3074
|
+
* Replicates the three-layer NIP-59 envelope (rumor → seal → gift wrap)
|
|
3075
|
+
* because NIP17.createGiftWrap hardcodes kind 14 for the inner rumor.
|
|
3076
|
+
*/
|
|
3077
|
+
createCustomKindGiftWrap(recipientPubkeyHex, content, rumorKind) {
|
|
3078
|
+
const senderPubkey = this.keyManager.getPublicKeyHex();
|
|
3079
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
3080
|
+
const rumorTags = [["p", recipientPubkeyHex]];
|
|
3081
|
+
const rumorSerialized = JSON.stringify([0, senderPubkey, now, rumorKind, rumorTags, content]);
|
|
3082
|
+
const rumorId = bytesToHex(sha256(new TextEncoder().encode(rumorSerialized)));
|
|
3083
|
+
const rumor = { id: rumorId, pubkey: senderPubkey, created_at: now, kind: rumorKind, tags: rumorTags, content };
|
|
3084
|
+
const recipientPubkeyBytes = hexToBytes(recipientPubkeyHex);
|
|
3085
|
+
const encryptedRumor = NIP44.encrypt(JSON.stringify(rumor), this.keyManager.getPrivateKey(), recipientPubkeyBytes);
|
|
3086
|
+
const sealTimestamp = now + Math.floor(Math.random() * 2 * TIMESTAMP_RANDOMIZATION) - TIMESTAMP_RANDOMIZATION;
|
|
3087
|
+
const seal = NostrEventClass.create(this.keyManager, {
|
|
3088
|
+
kind: EventKinds.SEAL,
|
|
3089
|
+
tags: [],
|
|
3090
|
+
content: encryptedRumor,
|
|
3091
|
+
created_at: sealTimestamp
|
|
3092
|
+
});
|
|
3093
|
+
const ephemeralKeys = NostrKeyManager.generate();
|
|
3094
|
+
const encryptedSeal = NIP44.encrypt(JSON.stringify(seal.toJSON()), ephemeralKeys.getPrivateKey(), recipientPubkeyBytes);
|
|
3095
|
+
const wrapTimestamp = now + Math.floor(Math.random() * 2 * TIMESTAMP_RANDOMIZATION) - TIMESTAMP_RANDOMIZATION;
|
|
3096
|
+
const giftWrap = NostrEventClass.create(ephemeralKeys, {
|
|
3097
|
+
kind: EventKinds.GIFT_WRAP,
|
|
3098
|
+
tags: [["p", recipientPubkeyHex]],
|
|
3099
|
+
content: encryptedSeal,
|
|
3100
|
+
created_at: wrapTimestamp
|
|
3101
|
+
});
|
|
3102
|
+
ephemeralKeys.clear();
|
|
3103
|
+
return giftWrap;
|
|
3104
|
+
}
|
|
2710
3105
|
log(...args) {
|
|
2711
3106
|
if (this.config.debug) {
|
|
2712
3107
|
console.log("[NostrTransportProvider]", ...args);
|
|
@@ -3336,7 +3731,7 @@ async function loadLibp2pModules() {
|
|
|
3336
3731
|
};
|
|
3337
3732
|
}
|
|
3338
3733
|
function deriveEd25519KeyMaterial(privateKeyHex, info = IPNS_HKDF_INFO) {
|
|
3339
|
-
const walletSecret =
|
|
3734
|
+
const walletSecret = hexToBytes2(privateKeyHex);
|
|
3340
3735
|
const infoBytes = new TextEncoder().encode(info);
|
|
3341
3736
|
return hkdf(sha256, walletSecret, void 0, infoBytes, 32);
|
|
3342
3737
|
}
|
|
@@ -5834,6 +6229,11 @@ function resolveGroupChatConfig(network, config) {
|
|
|
5834
6229
|
relays: config.relays ?? [...netConfig.groupRelays]
|
|
5835
6230
|
};
|
|
5836
6231
|
}
|
|
6232
|
+
function resolveMarketConfig(config) {
|
|
6233
|
+
if (!config) return void 0;
|
|
6234
|
+
if (config === true) return {};
|
|
6235
|
+
return { apiUrl: config.apiUrl, timeout: config.timeout };
|
|
6236
|
+
}
|
|
5837
6237
|
|
|
5838
6238
|
// impl/browser/index.ts
|
|
5839
6239
|
if (typeof globalThis.Buffer === "undefined") {
|
|
@@ -5891,7 +6291,7 @@ function createBrowserProviders(config) {
|
|
|
5891
6291
|
const oracleConfig = resolveOracleConfig(network, config?.oracle);
|
|
5892
6292
|
const l1Config = resolveL1Config(network, config?.l1);
|
|
5893
6293
|
const tokenSyncConfig = resolveTokenSyncConfig(network, config?.tokenSync);
|
|
5894
|
-
const storage =
|
|
6294
|
+
const storage = createIndexedDBStorageProvider(config?.storage);
|
|
5895
6295
|
const priceConfig = resolvePriceConfig(config?.price, storage);
|
|
5896
6296
|
const ipfsConfig = tokenSyncConfig?.ipfs;
|
|
5897
6297
|
const ipfsTokenStorage = ipfsConfig?.enabled ? createBrowserIpfsStorageProvider({
|
|
@@ -5900,11 +6300,13 @@ function createBrowserProviders(config) {
|
|
|
5900
6300
|
// reuse debug-like flag
|
|
5901
6301
|
}) : void 0;
|
|
5902
6302
|
const groupChat = resolveGroupChatConfig(network, config?.groupChat);
|
|
6303
|
+
const market = resolveMarketConfig(config?.market);
|
|
5903
6304
|
const networkConfig = getNetworkConfig(network);
|
|
5904
6305
|
TokenRegistry.configure({ remoteUrl: networkConfig.tokenRegistryUrl, storage });
|
|
5905
6306
|
return {
|
|
5906
6307
|
storage,
|
|
5907
6308
|
groupChat,
|
|
6309
|
+
market,
|
|
5908
6310
|
transport: createNostrTransportProvider({
|
|
5909
6311
|
relays: transportConfig.relays,
|
|
5910
6312
|
timeout: transportConfig.timeout,
|
|
@@ -5931,6 +6333,7 @@ function createBrowserProviders(config) {
|
|
|
5931
6333
|
}
|
|
5932
6334
|
export {
|
|
5933
6335
|
BrowserTrustBaseLoader,
|
|
6336
|
+
IndexedDBStorageProvider,
|
|
5934
6337
|
IndexedDBTokenStorageProvider,
|
|
5935
6338
|
LocalStorageProvider,
|
|
5936
6339
|
NostrTransportProvider,
|
|
@@ -5940,6 +6343,7 @@ export {
|
|
|
5940
6343
|
createBrowserProviders,
|
|
5941
6344
|
createBrowserTrustBaseLoader,
|
|
5942
6345
|
createBrowserWebSocket,
|
|
6346
|
+
createIndexedDBStorageProvider,
|
|
5943
6347
|
createIndexedDBTokenStorageProvider,
|
|
5944
6348
|
createLocalStorageProvider,
|
|
5945
6349
|
createNostrTransportProvider,
|