@bsv/wallet-toolbox 1.3.31 → 1.3.32
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/CHANGELOG.md +4 -0
- package/mobile/out/src/WalletPermissionsManager.d.ts +11 -0
- package/mobile/out/src/WalletPermissionsManager.d.ts.map +1 -1
- package/mobile/out/src/WalletPermissionsManager.js +102 -6
- package/mobile/out/src/WalletPermissionsManager.js.map +1 -1
- package/mobile/package-lock.json +2 -2
- package/mobile/package.json +1 -1
- package/out/src/WalletPermissionsManager.d.ts +11 -0
- package/out/src/WalletPermissionsManager.d.ts.map +1 -1
- package/out/src/WalletPermissionsManager.js +102 -6
- package/out/src/WalletPermissionsManager.js.map +1 -1
- package/out/src/__tests/WalletPermissionsManager.flows.test.js +1 -0
- package/out/src/__tests/WalletPermissionsManager.flows.test.js.map +1 -1
- package/out/test/Wallet/support/operations.man.test.js +6 -2
- package/out/test/Wallet/support/operations.man.test.js.map +1 -1
- package/out/tsconfig.all.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/WalletPermissionsManager.ts +108 -6
- package/src/__tests/WalletPermissionsManager.flows.test.ts +3 -0
- package/test/Wallet/support/operations.man.test.ts +6 -2
package/package.json
CHANGED
|
@@ -324,6 +324,12 @@ export class WalletPermissionsManager implements WalletInterface {
|
|
|
324
324
|
}
|
|
325
325
|
> = new Map()
|
|
326
326
|
|
|
327
|
+
/** Cache recently confirmed permissions to avoid repeated lookups. */
|
|
328
|
+
private permissionCache: Map<string, { expiry: number; cachedAt: number }> = new Map()
|
|
329
|
+
|
|
330
|
+
/** How long a cached permission remains valid (5 minutes). */
|
|
331
|
+
private static readonly CACHE_TTL_MS = 5 * 60 * 1000
|
|
332
|
+
|
|
327
333
|
/**
|
|
328
334
|
* Configuration that determines whether to skip or apply various checks and encryption.
|
|
329
335
|
*/
|
|
@@ -480,6 +486,11 @@ export class WalletPermissionsManager implements WalletInterface {
|
|
|
480
486
|
)
|
|
481
487
|
}
|
|
482
488
|
}
|
|
489
|
+
|
|
490
|
+
// Update the cache regardless of ephemeral status
|
|
491
|
+
const expiry = params.expiry || Math.floor(Date.now() / 1000) + 3600 * 24 * 30
|
|
492
|
+
const key = this.buildRequestKey(matching.request)
|
|
493
|
+
this.cachePermission(key, expiry)
|
|
483
494
|
}
|
|
484
495
|
|
|
485
496
|
/**
|
|
@@ -562,6 +573,17 @@ export class WalletPermissionsManager implements WalletInterface {
|
|
|
562
573
|
privileged = false
|
|
563
574
|
}
|
|
564
575
|
|
|
576
|
+
const cacheKey = this.buildRequestKey({
|
|
577
|
+
type: 'protocol',
|
|
578
|
+
originator,
|
|
579
|
+
privileged,
|
|
580
|
+
protocolID,
|
|
581
|
+
counterparty
|
|
582
|
+
})
|
|
583
|
+
if (this.isPermissionCached(cacheKey)) {
|
|
584
|
+
return true
|
|
585
|
+
}
|
|
586
|
+
|
|
565
587
|
// 4) Attempt to find a valid token in the internal basket
|
|
566
588
|
const token = await this.findProtocolToken(
|
|
567
589
|
originator,
|
|
@@ -573,6 +595,7 @@ export class WalletPermissionsManager implements WalletInterface {
|
|
|
573
595
|
if (token) {
|
|
574
596
|
if (!this.isTokenExpired(token.expiry)) {
|
|
575
597
|
// valid and unexpired
|
|
598
|
+
this.cachePermission(cacheKey, token.expiry)
|
|
576
599
|
return true
|
|
577
600
|
} else {
|
|
578
601
|
// has a token but expired => request renewal if allowed
|
|
@@ -595,7 +618,7 @@ export class WalletPermissionsManager implements WalletInterface {
|
|
|
595
618
|
if (!seekPermission) {
|
|
596
619
|
throw new Error(`No protocol permission token found (seekPermission=false).`)
|
|
597
620
|
}
|
|
598
|
-
|
|
621
|
+
const granted = await this.requestPermissionFlow({
|
|
599
622
|
type: 'protocol',
|
|
600
623
|
originator,
|
|
601
624
|
privileged,
|
|
@@ -604,6 +627,11 @@ export class WalletPermissionsManager implements WalletInterface {
|
|
|
604
627
|
reason,
|
|
605
628
|
renewal: false
|
|
606
629
|
})
|
|
630
|
+
if (granted) {
|
|
631
|
+
// Unknown expiry until grantPermission() is called; cache for now with TTL only
|
|
632
|
+
this.cachePermission(cacheKey, 0)
|
|
633
|
+
}
|
|
634
|
+
return granted
|
|
607
635
|
}
|
|
608
636
|
}
|
|
609
637
|
|
|
@@ -631,9 +659,14 @@ export class WalletPermissionsManager implements WalletInterface {
|
|
|
631
659
|
if (usageType === 'insertion' && !this.config.seekBasketInsertionPermissions) return true
|
|
632
660
|
if (usageType === 'removal' && !this.config.seekBasketRemovalPermissions) return true
|
|
633
661
|
if (usageType === 'listing' && !this.config.seekBasketListingPermissions) return true
|
|
662
|
+
const cacheKey = this.buildRequestKey({ type: 'basket', originator, basket })
|
|
663
|
+
if (this.isPermissionCached(cacheKey)) {
|
|
664
|
+
return true
|
|
665
|
+
}
|
|
634
666
|
const token = await this.findBasketToken(originator, basket, true)
|
|
635
667
|
if (token) {
|
|
636
668
|
if (!this.isTokenExpired(token.expiry)) {
|
|
669
|
+
this.cachePermission(cacheKey, token.expiry)
|
|
637
670
|
return true
|
|
638
671
|
} else {
|
|
639
672
|
if (!seekPermission) {
|
|
@@ -653,13 +686,17 @@ export class WalletPermissionsManager implements WalletInterface {
|
|
|
653
686
|
if (!seekPermission) {
|
|
654
687
|
throw new Error(`No basket permission found, and no user consent allowed (seekPermission=false).`)
|
|
655
688
|
}
|
|
656
|
-
|
|
689
|
+
const granted = await this.requestPermissionFlow({
|
|
657
690
|
type: 'basket',
|
|
658
691
|
originator,
|
|
659
692
|
basket,
|
|
660
693
|
reason,
|
|
661
694
|
renewal: false
|
|
662
695
|
})
|
|
696
|
+
if (granted) {
|
|
697
|
+
this.cachePermission(cacheKey, 0)
|
|
698
|
+
}
|
|
699
|
+
return granted
|
|
663
700
|
}
|
|
664
701
|
}
|
|
665
702
|
|
|
@@ -693,6 +730,15 @@ export class WalletPermissionsManager implements WalletInterface {
|
|
|
693
730
|
if (!this.config.differentiatePrivilegedOperations) {
|
|
694
731
|
privileged = false
|
|
695
732
|
}
|
|
733
|
+
const cacheKey = this.buildRequestKey({
|
|
734
|
+
type: 'certificate',
|
|
735
|
+
originator,
|
|
736
|
+
privileged,
|
|
737
|
+
certificate: { verifier, certType, fields }
|
|
738
|
+
})
|
|
739
|
+
if (this.isPermissionCached(cacheKey)) {
|
|
740
|
+
return true
|
|
741
|
+
}
|
|
696
742
|
const token = await this.findCertificateToken(
|
|
697
743
|
originator,
|
|
698
744
|
privileged,
|
|
@@ -703,6 +749,7 @@ export class WalletPermissionsManager implements WalletInterface {
|
|
|
703
749
|
)
|
|
704
750
|
if (token) {
|
|
705
751
|
if (!this.isTokenExpired(token.expiry)) {
|
|
752
|
+
this.cachePermission(cacheKey, token.expiry)
|
|
706
753
|
return true
|
|
707
754
|
} else {
|
|
708
755
|
if (!seekPermission) {
|
|
@@ -722,7 +769,7 @@ export class WalletPermissionsManager implements WalletInterface {
|
|
|
722
769
|
if (!seekPermission) {
|
|
723
770
|
throw new Error(`No certificate permission found (seekPermission=false).`)
|
|
724
771
|
}
|
|
725
|
-
|
|
772
|
+
const granted = await this.requestPermissionFlow({
|
|
726
773
|
type: 'certificate',
|
|
727
774
|
originator,
|
|
728
775
|
privileged,
|
|
@@ -730,6 +777,10 @@ export class WalletPermissionsManager implements WalletInterface {
|
|
|
730
777
|
reason,
|
|
731
778
|
renewal: false
|
|
732
779
|
})
|
|
780
|
+
if (granted) {
|
|
781
|
+
this.cachePermission(cacheKey, 0)
|
|
782
|
+
}
|
|
783
|
+
return granted
|
|
733
784
|
}
|
|
734
785
|
}
|
|
735
786
|
|
|
@@ -759,11 +810,16 @@ export class WalletPermissionsManager implements WalletInterface {
|
|
|
759
810
|
// We skip spending permission entirely
|
|
760
811
|
return true
|
|
761
812
|
}
|
|
813
|
+
const cacheKey = this.buildRequestKey({ type: 'spending', originator })
|
|
814
|
+
if (this.isPermissionCached(cacheKey)) {
|
|
815
|
+
return true
|
|
816
|
+
}
|
|
762
817
|
const token = await this.findSpendingToken(originator)
|
|
763
818
|
if (token?.authorizedAmount) {
|
|
764
819
|
// Check how much has been spent so far
|
|
765
820
|
const spentSoFar = await this.querySpentSince(token)
|
|
766
821
|
if (spentSoFar + satoshis <= token.authorizedAmount) {
|
|
822
|
+
this.cachePermission(cacheKey, token.expiry)
|
|
767
823
|
return true
|
|
768
824
|
} else {
|
|
769
825
|
// Renew if possible
|
|
@@ -772,7 +828,7 @@ export class WalletPermissionsManager implements WalletInterface {
|
|
|
772
828
|
`Spending authorization insufficient for ${satoshis}, no user consent (seekPermission=false).`
|
|
773
829
|
)
|
|
774
830
|
}
|
|
775
|
-
|
|
831
|
+
const granted = await this.requestPermissionFlow({
|
|
776
832
|
type: 'spending',
|
|
777
833
|
originator,
|
|
778
834
|
spending: { satoshis, lineItems },
|
|
@@ -780,19 +836,27 @@ export class WalletPermissionsManager implements WalletInterface {
|
|
|
780
836
|
renewal: true,
|
|
781
837
|
previousToken: token
|
|
782
838
|
})
|
|
839
|
+
if (granted) {
|
|
840
|
+
this.cachePermission(cacheKey, 0)
|
|
841
|
+
}
|
|
842
|
+
return granted
|
|
783
843
|
}
|
|
784
844
|
} else {
|
|
785
845
|
// no token
|
|
786
846
|
if (!seekPermission) {
|
|
787
847
|
throw new Error(`No spending authorization found, (seekPermission=false).`)
|
|
788
848
|
}
|
|
789
|
-
|
|
849
|
+
const granted = await this.requestPermissionFlow({
|
|
790
850
|
type: 'spending',
|
|
791
851
|
originator,
|
|
792
852
|
spending: { satoshis, lineItems },
|
|
793
853
|
reason,
|
|
794
854
|
renewal: false
|
|
795
855
|
})
|
|
856
|
+
if (granted) {
|
|
857
|
+
this.cachePermission(cacheKey, 0)
|
|
858
|
+
}
|
|
859
|
+
return granted
|
|
796
860
|
}
|
|
797
861
|
}
|
|
798
862
|
|
|
@@ -828,8 +892,19 @@ export class WalletPermissionsManager implements WalletInterface {
|
|
|
828
892
|
return true
|
|
829
893
|
}
|
|
830
894
|
|
|
895
|
+
const cacheKey = this.buildRequestKey({
|
|
896
|
+
type: 'protocol',
|
|
897
|
+
originator,
|
|
898
|
+
privileged: false,
|
|
899
|
+
protocolID: [1, `action label ${label}`],
|
|
900
|
+
counterparty: 'self'
|
|
901
|
+
})
|
|
902
|
+
if (this.isPermissionCached(cacheKey)) {
|
|
903
|
+
return true
|
|
904
|
+
}
|
|
905
|
+
|
|
831
906
|
// 3) Let ensureProtocolPermission handle the rest.
|
|
832
|
-
|
|
907
|
+
const granted = await this.ensureProtocolPermission({
|
|
833
908
|
originator,
|
|
834
909
|
privileged: false,
|
|
835
910
|
protocolID: [1, `action label ${label}`],
|
|
@@ -838,6 +913,10 @@ export class WalletPermissionsManager implements WalletInterface {
|
|
|
838
913
|
seekPermission,
|
|
839
914
|
usageType: 'generic'
|
|
840
915
|
})
|
|
916
|
+
if (granted) {
|
|
917
|
+
this.cachePermission(cacheKey, 0)
|
|
918
|
+
}
|
|
919
|
+
return granted
|
|
841
920
|
}
|
|
842
921
|
|
|
843
922
|
/**
|
|
@@ -2525,6 +2604,29 @@ export class WalletPermissionsManager implements WalletInterface {
|
|
|
2525
2604
|
return false
|
|
2526
2605
|
}
|
|
2527
2606
|
|
|
2607
|
+
/**
|
|
2608
|
+
* Returns true if we have a cached record that the permission identified by
|
|
2609
|
+
* `key` is valid and unexpired.
|
|
2610
|
+
*/
|
|
2611
|
+
private isPermissionCached(key: string): boolean {
|
|
2612
|
+
const entry = this.permissionCache.get(key)
|
|
2613
|
+
if (!entry) return false
|
|
2614
|
+
if (Date.now() - entry.cachedAt > WalletPermissionsManager.CACHE_TTL_MS) {
|
|
2615
|
+
this.permissionCache.delete(key)
|
|
2616
|
+
return false
|
|
2617
|
+
}
|
|
2618
|
+
if (this.isTokenExpired(entry.expiry)) {
|
|
2619
|
+
this.permissionCache.delete(key)
|
|
2620
|
+
return false
|
|
2621
|
+
}
|
|
2622
|
+
return true
|
|
2623
|
+
}
|
|
2624
|
+
|
|
2625
|
+
/** Caches the fact that the permission for `key` is valid until `expiry`. */
|
|
2626
|
+
private cachePermission(key: string, expiry: number): void {
|
|
2627
|
+
this.permissionCache.set(key, { expiry, cachedAt: Date.now() })
|
|
2628
|
+
}
|
|
2629
|
+
|
|
2528
2630
|
/**
|
|
2529
2631
|
* Builds a "map key" string so that identical requests (e.g. "protocol:domain:true:protoName:counterparty")
|
|
2530
2632
|
* do not produce multiple user prompts.
|
|
@@ -295,6 +295,9 @@ describe('WalletPermissionsManager - Permission Request Flow & Active Requests',
|
|
|
295
295
|
// Because ephemeral=true, we do NOT create an on-chain token
|
|
296
296
|
expect(createTokenSpy).not.toHaveBeenCalled()
|
|
297
297
|
|
|
298
|
+
// Clear cache to actually run again
|
|
299
|
+
;(manager as any).permissionCache = new Map()
|
|
300
|
+
|
|
298
301
|
// 2) Immediately call ensureProtocolPermission again for the same resource
|
|
299
302
|
// Because ephemeral usage didn't store a token, it should re-prompt.
|
|
300
303
|
const pCall2 = manager.ensureProtocolPermission({
|
|
@@ -52,6 +52,7 @@ describe('operations.man tests', () => {
|
|
|
52
52
|
let offset = 0
|
|
53
53
|
const limit = 100
|
|
54
54
|
let allUnfails: number[] = []
|
|
55
|
+
let reviewed = 0
|
|
55
56
|
for (;;) {
|
|
56
57
|
let log = ''
|
|
57
58
|
const unfails: number[] = []
|
|
@@ -66,8 +67,9 @@ describe('operations.man tests', () => {
|
|
|
66
67
|
log += `unfail ${req.provenTxReqId} ${req.txid}\n`
|
|
67
68
|
unfails.push(req.provenTxReqId)
|
|
68
69
|
}
|
|
70
|
+
reviewed++
|
|
69
71
|
}
|
|
70
|
-
console.log(`DoubleSpends OFFSET: ${offset} ${unfails.length} unfails\n${log}`)
|
|
72
|
+
console.log(`DoubleSpends OFFSET: ${offset} ${reviewed} ${unfails.length} unfails\n${log}`)
|
|
71
73
|
allUnfails = allUnfails.concat(unfails)
|
|
72
74
|
if (reqs.length < limit) break
|
|
73
75
|
offset += reqs.length
|
|
@@ -83,6 +85,7 @@ describe('operations.man tests', () => {
|
|
|
83
85
|
let offset = 0
|
|
84
86
|
const limit = 100
|
|
85
87
|
let allUnfails: number[] = []
|
|
88
|
+
let reviewed = 0
|
|
86
89
|
for (;;) {
|
|
87
90
|
let log = ''
|
|
88
91
|
const unfails: number[] = []
|
|
@@ -98,8 +101,9 @@ describe('operations.man tests', () => {
|
|
|
98
101
|
log += `unfail ${req.provenTxReqId} ${req.txid}\n`
|
|
99
102
|
unfails.push(req.provenTxReqId)
|
|
100
103
|
}
|
|
104
|
+
reviewed++
|
|
101
105
|
}
|
|
102
|
-
console.log(`Failed OFFSET: ${offset} ${unfails.length} unfails\n${log}`)
|
|
106
|
+
console.log(`Failed OFFSET: ${offset} ${reviewed} ${unfails.length} unfails\n${log}`)
|
|
103
107
|
allUnfails = allUnfails.concat(unfails)
|
|
104
108
|
if (reqs.length < limit) break
|
|
105
109
|
offset += reqs.length
|