@bsv/wallet-toolbox 1.7.17 → 1.7.19
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/docs/client.md +106 -55
- package/docs/wallet.md +106 -55
- package/out/src/WalletPermissionsManager.d.ts +42 -1
- package/out/src/WalletPermissionsManager.d.ts.map +1 -1
- package/out/src/WalletPermissionsManager.js +556 -72
- package/out/src/WalletPermissionsManager.js.map +1 -1
- package/out/src/__tests/WalletPermissionsManager.fixtures.d.ts.map +1 -1
- package/out/src/__tests/WalletPermissionsManager.fixtures.js +9 -0
- package/out/src/__tests/WalletPermissionsManager.fixtures.js.map +1 -1
- package/out/src/__tests/WalletPermissionsManager.flows.test.js +121 -0
- package/out/src/__tests/WalletPermissionsManager.flows.test.js.map +1 -1
- package/out/tsconfig.all.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/WalletPermissionsManager.ts +693 -80
- package/src/__tests/WalletPermissionsManager.fixtures.ts +9 -0
- package/src/__tests/WalletPermissionsManager.flows.test.ts +157 -0
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
const { Validation } = jest.requireActual('@bsv/sdk')
|
|
2
2
|
|
|
3
|
+
const existingFetch = (globalThis as any).fetch
|
|
4
|
+
if (!existingFetch || !(existingFetch as any)._isMockFunction) {
|
|
5
|
+
;(globalThis as any).fetch = jest.fn(async () => ({
|
|
6
|
+
ok: false,
|
|
7
|
+
status: 404,
|
|
8
|
+
json: async () => ({})
|
|
9
|
+
}))
|
|
10
|
+
}
|
|
11
|
+
|
|
3
12
|
/**
|
|
4
13
|
* A permissions manager testing mock/stub file for:
|
|
5
14
|
* 1) The `@bsv/sdk` library: Transaction, LockingScript, PushDrop, Utils, Random, etc.
|
|
@@ -35,6 +35,163 @@ describe('WalletPermissionsManager - Permission Request Flow & Active Requests',
|
|
|
35
35
|
* UNIT TESTS
|
|
36
36
|
*/
|
|
37
37
|
describe('Unit Tests: requestPermissionFlow & activeRequests map', () => {
|
|
38
|
+
it('should coalesce level-2 protocol requests for the same counterparty into a single grouped permission prompt based on manifest.json', async () => {
|
|
39
|
+
mockNoTokensFound(manager)
|
|
40
|
+
|
|
41
|
+
jest.spyOn(manager as any, 'fetchManifestGroupPermissions').mockResolvedValue({
|
|
42
|
+
protocolPermissions: [
|
|
43
|
+
{
|
|
44
|
+
protocolID: [2, 'l2-proto-A'],
|
|
45
|
+
counterparty: '',
|
|
46
|
+
description: 'A'
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
protocolID: [2, 'l2-proto-B'],
|
|
50
|
+
counterparty: 'peer-123',
|
|
51
|
+
description: 'B'
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
protocolID: [2, 'l2-proto-C'],
|
|
55
|
+
counterparty: 'peer-999',
|
|
56
|
+
description: 'C'
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
protocolID: [1, 'l1-proto-D'],
|
|
60
|
+
counterparty: 'peer-123',
|
|
61
|
+
description: 'D'
|
|
62
|
+
}
|
|
63
|
+
]
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
const groupRequestCallback = jest.fn(() => {})
|
|
67
|
+
manager.bindCallback('onGroupedPermissionRequested', groupRequestCallback)
|
|
68
|
+
|
|
69
|
+
const callA = manager.ensureProtocolPermission({
|
|
70
|
+
originator: 'example.com',
|
|
71
|
+
privileged: false,
|
|
72
|
+
protocolID: [2, 'l2-proto-A'],
|
|
73
|
+
counterparty: 'peer-123',
|
|
74
|
+
reason: 'UnitTest - L2 A',
|
|
75
|
+
seekPermission: true,
|
|
76
|
+
usageType: 'signing'
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
const callB = manager.ensureProtocolPermission({
|
|
80
|
+
originator: 'example.com',
|
|
81
|
+
privileged: false,
|
|
82
|
+
protocolID: [2, 'l2-proto-B'],
|
|
83
|
+
counterparty: 'peer-123',
|
|
84
|
+
reason: 'UnitTest - L2 B',
|
|
85
|
+
seekPermission: true,
|
|
86
|
+
usageType: 'signing'
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
await new Promise(res => setTimeout(res, 5))
|
|
90
|
+
|
|
91
|
+
expect(groupRequestCallback).toHaveBeenCalledTimes(1)
|
|
92
|
+
const callbackArg = (groupRequestCallback.mock as any).calls[0][0]
|
|
93
|
+
const requestID = callbackArg.requestID
|
|
94
|
+
expect(typeof requestID).toBe('string')
|
|
95
|
+
expect(requestID).toMatch(/^group-peer:/)
|
|
96
|
+
|
|
97
|
+
const activeRequests = (manager as any).activeRequests as Map<string, any>
|
|
98
|
+
const queued = activeRequests.get(requestID)
|
|
99
|
+
expect(queued.request.permissions.protocolPermissions.length).toBe(2)
|
|
100
|
+
expect(queued.request.permissions.protocolPermissions).toEqual(
|
|
101
|
+
expect.arrayContaining([
|
|
102
|
+
expect.objectContaining({ protocolID: [2, 'l2-proto-A'], counterparty: 'peer-123' }),
|
|
103
|
+
expect.objectContaining({ protocolID: [2, 'l2-proto-B'], counterparty: 'peer-123' })
|
|
104
|
+
])
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
await manager.denyGroupedPermission(requestID)
|
|
108
|
+
|
|
109
|
+
await expect(callA).rejects.toThrow(/denied/i)
|
|
110
|
+
await expect(callB).rejects.toThrow(/denied/i)
|
|
111
|
+
|
|
112
|
+
expect(activeRequests.size).toBe(0)
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
it('should create separate grouped permission requests for different peers (no cross-peer grouping)', async () => {
|
|
116
|
+
mockNoTokensFound(manager)
|
|
117
|
+
|
|
118
|
+
jest.spyOn(manager as any, 'fetchManifestGroupPermissions').mockResolvedValue({
|
|
119
|
+
protocolPermissions: [
|
|
120
|
+
{
|
|
121
|
+
protocolID: [2, 'l2-proto-B'],
|
|
122
|
+
counterparty: 'peer-123',
|
|
123
|
+
description: 'B'
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
protocolID: [2, 'l2-proto-C'],
|
|
127
|
+
counterparty: 'peer-999',
|
|
128
|
+
description: 'C'
|
|
129
|
+
}
|
|
130
|
+
]
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
const groupRequestCallback = jest.fn(() => {})
|
|
134
|
+
manager.bindCallback('onGroupedPermissionRequested', groupRequestCallback)
|
|
135
|
+
|
|
136
|
+
const callB = manager.ensureProtocolPermission({
|
|
137
|
+
originator: 'example.com',
|
|
138
|
+
privileged: false,
|
|
139
|
+
protocolID: [2, 'l2-proto-B'],
|
|
140
|
+
counterparty: 'peer-123',
|
|
141
|
+
reason: 'UnitTest - L2 B peer-123',
|
|
142
|
+
seekPermission: true,
|
|
143
|
+
usageType: 'signing'
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
const callC = manager.ensureProtocolPermission({
|
|
147
|
+
originator: 'example.com',
|
|
148
|
+
privileged: false,
|
|
149
|
+
protocolID: [2, 'l2-proto-C'],
|
|
150
|
+
counterparty: 'peer-999',
|
|
151
|
+
reason: 'UnitTest - L2 C peer-999',
|
|
152
|
+
seekPermission: true,
|
|
153
|
+
usageType: 'signing'
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
await new Promise(res => setTimeout(res, 5))
|
|
157
|
+
|
|
158
|
+
expect(groupRequestCallback).toHaveBeenCalledTimes(2)
|
|
159
|
+
const requestID1 = (groupRequestCallback.mock as any).calls[0][0].requestID
|
|
160
|
+
const requestID2 = (groupRequestCallback.mock as any).calls[1][0].requestID
|
|
161
|
+
|
|
162
|
+
expect(requestID1).not.toBe(requestID2)
|
|
163
|
+
expect(requestID1).toMatch(/^group-peer:/)
|
|
164
|
+
expect(requestID2).toMatch(/^group-peer:/)
|
|
165
|
+
|
|
166
|
+
const activeRequests = (manager as any).activeRequests as Map<string, any>
|
|
167
|
+
expect(activeRequests.size).toBe(2)
|
|
168
|
+
|
|
169
|
+
const queued1 = activeRequests.get(requestID1)
|
|
170
|
+
const queued2 = activeRequests.get(requestID2)
|
|
171
|
+
|
|
172
|
+
expect(queued1.request.permissions.protocolPermissions).toEqual(
|
|
173
|
+
expect.arrayContaining([expect.objectContaining({ protocolID: [2, 'l2-proto-B'], counterparty: 'peer-123' })])
|
|
174
|
+
)
|
|
175
|
+
expect(queued1.request.permissions.protocolPermissions).not.toEqual(
|
|
176
|
+
expect.arrayContaining([expect.objectContaining({ protocolID: [2, 'l2-proto-C'], counterparty: 'peer-999' })])
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
expect(queued2.request.permissions.protocolPermissions).toEqual(
|
|
180
|
+
expect.arrayContaining([expect.objectContaining({ protocolID: [2, 'l2-proto-C'], counterparty: 'peer-999' })])
|
|
181
|
+
)
|
|
182
|
+
expect(queued2.request.permissions.protocolPermissions).not.toEqual(
|
|
183
|
+
expect.arrayContaining([expect.objectContaining({ protocolID: [2, 'l2-proto-B'], counterparty: 'peer-123' })])
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
await manager.denyGroupedPermission(requestID1)
|
|
187
|
+
await manager.denyGroupedPermission(requestID2)
|
|
188
|
+
|
|
189
|
+
await expect(callB).rejects.toThrow(/denied/i)
|
|
190
|
+
await expect(callC).rejects.toThrow(/denied/i)
|
|
191
|
+
|
|
192
|
+
expect(activeRequests.size).toBe(0)
|
|
193
|
+
})
|
|
194
|
+
|
|
38
195
|
it('should coalesce parallel requests for the same resource into a single user prompt', async () => {
|
|
39
196
|
// We want to test the underlying private method "requestPermissionFlow" indirectly
|
|
40
197
|
// or we can test it via a public method that calls it. We'll do so via ensureProtocolPermission.
|