@bsv/sdk 1.6.5 → 1.6.7
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/cjs/package.json +1 -1
- package/dist/cjs/src/kvstore/LocalKVStore.js +139 -84
- package/dist/cjs/src/kvstore/LocalKVStore.js.map +1 -1
- package/dist/cjs/src/storage/StorageDownloader.js +10 -11
- package/dist/cjs/src/storage/StorageDownloader.js.map +1 -1
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/src/kvstore/LocalKVStore.js +139 -84
- package/dist/esm/src/kvstore/LocalKVStore.js.map +1 -1
- package/dist/esm/src/storage/StorageDownloader.js +10 -11
- package/dist/esm/src/storage/StorageDownloader.js.map +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/types/src/kvstore/LocalKVStore.d.ts +12 -2
- package/dist/types/src/kvstore/LocalKVStore.d.ts.map +1 -1
- package/dist/types/src/storage/StorageDownloader.d.ts +10 -11
- package/dist/types/src/storage/StorageDownloader.d.ts.map +1 -1
- package/dist/types/tsconfig.types.tsbuildinfo +1 -1
- package/dist/umd/bundle.js +1 -1
- package/docs/kvstore.md +6 -3
- package/docs/storage.md +35 -2
- package/package.json +1 -1
- package/src/kvstore/LocalKVStore.ts +154 -84
- package/src/storage/StorageDownloader.ts +10 -11
package/docs/kvstore.md
CHANGED
|
@@ -14,7 +14,8 @@ Allows setting, getting, and removing key-value pairs, with optional encryption.
|
|
|
14
14
|
|
|
15
15
|
```ts
|
|
16
16
|
export default class LocalKVStore {
|
|
17
|
-
|
|
17
|
+
acceptDelayedBroadcast: boolean = false;
|
|
18
|
+
constructor(wallet: WalletInterface = new WalletClient(), context = "kvstore default", encrypt = true, originator?: string, acceptDelayedBroadcast = false)
|
|
18
19
|
async get(key: string, defaultValue: string | undefined = undefined): Promise<string | undefined>
|
|
19
20
|
async set(key: string, value: string): Promise<OutpointString>
|
|
20
21
|
async remove(key: string): Promise<string[]>
|
|
@@ -28,7 +29,7 @@ See also: [OutpointString](./wallet.md#type-outpointstring), [WalletClient](./wa
|
|
|
28
29
|
Creates an instance of the localKVStore.
|
|
29
30
|
|
|
30
31
|
```ts
|
|
31
|
-
constructor(wallet: WalletInterface = new WalletClient(), context = "kvstore default", encrypt = true, originator?: string)
|
|
32
|
+
constructor(wallet: WalletInterface = new WalletClient(), context = "kvstore default", encrypt = true, originator?: string, acceptDelayedBroadcast = false)
|
|
32
33
|
```
|
|
33
34
|
See also: [WalletClient](./wallet.md#class-walletclient), [WalletInterface](./wallet.md#interface-walletinterface), [encrypt](./messages.md#variable-encrypt)
|
|
34
35
|
|
|
@@ -96,13 +97,15 @@ Argument Details
|
|
|
96
97
|
|
|
97
98
|
#### Method set
|
|
98
99
|
|
|
99
|
-
Sets or updates the value associated with a given key.
|
|
100
|
+
Sets or updates the value associated with a given key atomically.
|
|
100
101
|
If the key already exists (one or more outputs found), it spends the existing output(s)
|
|
101
102
|
and creates a new one with the updated value. If multiple outputs exist for the key,
|
|
102
103
|
they are collapsed into a single new output.
|
|
103
104
|
If the key does not exist, it creates a new output.
|
|
104
105
|
Handles encryption if enabled.
|
|
105
106
|
If signing the update/collapse transaction fails, it relinquishes the original outputs and starts over with a new chain.
|
|
107
|
+
Ensures atomicity by locking the key during the operation, preventing concurrent updates
|
|
108
|
+
to the same key from missing earlier changes.
|
|
106
109
|
|
|
107
110
|
```ts
|
|
108
111
|
async set(key: string, value: string): Promise<OutpointString>
|
package/docs/storage.md
CHANGED
|
@@ -120,8 +120,6 @@ Links: [API](#api), [Interfaces](#interfaces), [Classes](#classes), [Functions](
|
|
|
120
120
|
|
|
121
121
|
### Class: StorageDownloader
|
|
122
122
|
|
|
123
|
-
Locates HTTP URLs where content can be downloaded. It uses the passed or the default one.
|
|
124
|
-
|
|
125
123
|
```ts
|
|
126
124
|
export class StorageDownloader {
|
|
127
125
|
constructor(config?: DownloaderConfig)
|
|
@@ -132,6 +130,41 @@ export class StorageDownloader {
|
|
|
132
130
|
|
|
133
131
|
See also: [DownloadResult](./storage.md#interface-downloadresult), [DownloaderConfig](./storage.md#interface-downloaderconfig)
|
|
134
132
|
|
|
133
|
+
#### Method download
|
|
134
|
+
|
|
135
|
+
Downloads the content from the UHRP URL after validating the hash for integrity.
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
public async download(uhrpUrl: string): Promise<DownloadResult>
|
|
139
|
+
```
|
|
140
|
+
See also: [DownloadResult](./storage.md#interface-downloadresult)
|
|
141
|
+
|
|
142
|
+
Returns
|
|
143
|
+
|
|
144
|
+
A promise that resolves to the downloaded content.
|
|
145
|
+
|
|
146
|
+
Argument Details
|
|
147
|
+
|
|
148
|
+
+ **uhrpUrl**
|
|
149
|
+
+ The UHRP URL to download.
|
|
150
|
+
|
|
151
|
+
#### Method resolve
|
|
152
|
+
|
|
153
|
+
Resolves the UHRP URL to a list of HTTP URLs where content can be downloaded.
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
public async resolve(uhrpUrl: string): Promise<string[]>
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Returns
|
|
160
|
+
|
|
161
|
+
A promise that resolves to an array of HTTP URLs.
|
|
162
|
+
|
|
163
|
+
Argument Details
|
|
164
|
+
|
|
165
|
+
+ **uhrpUrl**
|
|
166
|
+
+ The UHRP URL to resolve.
|
|
167
|
+
|
|
135
168
|
Links: [API](#api), [Interfaces](#interfaces), [Classes](#classes), [Functions](#functions), [Types](#types), [Enums](#enums), [Variables](#variables)
|
|
136
169
|
|
|
137
170
|
---
|
package/package.json
CHANGED
|
@@ -38,6 +38,14 @@ export default class LocalKVStore {
|
|
|
38
38
|
*/
|
|
39
39
|
private readonly originator?: string
|
|
40
40
|
|
|
41
|
+
acceptDelayedBroadcast: boolean = false
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* A map to store locks for each key to ensure atomic updates.
|
|
45
|
+
* @private
|
|
46
|
+
*/
|
|
47
|
+
private readonly keyLocks: Map<string, Array<(value: void | PromiseLike<void>) => void>> = new Map()
|
|
48
|
+
|
|
41
49
|
/**
|
|
42
50
|
* Creates an instance of the localKVStore.
|
|
43
51
|
*
|
|
@@ -51,7 +59,8 @@ export default class LocalKVStore {
|
|
|
51
59
|
wallet: WalletInterface = new WalletClient(),
|
|
52
60
|
context = 'kvstore default',
|
|
53
61
|
encrypt = true,
|
|
54
|
-
originator?: string
|
|
62
|
+
originator?: string,
|
|
63
|
+
acceptDelayedBroadcast = false
|
|
55
64
|
) {
|
|
56
65
|
if (typeof context !== 'string' || context.length < 1) {
|
|
57
66
|
throw new Error('A context in which to operate is required.')
|
|
@@ -60,6 +69,39 @@ export default class LocalKVStore {
|
|
|
60
69
|
this.context = context
|
|
61
70
|
this.encrypt = encrypt
|
|
62
71
|
this.originator = originator
|
|
72
|
+
this.acceptDelayedBroadcast = acceptDelayedBroadcast
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
private async queueOperationOnKey (key: string): Promise<Array<(value: void | PromiseLike<void>) => void>> {
|
|
76
|
+
// Check if a lock exists for this key and wait for it to resolve
|
|
77
|
+
let lockQueue = this.keyLocks.get(key)
|
|
78
|
+
if (lockQueue == null) {
|
|
79
|
+
lockQueue = []
|
|
80
|
+
this.keyLocks.set(key, lockQueue)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
let resolveNewLock: () => void = () => {}
|
|
84
|
+
const newLock = new Promise<void>((resolve) => {
|
|
85
|
+
resolveNewLock = resolve
|
|
86
|
+
if (lockQueue != null) { lockQueue.push(resolve) }
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
// If we are the only request, resolve the lock immediately, queue remains at 1 item until request ends.
|
|
90
|
+
if (lockQueue.length === 1) {
|
|
91
|
+
resolveNewLock()
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
await newLock
|
|
95
|
+
|
|
96
|
+
return lockQueue
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private finishOperationOnKey (key: string, lockQueue: Array<(value: void | PromiseLike<void>) => void>): void {
|
|
100
|
+
lockQueue.shift() // Remove the current lock from the queue
|
|
101
|
+
if (lockQueue.length > 0) {
|
|
102
|
+
// If there are more locks waiting, resolve the next one
|
|
103
|
+
lockQueue[0]()
|
|
104
|
+
}
|
|
63
105
|
}
|
|
64
106
|
|
|
65
107
|
private getProtocol (key: string): { protocolID: WalletProtocol, keyID: string } {
|
|
@@ -88,8 +130,14 @@ export default class LocalKVStore {
|
|
|
88
130
|
* @throws {Error} If the found output's locking script cannot be decoded or represents an invalid token format.
|
|
89
131
|
*/
|
|
90
132
|
async get (key: string, defaultValue: string | undefined = undefined): Promise<string | undefined> {
|
|
91
|
-
const
|
|
92
|
-
|
|
133
|
+
const lockQueue = await this.queueOperationOnKey(key)
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
const r = await this.lookupValue(key, defaultValue, 5)
|
|
137
|
+
return r.value
|
|
138
|
+
} finally {
|
|
139
|
+
this.finishOperationOnKey(key, lockQueue)
|
|
140
|
+
}
|
|
93
141
|
}
|
|
94
142
|
|
|
95
143
|
private getLockingScript (output: WalletOutput, beef: Beef): LockingScript {
|
|
@@ -160,78 +208,94 @@ export default class LocalKVStore {
|
|
|
160
208
|
}
|
|
161
209
|
|
|
162
210
|
/**
|
|
163
|
-
* Sets or updates the value associated with a given key.
|
|
211
|
+
* Sets or updates the value associated with a given key atomically.
|
|
164
212
|
* If the key already exists (one or more outputs found), it spends the existing output(s)
|
|
165
213
|
* and creates a new one with the updated value. If multiple outputs exist for the key,
|
|
166
214
|
* they are collapsed into a single new output.
|
|
167
215
|
* If the key does not exist, it creates a new output.
|
|
168
216
|
* Handles encryption if enabled.
|
|
169
217
|
* If signing the update/collapse transaction fails, it relinquishes the original outputs and starts over with a new chain.
|
|
218
|
+
* Ensures atomicity by locking the key during the operation, preventing concurrent updates
|
|
219
|
+
* to the same key from missing earlier changes.
|
|
170
220
|
*
|
|
171
221
|
* @param {string} key - The key to set or update.
|
|
172
222
|
* @param {string} value - The value to associate with the key.
|
|
173
223
|
* @returns {Promise<OutpointString>} A promise that resolves to the outpoint string (txid.vout) of the new or updated token output.
|
|
174
224
|
*/
|
|
175
225
|
async set (key: string, value: string): Promise<OutpointString> {
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
if (current.outpoint === undefined) { throw new Error('outpoint must be valid when value is valid and unchanged') }
|
|
179
|
-
// Don't create a new transaction if the value doesn't need to change...
|
|
180
|
-
return current.outpoint
|
|
181
|
-
}
|
|
182
|
-
const protocol = this.getProtocol(key)
|
|
183
|
-
let valueAsArray = Utils.toArray(value, 'utf8')
|
|
184
|
-
if (this.encrypt) {
|
|
185
|
-
const { ciphertext } = await this.wallet.encrypt({
|
|
186
|
-
...protocol,
|
|
187
|
-
plaintext: valueAsArray
|
|
188
|
-
})
|
|
189
|
-
valueAsArray = ciphertext
|
|
190
|
-
}
|
|
191
|
-
const pushdrop = new PushDrop(this.wallet, this.originator)
|
|
192
|
-
const lockingScript = await pushdrop.lock(
|
|
193
|
-
[valueAsArray],
|
|
194
|
-
protocol.protocolID,
|
|
195
|
-
protocol.keyID,
|
|
196
|
-
'self'
|
|
197
|
-
)
|
|
198
|
-
const { outputs, BEEF: inputBEEF } = current.lor
|
|
199
|
-
let outpoint: OutpointString
|
|
226
|
+
const lockQueue = await this.queueOperationOnKey(key)
|
|
227
|
+
|
|
200
228
|
try {
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
inputs,
|
|
206
|
-
outputs: [{
|
|
207
|
-
basket: this.context,
|
|
208
|
-
tags: [key],
|
|
209
|
-
lockingScript: lockingScript.toHex(),
|
|
210
|
-
satoshis: 1,
|
|
211
|
-
outputDescription: 'Key-value token'
|
|
212
|
-
}],
|
|
213
|
-
options: {
|
|
214
|
-
acceptDelayedBroadcast: false,
|
|
215
|
-
randomizeOutputs: false
|
|
229
|
+
const current = await this.lookupValue(key, undefined, 10)
|
|
230
|
+
if (current.value === value) {
|
|
231
|
+
if (current.outpoint === undefined) {
|
|
232
|
+
throw new Error('outpoint must be valid when value is valid and unchanged')
|
|
216
233
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
throw new Error('Wallet did not return a signable transaction when expected.')
|
|
234
|
+
// Don't create a new transaction if the value doesn't need to change
|
|
235
|
+
return current.outpoint
|
|
220
236
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
const {
|
|
226
|
-
|
|
227
|
-
|
|
237
|
+
|
|
238
|
+
const protocol = this.getProtocol(key)
|
|
239
|
+
let valueAsArray = Utils.toArray(value, 'utf8')
|
|
240
|
+
if (this.encrypt) {
|
|
241
|
+
const { ciphertext } = await this.wallet.encrypt({
|
|
242
|
+
...protocol,
|
|
243
|
+
plaintext: valueAsArray
|
|
228
244
|
})
|
|
229
|
-
|
|
245
|
+
valueAsArray = ciphertext
|
|
230
246
|
}
|
|
231
|
-
|
|
232
|
-
|
|
247
|
+
|
|
248
|
+
const pushdrop = new PushDrop(this.wallet, this.originator)
|
|
249
|
+
const lockingScript = await pushdrop.lock(
|
|
250
|
+
[valueAsArray],
|
|
251
|
+
protocol.protocolID,
|
|
252
|
+
protocol.keyID,
|
|
253
|
+
'self'
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
const { outputs, BEEF: inputBEEF } = current.lor
|
|
257
|
+
let outpoint: OutpointString
|
|
258
|
+
try {
|
|
259
|
+
const inputs = this.getInputs(outputs)
|
|
260
|
+
const { txid, signableTransaction } = await this.wallet.createAction({
|
|
261
|
+
description: `Update ${key} in ${this.context}`,
|
|
262
|
+
inputBEEF,
|
|
263
|
+
inputs,
|
|
264
|
+
outputs: [{
|
|
265
|
+
basket: this.context,
|
|
266
|
+
tags: [key],
|
|
267
|
+
lockingScript: lockingScript.toHex(),
|
|
268
|
+
satoshis: 1,
|
|
269
|
+
outputDescription: 'Key-value token'
|
|
270
|
+
}],
|
|
271
|
+
options: {
|
|
272
|
+
acceptDelayedBroadcast: this.acceptDelayedBroadcast,
|
|
273
|
+
randomizeOutputs: false
|
|
274
|
+
}
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
if (outputs.length > 0 && typeof signableTransaction !== 'object') {
|
|
278
|
+
throw new Error('Wallet did not return a signable transaction when expected.')
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (signableTransaction == null) {
|
|
282
|
+
outpoint = `${txid as string}.0`
|
|
283
|
+
} else {
|
|
284
|
+
const spends = await this.getSpends(key, outputs, pushdrop, signableTransaction.tx)
|
|
285
|
+
const { txid } = await this.wallet.signAction({
|
|
286
|
+
reference: signableTransaction.reference,
|
|
287
|
+
spends
|
|
288
|
+
})
|
|
289
|
+
outpoint = `${txid as string}.0`
|
|
290
|
+
}
|
|
291
|
+
} catch (_) {
|
|
292
|
+
throw new Error(`There are ${outputs.length} outputs with tag ${key} that cannot be unlocked.`)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return outpoint
|
|
296
|
+
} finally {
|
|
297
|
+
this.finishOperationOnKey(key, lockQueue)
|
|
233
298
|
}
|
|
234
|
-
return outpoint
|
|
235
299
|
}
|
|
236
300
|
|
|
237
301
|
/**
|
|
@@ -245,38 +309,44 @@ export default class LocalKVStore {
|
|
|
245
309
|
* @returns {Promise<string[]>} A promise that resolves to the txids of the removal transactions if successful.
|
|
246
310
|
*/
|
|
247
311
|
async remove (key: string): Promise<string[]> {
|
|
248
|
-
const
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
const
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
312
|
+
const lockQueue = await this.queueOperationOnKey(key)
|
|
313
|
+
|
|
314
|
+
try {
|
|
315
|
+
const txids: string[] = []
|
|
316
|
+
for (; ;) {
|
|
317
|
+
const { outputs, BEEF: inputBEEF, totalOutputs } = await this.getOutputs(key)
|
|
318
|
+
if (outputs.length > 0) {
|
|
319
|
+
const pushdrop = new PushDrop(this.wallet, this.originator)
|
|
320
|
+
try {
|
|
321
|
+
const inputs = this.getInputs(outputs)
|
|
322
|
+
const { signableTransaction } = await this.wallet.createAction({
|
|
323
|
+
description: `Remove ${key} in ${this.context}`,
|
|
324
|
+
inputBEEF,
|
|
325
|
+
inputs,
|
|
326
|
+
options: {
|
|
327
|
+
acceptDelayedBroadcast: this.acceptDelayedBroadcast
|
|
328
|
+
}
|
|
329
|
+
})
|
|
330
|
+
if (typeof signableTransaction !== 'object') {
|
|
331
|
+
throw new Error('Wallet did not return a signable transaction when expected.')
|
|
261
332
|
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
333
|
+
const spends = await this.getSpends(key, outputs, pushdrop, signableTransaction.tx)
|
|
334
|
+
const { txid } = await this.wallet.signAction({
|
|
335
|
+
reference: signableTransaction.reference,
|
|
336
|
+
spends
|
|
337
|
+
})
|
|
338
|
+
if (txid === undefined) { throw new Error('signAction must return a valid txid') }
|
|
339
|
+
txids.push(txid)
|
|
340
|
+
} catch (_) {
|
|
341
|
+
throw new Error(`There are ${totalOutputs} outputs with tag ${key} that cannot be unlocked.`)
|
|
265
342
|
}
|
|
266
|
-
const spends = await this.getSpends(key, outputs, pushdrop, signableTransaction.tx)
|
|
267
|
-
const { txid } = await this.wallet.signAction({
|
|
268
|
-
reference: signableTransaction.reference,
|
|
269
|
-
spends
|
|
270
|
-
})
|
|
271
|
-
if (txid === undefined) { throw new Error('signAction must return a valid txid') }
|
|
272
|
-
txids.push(txid)
|
|
273
|
-
} catch (_) {
|
|
274
|
-
throw new Error(`There are ${totalOutputs} outputs with tag ${key} that cannot be unlocked.`)
|
|
275
343
|
}
|
|
344
|
+
if (outputs.length === totalOutputs) { break }
|
|
276
345
|
}
|
|
277
|
-
|
|
346
|
+
return txids
|
|
347
|
+
} finally {
|
|
348
|
+
this.finishOperationOnKey(key, lockQueue)
|
|
278
349
|
}
|
|
279
|
-
return txids
|
|
280
350
|
}
|
|
281
351
|
}
|
|
282
352
|
|
|
@@ -13,17 +13,6 @@ export interface DownloadResult {
|
|
|
13
13
|
mimeType: string | null
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
/**
|
|
17
|
-
* Locates HTTP URLs where content can be downloaded. It uses the passed or the default one.
|
|
18
|
-
*
|
|
19
|
-
* @param {Object} obj All parameters are passed in an object.
|
|
20
|
-
* @param {String} obj.uhrpUrl The UHRP url to resolve.
|
|
21
|
-
* @param {string} obj.confederacyHost HTTPS URL for for the with default setting.
|
|
22
|
-
*
|
|
23
|
-
* @return {Array<String>} An array of HTTP URLs where content can be downloaded.
|
|
24
|
-
* @throws {Error} If UHRP url parameter invalid or is not an array
|
|
25
|
-
* or there is an error retrieving url(s) stored in the UHRP token.
|
|
26
|
-
*/
|
|
27
16
|
export class StorageDownloader {
|
|
28
17
|
private readonly networkPreset?: 'mainnet' | 'testnet' | 'local' = 'mainnet'
|
|
29
18
|
|
|
@@ -31,6 +20,11 @@ export class StorageDownloader {
|
|
|
31
20
|
this.networkPreset = config?.networkPreset
|
|
32
21
|
}
|
|
33
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Resolves the UHRP URL to a list of HTTP URLs where content can be downloaded.
|
|
25
|
+
* @param uhrpUrl The UHRP URL to resolve.
|
|
26
|
+
* @returns A promise that resolves to an array of HTTP URLs.
|
|
27
|
+
*/
|
|
34
28
|
public async resolve (uhrpUrl: string): Promise<string[]> {
|
|
35
29
|
// Use UHRP lookup service
|
|
36
30
|
const lookupResolver = new LookupResolver({ networkPreset: this.networkPreset })
|
|
@@ -54,6 +48,11 @@ export class StorageDownloader {
|
|
|
54
48
|
return decodedResults
|
|
55
49
|
}
|
|
56
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Downloads the content from the UHRP URL after validating the hash for integrity.
|
|
53
|
+
* @param uhrpUrl The UHRP URL to download.
|
|
54
|
+
* @returns A promise that resolves to the downloaded content.
|
|
55
|
+
*/
|
|
57
56
|
public async download (uhrpUrl: string): Promise<DownloadResult> {
|
|
58
57
|
if (!StorageUtils.isValidURL(uhrpUrl)) {
|
|
59
58
|
throw new Error('Invalid parameter UHRP url')
|