@bsv/wallet-toolbox-client 2.8.0 → 2.10.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/out/index.client.cjs +764 -401
- package/out/index.client.cjs.map +1 -1
- package/out/index.client.d.cts +252 -56
- package/out/index.client.d.cts.map +1 -1
- package/out/index.client.d.mts +252 -56
- package/out/index.client.d.mts.map +1 -1
- package/out/index.client.mjs +764 -403
- package/out/index.client.mjs.map +1 -1
- package/package.json +2 -2
package/out/index.client.cjs
CHANGED
|
@@ -4081,7 +4081,7 @@ var EntitySyncState = class EntitySyncState extends EntityBase {
|
|
|
4081
4081
|
log += formatSyncSection("OUTPUTS", c.outputs, (r) => `${r.outputId} ${r.txid}.${r.vout} ${r.transactionId} ${r.spendable ? "spendable" : ""} sats:${r.satoshis}`);
|
|
4082
4082
|
return log;
|
|
4083
4083
|
}
|
|
4084
|
-
async processSyncChunk(writer, args, chunk) {
|
|
4084
|
+
async processSyncChunk(writer, args, chunk, trx) {
|
|
4085
4085
|
const mes = [
|
|
4086
4086
|
new MergeEntity(chunk.provenTxs, EntityProvenTx.mergeFind, this.syncMap.provenTx),
|
|
4087
4087
|
new MergeEntity(chunk.outputBaskets, EntityOutputBasket.mergeFind, this.syncMap.outputBasket),
|
|
@@ -4102,16 +4102,16 @@ var EntitySyncState = class EntitySyncState extends EntityBase {
|
|
|
4102
4102
|
let done = true;
|
|
4103
4103
|
if (chunk.user != null) {
|
|
4104
4104
|
const ei = chunk.user;
|
|
4105
|
-
const { found, eo } = await EntityUser.mergeFind(writer, this.userId, ei);
|
|
4105
|
+
const { found, eo } = await EntityUser.mergeFind(writer, this.userId, ei, trx);
|
|
4106
4106
|
if (found) {
|
|
4107
|
-
if (await eo.mergeExisting(writer, args.since, ei)) {
|
|
4107
|
+
if (await eo.mergeExisting(writer, args.since, ei, void 0, trx)) {
|
|
4108
4108
|
maxUpdated_at = maxDate(maxUpdated_at, ei.updated_at);
|
|
4109
4109
|
updates++;
|
|
4110
4110
|
}
|
|
4111
4111
|
}
|
|
4112
4112
|
}
|
|
4113
4113
|
for (const me of mes) {
|
|
4114
|
-
const r = await me.merge(args.since, writer, this.userId, this.syncMap);
|
|
4114
|
+
const r = await me.merge(args.since, writer, this.userId, this.syncMap, trx);
|
|
4115
4115
|
me.esm.count += me.stateArray?.length || 0;
|
|
4116
4116
|
updates += r.updates;
|
|
4117
4117
|
inserts += r.inserts;
|
|
@@ -4122,7 +4122,7 @@ var EntitySyncState = class EntitySyncState extends EntityBase {
|
|
|
4122
4122
|
this.when = maxUpdated_at;
|
|
4123
4123
|
for (const me of mes) me.esm.count = 0;
|
|
4124
4124
|
}
|
|
4125
|
-
await this.updateStorage(writer, false);
|
|
4125
|
+
await this.updateStorage(writer, false, trx);
|
|
4126
4126
|
return {
|
|
4127
4127
|
done,
|
|
4128
4128
|
maxUpdated_at,
|
|
@@ -8254,28 +8254,70 @@ function verifyActionBatchManifestDigest(manifest) {
|
|
|
8254
8254
|
//#endregion
|
|
8255
8255
|
//#region ../src/utility/beefForTxids.ts
|
|
8256
8256
|
/**
|
|
8257
|
-
* Return
|
|
8257
|
+
* Return a minimal BEEF when the source contains data outside the requested
|
|
8258
|
+
* transaction dependency closure. Return undefined when no pruning is needed.
|
|
8258
8259
|
*
|
|
8259
|
-
*
|
|
8260
|
-
*
|
|
8260
|
+
* This form lets forwarding clients retain the caller's original bytes in the
|
|
8261
|
+
* common no-op case instead of rebuilding and reserializing an equivalent BEEF.
|
|
8262
|
+
*/
|
|
8263
|
+
function pruneBeefForTxids(source, txids) {
|
|
8264
|
+
const selection = selectTransactions(source, txids);
|
|
8265
|
+
if (selection.transactions.size === source.txs.length && selection.bumpIndexes.size === source.bumps.length) return;
|
|
8266
|
+
return copySelection(source, selection);
|
|
8267
|
+
}
|
|
8268
|
+
/**
|
|
8269
|
+
* Return an independent minimal BEEF needed to prove the requested transactions.
|
|
8270
|
+
*
|
|
8271
|
+
* Transactions remain in source order and are sorted by Beef when serialized.
|
|
8272
|
+
* The source is indexed and walked once, using an explicit stack so a hostile
|
|
8273
|
+
* dependency depth cannot exhaust the JavaScript call stack.
|
|
8261
8274
|
*/
|
|
8262
8275
|
function beefForTxids(source, txids) {
|
|
8263
|
-
|
|
8276
|
+
return copySelection(source, selectTransactions(source, txids));
|
|
8277
|
+
}
|
|
8278
|
+
function selectTransactions(source, txids) {
|
|
8279
|
+
const byTxid = /* @__PURE__ */ new Map();
|
|
8280
|
+
for (const tx of source.txs) byTxid.set(tx.txid, tx);
|
|
8281
|
+
const transactions = /* @__PURE__ */ new Set();
|
|
8282
|
+
const bumpIndexes = /* @__PURE__ */ new Set();
|
|
8264
8283
|
const visited = /* @__PURE__ */ new Set();
|
|
8265
|
-
const
|
|
8266
|
-
|
|
8284
|
+
const stack = [...txids];
|
|
8285
|
+
while (stack.length > 0) {
|
|
8286
|
+
const txid = stack.pop();
|
|
8287
|
+
if (txid == null || visited.has(txid)) continue;
|
|
8267
8288
|
visited.add(txid);
|
|
8268
|
-
const
|
|
8269
|
-
if (
|
|
8270
|
-
|
|
8271
|
-
|
|
8272
|
-
|
|
8273
|
-
|
|
8274
|
-
|
|
8289
|
+
const tx = byTxid.get(txid);
|
|
8290
|
+
if (tx == null) continue;
|
|
8291
|
+
transactions.add(tx);
|
|
8292
|
+
const bumpIndex = tx.bumpIndex;
|
|
8293
|
+
if (bumpIndex != null && Number.isSafeInteger(bumpIndex) && bumpIndex >= 0 && bumpIndex < source.bumps.length) bumpIndexes.add(bumpIndex);
|
|
8294
|
+
for (const inputTxid of tx.inputTxids) if (!visited.has(inputTxid)) stack.push(inputTxid);
|
|
8295
|
+
}
|
|
8296
|
+
return {
|
|
8297
|
+
transactions,
|
|
8298
|
+
bumpIndexes
|
|
8275
8299
|
};
|
|
8276
|
-
|
|
8300
|
+
}
|
|
8301
|
+
function copySelection(source, selection) {
|
|
8302
|
+
const beef = new _bsv_sdk.Beef(source.version);
|
|
8303
|
+
const bumpIndexMap = /* @__PURE__ */ new Map();
|
|
8304
|
+
for (let index = 0; index < source.bumps.length; index++) {
|
|
8305
|
+
if (!selection.bumpIndexes.has(index)) continue;
|
|
8306
|
+
bumpIndexMap.set(index, beef.bumps.length);
|
|
8307
|
+
beef.bumps.push(cloneMerklePath(source.bumps[index]));
|
|
8308
|
+
}
|
|
8309
|
+
for (const sourceTx of source.txs) {
|
|
8310
|
+
if (!selection.transactions.has(sourceTx)) continue;
|
|
8311
|
+
const bumpIndex = sourceTx.bumpIndex == null ? void 0 : bumpIndexMap.get(sourceTx.bumpIndex);
|
|
8312
|
+
const rawTx = sourceTx.rawTxUint8Array;
|
|
8313
|
+
const copy = rawTx == null ? _bsv_sdk.BeefTx.fromTxid(sourceTx.txid, bumpIndex) : new _bsv_sdk.BeefTx(Uint8Array.from(rawTx), bumpIndex, Array.from(sourceTx.inputTxids));
|
|
8314
|
+
beef.txs.push(copy);
|
|
8315
|
+
}
|
|
8277
8316
|
return beef;
|
|
8278
8317
|
}
|
|
8318
|
+
function cloneMerklePath(source) {
|
|
8319
|
+
return new _bsv_sdk.MerklePath(source.blockHeight, source.path.map((level) => level.map((leaf) => ({ ...leaf }))), false, false);
|
|
8320
|
+
}
|
|
8279
8321
|
//#endregion
|
|
8280
8322
|
//#region ../src/storage/methods/offsetKey.ts
|
|
8281
8323
|
function keyOffsetToHashedSecret(pub, keyOffset) {
|
|
@@ -10973,7 +11015,7 @@ function validateRequiredOutputs(storage, userId, vargs) {
|
|
|
10973
11015
|
* @returns {xinputs} extended validated required inputs.
|
|
10974
11016
|
*/
|
|
10975
11017
|
async function validateRequiredInputs(storage, userId, vargs) {
|
|
10976
|
-
|
|
11018
|
+
let beef = new _bsv_sdk.Beef();
|
|
10977
11019
|
if (vargs.inputs.length === 0) return {
|
|
10978
11020
|
storageBeef: beef,
|
|
10979
11021
|
beef,
|
|
@@ -10999,6 +11041,7 @@ async function validateRequiredInputs(storage, userId, vargs) {
|
|
|
10999
11041
|
inputsByTxid[input.outpoint.txid] ||= [];
|
|
11000
11042
|
inputsByTxid[input.outpoint.txid].push(input);
|
|
11001
11043
|
}
|
|
11044
|
+
beef = beefForTxids(beef, Object.keys(inputsByTxid));
|
|
11002
11045
|
const localKnownInputTxids = {};
|
|
11003
11046
|
for (const [txid, txInputs] of Object.entries(inputsByTxid)) localKnownInputTxids[txid] = txInputs.every((input) => {
|
|
11004
11047
|
const output = preloadedOutputsByOutpoint[`${input.outpoint.txid}.${input.outpoint.vout}`];
|
|
@@ -15260,11 +15303,19 @@ var StorageProvider = class StorageProvider extends StorageReaderWriter {
|
|
|
15260
15303
|
return await this.updateOutput(output.outputId, { basketId: void 0 });
|
|
15261
15304
|
}
|
|
15262
15305
|
async processSyncChunk(args, chunk) {
|
|
15263
|
-
|
|
15264
|
-
|
|
15265
|
-
|
|
15266
|
-
|
|
15267
|
-
|
|
15306
|
+
return await this.transaction(async (trx) => {
|
|
15307
|
+
const user = verifyTruthy(verifyOneOrNone(await this.findUsers({
|
|
15308
|
+
partial: { identityKey: args.identityKey },
|
|
15309
|
+
trx
|
|
15310
|
+
})));
|
|
15311
|
+
return await new EntitySyncState(verifyOne(await this.findSyncStates({
|
|
15312
|
+
partial: {
|
|
15313
|
+
storageIdentityKey: args.fromStorageIdentityKey,
|
|
15314
|
+
userId: user.userId
|
|
15315
|
+
},
|
|
15316
|
+
trx
|
|
15317
|
+
}))).processSyncChunk(this, args, chunk, trx);
|
|
15318
|
+
});
|
|
15268
15319
|
}
|
|
15269
15320
|
/**
|
|
15270
15321
|
* Handles storage changes when a valid MerklePath and mined block header are found for a ProvenTxReq txid.
|
|
@@ -17329,8 +17380,10 @@ var StorageIdb = class extends StorageProvider {
|
|
|
17329
17380
|
await tx.done;
|
|
17330
17381
|
return r;
|
|
17331
17382
|
} catch (err) {
|
|
17332
|
-
|
|
17333
|
-
|
|
17383
|
+
try {
|
|
17384
|
+
tx.abort();
|
|
17385
|
+
await tx.done;
|
|
17386
|
+
} catch {}
|
|
17334
17387
|
throw err;
|
|
17335
17388
|
}
|
|
17336
17389
|
}
|
|
@@ -18297,6 +18350,25 @@ var StorageClientBase = class {
|
|
|
18297
18350
|
* @returns `StorageCreateActionResults` supporting additional wallet processing to yield `createAction` results.
|
|
18298
18351
|
*/
|
|
18299
18352
|
async createAction(auth, args) {
|
|
18353
|
+
if (args.inputBEEF != null) if (args.inputs.length === 0) args = {
|
|
18354
|
+
...args,
|
|
18355
|
+
inputBEEF: void 0
|
|
18356
|
+
};
|
|
18357
|
+
else {
|
|
18358
|
+
let source;
|
|
18359
|
+
try {
|
|
18360
|
+
source = _bsv_sdk.Beef.fromBinary(args.inputBEEF);
|
|
18361
|
+
} catch {
|
|
18362
|
+
source = void 0;
|
|
18363
|
+
}
|
|
18364
|
+
if (source != null) {
|
|
18365
|
+
const pruned = pruneBeefForTxids(source, args.inputs.map((input) => input.outpoint.txid));
|
|
18366
|
+
if (pruned != null) args = {
|
|
18367
|
+
...args,
|
|
18368
|
+
inputBEEF: pruned.toBinary()
|
|
18369
|
+
};
|
|
18370
|
+
}
|
|
18371
|
+
}
|
|
18300
18372
|
return await this.rpcCall("createAction", [auth, args]);
|
|
18301
18373
|
}
|
|
18302
18374
|
/**
|
|
@@ -19597,6 +19669,24 @@ function isLiveBlockHeader(header) {
|
|
|
19597
19669
|
return "chainWork" in header && typeof header.previousHash === "string";
|
|
19598
19670
|
}
|
|
19599
19671
|
//#endregion
|
|
19672
|
+
//#region ../src/services/chaintracker/chaintracks/Api/BulkFileDataValidatorApi.ts
|
|
19673
|
+
/**
|
|
19674
|
+
* Identifies deterministic rejection of the supplied immutable bytes.
|
|
19675
|
+
* Operational failures such as worker crashes and queue saturation deliberately
|
|
19676
|
+
* use ordinary errors so callers preserve the cache entry and avoid downloading
|
|
19677
|
+
* a replacement that cannot be validated.
|
|
19678
|
+
*
|
|
19679
|
+
* @public
|
|
19680
|
+
*/
|
|
19681
|
+
var BulkFileDataValidationError = class extends Error {
|
|
19682
|
+
data;
|
|
19683
|
+
constructor(message, data) {
|
|
19684
|
+
super(message);
|
|
19685
|
+
this.data = data;
|
|
19686
|
+
this.name = "BulkFileDataValidationError";
|
|
19687
|
+
}
|
|
19688
|
+
};
|
|
19689
|
+
//#endregion
|
|
19600
19690
|
//#region ../src/services/chaintracker/chaintracks/util/HeightRange.ts
|
|
19601
19691
|
/**
|
|
19602
19692
|
* Represents a range of block heights.
|
|
@@ -19815,6 +19905,8 @@ var Chaintracks = class {
|
|
|
19815
19905
|
lastPresentHeight = -1;
|
|
19816
19906
|
lastPresentHeightMsecs = 0;
|
|
19817
19907
|
lastPresentHeightMaxAge = 60 * 1e3;
|
|
19908
|
+
presentHeightRefresh;
|
|
19909
|
+
mainLoopHeartbeatMsecs = 0;
|
|
19818
19910
|
lock = new SingleWriterMultiReaderLock();
|
|
19819
19911
|
sourceStatus = /* @__PURE__ */ new Map();
|
|
19820
19912
|
constructor(options) {
|
|
@@ -19852,12 +19944,31 @@ var Chaintracks = class {
|
|
|
19852
19944
|
return this.chain;
|
|
19853
19945
|
}
|
|
19854
19946
|
/**
|
|
19855
|
-
*
|
|
19856
|
-
*
|
|
19947
|
+
* Returns the last known valid height immediately and refreshes stale state
|
|
19948
|
+
* once in the background. Cold start waits for the single shared refresh.
|
|
19857
19949
|
*/
|
|
19858
19950
|
async getPresentHeight() {
|
|
19859
19951
|
const now = Date.now();
|
|
19860
19952
|
if (this.lastPresentHeight >= 0 && now - this.lastPresentHeightMsecs < this.lastPresentHeightMaxAge) return this.lastPresentHeight;
|
|
19953
|
+
if (this.lastPresentHeight >= 0) {
|
|
19954
|
+
this.refreshPresentHeight().catch((error) => {
|
|
19955
|
+
this.log(`Background present-height refresh failed: ${WalletError.fromUnknown(error).message}`);
|
|
19956
|
+
});
|
|
19957
|
+
return this.lastPresentHeight;
|
|
19958
|
+
}
|
|
19959
|
+
return await this.refreshPresentHeight();
|
|
19960
|
+
}
|
|
19961
|
+
async refreshPresentHeight() {
|
|
19962
|
+
if (this.presentHeightRefresh != null) return await this.presentHeightRefresh;
|
|
19963
|
+
const refresh = this.loadPresentHeight();
|
|
19964
|
+
this.presentHeightRefresh = refresh;
|
|
19965
|
+
try {
|
|
19966
|
+
return await refresh;
|
|
19967
|
+
} finally {
|
|
19968
|
+
if (this.presentHeightRefresh === refresh) this.presentHeightRefresh = void 0;
|
|
19969
|
+
}
|
|
19970
|
+
}
|
|
19971
|
+
async loadPresentHeight() {
|
|
19861
19972
|
for (const [index, bulk] of this.bulkIngestors.entries()) {
|
|
19862
19973
|
const source = this.sourceName("bulk", index, bulk);
|
|
19863
19974
|
try {
|
|
@@ -19865,7 +19976,7 @@ var Chaintracks = class {
|
|
|
19865
19976
|
if (presentHeight != null && Number.isInteger(presentHeight) && presentHeight >= 0) {
|
|
19866
19977
|
this.markSourceSuccess(source, "bulk");
|
|
19867
19978
|
this.lastPresentHeight = presentHeight;
|
|
19868
|
-
this.lastPresentHeightMsecs = now;
|
|
19979
|
+
this.lastPresentHeightMsecs = Date.now();
|
|
19869
19980
|
return presentHeight;
|
|
19870
19981
|
}
|
|
19871
19982
|
} catch (uerr) {
|
|
@@ -19880,7 +19991,7 @@ var Chaintracks = class {
|
|
|
19880
19991
|
const localHeight = Math.max(ranges.bulk.maxHeight, ranges.live.maxHeight);
|
|
19881
19992
|
if (localHeight >= 0) {
|
|
19882
19993
|
this.lastPresentHeight = localHeight;
|
|
19883
|
-
this.lastPresentHeightMsecs = now;
|
|
19994
|
+
this.lastPresentHeightMsecs = Date.now();
|
|
19884
19995
|
return localHeight;
|
|
19885
19996
|
}
|
|
19886
19997
|
} catch (error) {
|
|
@@ -19891,6 +20002,19 @@ var Chaintracks = class {
|
|
|
19891
20002
|
async currentHeight() {
|
|
19892
20003
|
return await this.getPresentHeight();
|
|
19893
20004
|
}
|
|
20005
|
+
/** Returns local process state without locks, storage reads, or network I/O. */
|
|
20006
|
+
getAvailabilitySnapshot() {
|
|
20007
|
+
return {
|
|
20008
|
+
available: this.available,
|
|
20009
|
+
startupError: this.startupError?.message,
|
|
20010
|
+
presentHeight: this.lastPresentHeight >= 0 ? this.lastPresentHeight : void 0,
|
|
20011
|
+
presentHeightUpdatedAt: this.lastPresentHeightMsecs > 0 ? new Date(this.lastPresentHeightMsecs).toISOString() : void 0,
|
|
20012
|
+
presentHeightRefreshInFlight: this.presentHeightRefresh != null,
|
|
20013
|
+
mainLoopHeartbeatAt: this.mainLoopHeartbeatMsecs > 0 ? new Date(this.mainLoopHeartbeatMsecs).toISOString() : void 0,
|
|
20014
|
+
sources: Array.from(this.sourceStatus.values()).map((status) => ({ ...status })),
|
|
20015
|
+
bulkData: this.storage.bulkManager.getStats()
|
|
20016
|
+
};
|
|
20017
|
+
}
|
|
19894
20018
|
async subscribeHeaders(listener) {
|
|
19895
20019
|
const ID = randomBytesBase64(8);
|
|
19896
20020
|
this.callbacks.header[ID] = listener;
|
|
@@ -19955,6 +20079,7 @@ var Chaintracks = class {
|
|
|
19955
20079
|
for (const liveIn of this.liveIngestors) await liveIn.shutdown();
|
|
19956
20080
|
for (const bulkIn of this.bulkIngestors) await bulkIn.shutdown();
|
|
19957
20081
|
await Promise.all(this.promises);
|
|
20082
|
+
await this.storage.bulkManager.destroy();
|
|
19958
20083
|
await this.storage.destroy();
|
|
19959
20084
|
this.available = false;
|
|
19960
20085
|
this.stopMainThread = false;
|
|
@@ -20236,9 +20361,11 @@ var Chaintracks = class {
|
|
|
20236
20361
|
const syncCheckRepeatMsecs = 1800 * 1e3;
|
|
20237
20362
|
while (!this.stopMainThread) try {
|
|
20238
20363
|
const now = Date.now();
|
|
20364
|
+
this.mainLoopHeartbeatMsecs = now;
|
|
20239
20365
|
lastSyncCheck = now;
|
|
20240
20366
|
lastBulkSync = await this.runBulkSyncIfNeeded(now, lastBulkSync, cdnSyncRepeatMsecs);
|
|
20241
20367
|
await this.processLiveHeaderQueue(lastSyncCheck, syncCheckRepeatMsecs);
|
|
20368
|
+
this.mainLoopHeartbeatMsecs = Date.now();
|
|
20242
20369
|
} catch (error_) {
|
|
20243
20370
|
const e = WalletError.fromUnknown(error_);
|
|
20244
20371
|
if (this.available) this.log(`Error occurred during chaintracks main thread processing: ${e.stack || e.message}`);
|
|
@@ -20250,7 +20377,7 @@ var Chaintracks = class {
|
|
|
20250
20377
|
}
|
|
20251
20378
|
/** Returns (potentially updated) lastBulkSync timestamp. */
|
|
20252
20379
|
async runBulkSyncIfNeeded(now, lastBulkSync, cdnSyncRepeatMsecs) {
|
|
20253
|
-
const presentHeight = await this.
|
|
20380
|
+
const presentHeight = await this.refreshPresentHeight();
|
|
20254
20381
|
const before = await this.storage.getAvailableHeightRanges();
|
|
20255
20382
|
let skipBulkSync = !before.live.isEmpty && before.live.maxHeight >= presentHeight - this.addLiveRecursionLimit / 2;
|
|
20256
20383
|
if (skipBulkSync && now - lastBulkSync > cdnSyncRepeatMsecs) skipBulkSync = false;
|
|
@@ -21399,12 +21526,12 @@ var ChaintracksFetch = class {
|
|
|
21399
21526
|
this.maxRetryMsecs = positiveSafeInteger(options.maxRetryMsecs, DEFAULT_MAX_RETRY_MSECS, "maxRetryMsecs");
|
|
21400
21527
|
this.random = options.random ?? Math.random;
|
|
21401
21528
|
}
|
|
21402
|
-
async download(url, maxResponseBytes) {
|
|
21529
|
+
async download(url, maxResponseBytes, options) {
|
|
21403
21530
|
const responseLimit = maxResponseBytes == null ? this.maxResponseBytes : Math.min(this.maxResponseBytes, positiveSafeInteger(maxResponseBytes, this.maxResponseBytes, "maxResponseBytes"));
|
|
21404
21531
|
return await this.requestBytes(url, {
|
|
21405
21532
|
method: "GET",
|
|
21406
21533
|
headers: { Accept: "application/octet-stream" }
|
|
21407
|
-
}, "download", responseLimit);
|
|
21534
|
+
}, "download", responseLimit, options);
|
|
21408
21535
|
}
|
|
21409
21536
|
async fetchJson(url) {
|
|
21410
21537
|
const bytes = await this.requestBytes(url, {
|
|
@@ -21413,8 +21540,9 @@ var ChaintracksFetch = class {
|
|
|
21413
21540
|
}, "fetch JSON", this.maxResponseBytes);
|
|
21414
21541
|
return JSON.parse(new TextDecoder().decode(bytes));
|
|
21415
21542
|
}
|
|
21416
|
-
async requestBytes(url, init, kind, maxResponseBytes) {
|
|
21543
|
+
async requestBytes(url, init, kind, maxResponseBytes, downloadOptions) {
|
|
21417
21544
|
for (let retry = 0;; retry++) {
|
|
21545
|
+
if (retry > 0) await downloadOptions?.beforeRetry?.(retry + 1);
|
|
21418
21546
|
const controller = new AbortController();
|
|
21419
21547
|
const timeout = setTimeout(() => controller.abort(), this.timeoutMsecs);
|
|
21420
21548
|
try {
|
|
@@ -21490,6 +21618,38 @@ var ChaintracksFetch = class {
|
|
|
21490
21618
|
}
|
|
21491
21619
|
};
|
|
21492
21620
|
//#endregion
|
|
21621
|
+
//#region ../src/services/chaintracker/chaintracks/util/InlineBulkFileDataValidator.ts
|
|
21622
|
+
/**
|
|
21623
|
+
* Portable complete-object validator. Node services should normally inject
|
|
21624
|
+
* `NodeBulkFileDataValidator`; browser and mobile consumers retain this
|
|
21625
|
+
* dependency-free fallback.
|
|
21626
|
+
*
|
|
21627
|
+
* @public
|
|
21628
|
+
*/
|
|
21629
|
+
var InlineBulkFileDataValidator = class {
|
|
21630
|
+
async validate(request) {
|
|
21631
|
+
try {
|
|
21632
|
+
const expectedLength = request.count * 80;
|
|
21633
|
+
if (request.data.length !== expectedLength) throw new WERR_INVALID_PARAMETER("file.data", `bulk file ${request.fileName} data length ${request.data.length} does not match expected count ${request.count}`);
|
|
21634
|
+
const fileHash = asString(_bsv_sdk.Hash.sha256(asArray(request.data)), "base64");
|
|
21635
|
+
if (request.fileHash != null && fileHash !== request.fileHash) throw new WERR_INVALID_PARAMETER("fileHash", `a match for retrieved data for ${request.fileName}`);
|
|
21636
|
+
const { lastHeaderHash, lastChainWork } = validateBufferOfHeaders(request.data, request.prevHash, 0, request.count, request.prevChainWork);
|
|
21637
|
+
if (request.lastHash && request.lastHash !== lastHeaderHash) throw new WERR_INVALID_PARAMETER("file.lastHash", `expected ${request.lastHash} but got ${lastHeaderHash}`);
|
|
21638
|
+
if (request.lastChainWork && request.lastChainWork !== lastChainWork) throw new WERR_INVALID_PARAMETER("file.lastChainWork", `expected ${request.lastChainWork} but got ${lastChainWork}`);
|
|
21639
|
+
if (request.firstHeight === 0 && request.chain != null) validateGenesisHeader(request.data, request.chain);
|
|
21640
|
+
return {
|
|
21641
|
+
data: request.data,
|
|
21642
|
+
fileHash,
|
|
21643
|
+
lastHeaderHash,
|
|
21644
|
+
lastChainWork
|
|
21645
|
+
};
|
|
21646
|
+
} catch (error) {
|
|
21647
|
+
if (error instanceof BulkFileDataValidationError) throw error;
|
|
21648
|
+
throw new BulkFileDataValidationError(error instanceof Error ? error.message : String(error), request.data);
|
|
21649
|
+
}
|
|
21650
|
+
}
|
|
21651
|
+
};
|
|
21652
|
+
//#endregion
|
|
21493
21653
|
//#region ../src/services/chaintracker/chaintracks/util/BulkFileDataManager.ts
|
|
21494
21654
|
/**
|
|
21495
21655
|
* Manages bulk file data (typically 8MB chunks of 100,000 headers each).
|
|
@@ -21515,6 +21675,7 @@ var BulkFileDataManager = class BulkFileDataManager {
|
|
|
21515
21675
|
fileHashToIndex = {};
|
|
21516
21676
|
lock = new SingleWriterMultiReaderLock();
|
|
21517
21677
|
inFlightLoads = /* @__PURE__ */ new Map();
|
|
21678
|
+
failedLoads = /* @__PURE__ */ new Map();
|
|
21518
21679
|
storage;
|
|
21519
21680
|
stats = {
|
|
21520
21681
|
memoryHits: 0,
|
|
@@ -21524,7 +21685,8 @@ var BulkFileDataManager = class BulkFileDataManager {
|
|
|
21524
21685
|
persistentCacheRejects: 0,
|
|
21525
21686
|
coalescedLoads: 0,
|
|
21526
21687
|
downloads: 0,
|
|
21527
|
-
downloadedBytes: 0
|
|
21688
|
+
downloadedBytes: 0,
|
|
21689
|
+
loadBackoffs: 0
|
|
21528
21690
|
};
|
|
21529
21691
|
chain;
|
|
21530
21692
|
maxPerFile;
|
|
@@ -21533,6 +21695,8 @@ var BulkFileDataManager = class BulkFileDataManager {
|
|
|
21533
21695
|
fromKnownSourceUrl;
|
|
21534
21696
|
cache;
|
|
21535
21697
|
downloadBudget;
|
|
21698
|
+
validator;
|
|
21699
|
+
failedLoadRetryMsecs;
|
|
21536
21700
|
constructor(options) {
|
|
21537
21701
|
const resolvedOptions = typeof options === "object" ? options : BulkFileDataManager.createDefaultOptions(options);
|
|
21538
21702
|
this.chain = resolvedOptions.chain;
|
|
@@ -21542,10 +21706,17 @@ var BulkFileDataManager = class BulkFileDataManager {
|
|
|
21542
21706
|
this.fetch = resolvedOptions.fetch;
|
|
21543
21707
|
this.cache = resolvedOptions.cache;
|
|
21544
21708
|
this.downloadBudget = resolvedOptions.downloadBudget;
|
|
21709
|
+
this.validator = resolvedOptions.validator ?? new InlineBulkFileDataValidator();
|
|
21710
|
+
this.failedLoadRetryMsecs = resolvedOptions.failedLoadRetryMsecs ?? 30 * 1e3;
|
|
21711
|
+
if (!Number.isSafeInteger(this.failedLoadRetryMsecs) || this.failedLoadRetryMsecs < 0) throw new WERR_INVALID_PARAMETER("failedLoadRetryMsecs", "a non-negative safe integer");
|
|
21545
21712
|
this.deleteBulkFilesNoLock();
|
|
21546
21713
|
}
|
|
21547
21714
|
getStats() {
|
|
21548
|
-
return {
|
|
21715
|
+
return {
|
|
21716
|
+
...this.stats,
|
|
21717
|
+
validation: this.validator.getStats?.(),
|
|
21718
|
+
downloadBudget: this.downloadBudget?.snapshot?.()
|
|
21719
|
+
};
|
|
21549
21720
|
}
|
|
21550
21721
|
async deleteBulkFiles() {
|
|
21551
21722
|
return await this.lock.withWriteLock(async () => this.deleteBulkFilesNoLock());
|
|
@@ -21764,9 +21935,29 @@ var BulkFileDataManager = class BulkFileDataManager {
|
|
|
21764
21935
|
});
|
|
21765
21936
|
}
|
|
21766
21937
|
async getDataFromFile(file, offset, length) {
|
|
21767
|
-
const
|
|
21768
|
-
|
|
21769
|
-
|
|
21938
|
+
const resolved = await this.lock.withReadLock(async () => {
|
|
21939
|
+
const resolved = this.getBfdForHeight(file.firstHeight);
|
|
21940
|
+
if (resolved == null || resolved.count < file.count) throw new WERR_INVALID_PARAMETER("file", `a match for ${file.firstHeight}, ${file.count} in the BulkFileDataManager.`);
|
|
21941
|
+
return {
|
|
21942
|
+
current: resolved,
|
|
21943
|
+
snapshot: snapshotBfd(resolved)
|
|
21944
|
+
};
|
|
21945
|
+
});
|
|
21946
|
+
return await this.getDataFromSnapshot(resolved.current, resolved.snapshot, offset, length);
|
|
21947
|
+
}
|
|
21948
|
+
async getDataFromSnapshot(original, snapshot, offset, length) {
|
|
21949
|
+
const data = await this.getDataFromFileNoLock(snapshot, offset, length);
|
|
21950
|
+
if (snapshot.data != null) await this.lock.withWriteLock(async () => {
|
|
21951
|
+
if (this.bfds.includes(original) && original.fileHash === snapshot.fileHash && original.firstHeight === snapshot.firstHeight && original.count === snapshot.count) {
|
|
21952
|
+
original.data = snapshot.data;
|
|
21953
|
+
original.validated = true;
|
|
21954
|
+
original.lastHash = snapshot.lastHash;
|
|
21955
|
+
original.lastChainWork = snapshot.lastChainWork;
|
|
21956
|
+
original.mru = Date.now();
|
|
21957
|
+
this.ensureMaxRetained();
|
|
21958
|
+
}
|
|
21959
|
+
});
|
|
21960
|
+
return data;
|
|
21770
21961
|
}
|
|
21771
21962
|
async getDataFromFileNoLock(bfd, offset, length) {
|
|
21772
21963
|
const fileLength = bfd.count * 80;
|
|
@@ -21777,15 +21968,19 @@ var BulkFileDataManager = class BulkFileDataManager {
|
|
|
21777
21968
|
return (await this.ensureData(bfd)).slice(offset, offset + length);
|
|
21778
21969
|
}
|
|
21779
21970
|
async findHeaderForHeightOrUndefined(height) {
|
|
21780
|
-
|
|
21971
|
+
const resolved = await this.lock.withReadLock(async () => {
|
|
21781
21972
|
if (!Number.isInteger(height) || height < 0) throw new WERR_INVALID_PARAMETER("height", `a non-negative integer (${height}).`);
|
|
21782
21973
|
const file = this.bfds.find((f) => f.firstHeight <= height && f.firstHeight + f.count > height);
|
|
21783
21974
|
if (file == null) return void 0;
|
|
21784
|
-
|
|
21785
|
-
|
|
21786
|
-
|
|
21787
|
-
|
|
21975
|
+
return {
|
|
21976
|
+
current: file,
|
|
21977
|
+
snapshot: snapshotBfd(file),
|
|
21978
|
+
offset: (height - file.firstHeight) * 80
|
|
21979
|
+
};
|
|
21788
21980
|
});
|
|
21981
|
+
if (resolved == null) return void 0;
|
|
21982
|
+
const data = await this.getDataFromSnapshot(resolved.current, resolved.snapshot, resolved.offset, 80);
|
|
21983
|
+
return data == null ? void 0 : deserializeBlockHeader(data, height, 0);
|
|
21789
21984
|
}
|
|
21790
21985
|
async getFileForHeight(height) {
|
|
21791
21986
|
return await this.lock.withReadLock(async () => {
|
|
@@ -21840,22 +22035,30 @@ var BulkFileDataManager = class BulkFileDataManager {
|
|
|
21840
22035
|
return bfd;
|
|
21841
22036
|
}
|
|
21842
22037
|
async validateBfdData(bfd, expectedFileHash) {
|
|
21843
|
-
await this.ensureData(bfd);
|
|
21844
|
-
|
|
21845
|
-
bfd.fileHash = asString(_bsv_sdk.Hash.sha256(asArray(bfd.data)), "base64");
|
|
21846
|
-
if (expectedFileHash && expectedFileHash !== bfd.fileHash) throw new WERR_INVALID_PARAMETER("file.fileHash", `expected ${expectedFileHash} but got ${bfd.fileHash}`);
|
|
21847
|
-
this.validateBfdHeaders(bfd);
|
|
22038
|
+
const data = await this.ensureData(bfd);
|
|
22039
|
+
bfd.data = await this.validateRetrievedData(bfd, data, expectedFileHash);
|
|
21848
22040
|
}
|
|
21849
|
-
validateBfdHeaders(bfd) {
|
|
22041
|
+
async validateBfdHeaders(bfd, expectedFileHash = bfd.fileHash) {
|
|
21850
22042
|
const pbf = bfd.firstHeight > 0 ? this.getBfdForHeight(bfd.firstHeight - 1) : void 0;
|
|
21851
22043
|
const prevHash = pbf?.lastHash ?? "00".repeat(32);
|
|
21852
22044
|
const prevChainWork = pbf?.lastChainWork ?? "00".repeat(32);
|
|
21853
|
-
const
|
|
21854
|
-
|
|
21855
|
-
|
|
21856
|
-
|
|
21857
|
-
|
|
21858
|
-
|
|
22045
|
+
const result = await this.validator.validate({
|
|
22046
|
+
fileName: bfd.fileName,
|
|
22047
|
+
data: bfd.data,
|
|
22048
|
+
count: bfd.count,
|
|
22049
|
+
fileHash: expectedFileHash,
|
|
22050
|
+
firstHeight: bfd.firstHeight,
|
|
22051
|
+
prevHash,
|
|
22052
|
+
prevChainWork,
|
|
22053
|
+
lastHash: bfd.lastHash,
|
|
22054
|
+
lastChainWork: bfd.lastChainWork,
|
|
22055
|
+
chain: bfd.chain
|
|
22056
|
+
});
|
|
22057
|
+
bfd.data = result.data;
|
|
22058
|
+
bfd.fileHash = result.fileHash;
|
|
22059
|
+
bfd.lastHash = result.lastHeaderHash;
|
|
22060
|
+
bfd.lastChainWork = result.lastChainWork;
|
|
22061
|
+
return result.data;
|
|
21859
22062
|
}
|
|
21860
22063
|
async ReValidate() {
|
|
21861
22064
|
return await this.lock.withReadLock(async () => await this.ReValidateNoLock());
|
|
@@ -22046,62 +22249,96 @@ var BulkFileDataManager = class BulkFileDataManager {
|
|
|
22046
22249
|
this.ensureMaxRetained();
|
|
22047
22250
|
return data;
|
|
22048
22251
|
}
|
|
22252
|
+
const failed = this.failedLoads.get(key);
|
|
22253
|
+
if (failed != null) {
|
|
22254
|
+
if (Date.now() < failed.retryAt) {
|
|
22255
|
+
this.stats.loadBackoffs++;
|
|
22256
|
+
throw failed.error;
|
|
22257
|
+
}
|
|
22258
|
+
this.failedLoads.delete(key);
|
|
22259
|
+
}
|
|
22049
22260
|
const load = this.loadAndValidateData(bfd);
|
|
22050
22261
|
this.inFlightLoads.set(key, load);
|
|
22051
22262
|
try {
|
|
22052
22263
|
const data = await load;
|
|
22053
22264
|
bfd.data = data;
|
|
22054
22265
|
bfd.validated = true;
|
|
22266
|
+
this.failedLoads.delete(key);
|
|
22055
22267
|
bfd.mru = Date.now();
|
|
22056
22268
|
this.ensureMaxRetained();
|
|
22057
22269
|
return data;
|
|
22270
|
+
} catch (error) {
|
|
22271
|
+
const resolved = error instanceof Error ? error : new Error(String(error));
|
|
22272
|
+
this.failedLoads.set(key, {
|
|
22273
|
+
retryAt: Date.now() + this.failedLoadRetryMsecs,
|
|
22274
|
+
error: resolved
|
|
22275
|
+
});
|
|
22276
|
+
throw resolved;
|
|
22058
22277
|
} finally {
|
|
22059
22278
|
if (this.inFlightLoads.get(key) === load) this.inFlightLoads.delete(key);
|
|
22060
22279
|
}
|
|
22061
22280
|
}
|
|
22062
22281
|
async loadAndValidateData(bfd) {
|
|
22063
|
-
|
|
22064
|
-
|
|
22065
|
-
|
|
22066
|
-
|
|
22067
|
-
|
|
22068
|
-
|
|
22069
|
-
}
|
|
22070
|
-
if (this.cache != null) {
|
|
22071
|
-
const cached = await this.cache.get(bfd);
|
|
22072
|
-
if (cached != null) try {
|
|
22073
|
-
this.validateRetrievedData(bfd, cached);
|
|
22074
|
-
this.stats.persistentCacheHits++;
|
|
22075
|
-
return cached;
|
|
22076
|
-
} catch (error) {
|
|
22077
|
-
this.stats.persistentCacheRejects++;
|
|
22078
|
-
await this.cache.delete?.(bfd);
|
|
22079
|
-
this.log(`Rejected corrupt bulk-header cache entry ${bfd.fileName}: ${String(error)}`);
|
|
22080
|
-
}
|
|
22081
|
-
else this.stats.persistentCacheMisses++;
|
|
22082
|
-
}
|
|
22083
|
-
if (this.fetch != null && bfd.sourceUrl) {
|
|
22084
|
-
const expectedBytes = bfd.count * 80;
|
|
22085
|
-
await this.downloadBudget?.consume(expectedBytes);
|
|
22086
|
-
const url = this.fetch.pathJoin(bfd.sourceUrl, bfd.fileName);
|
|
22087
|
-
const downloaded = await this.fetch.download(url, expectedBytes);
|
|
22088
|
-
if (downloaded == null) throw new WERR_INVALID_PARAMETER("sourceUrl", `data not found for sourceUrl ${url}`);
|
|
22089
|
-
this.validateRetrievedData(bfd, downloaded);
|
|
22090
|
-
this.stats.downloads++;
|
|
22091
|
-
this.stats.downloadedBytes += downloaded.length;
|
|
22092
|
-
await this.cache?.set(bfd, downloaded);
|
|
22093
|
-
return downloaded;
|
|
22094
|
-
}
|
|
22282
|
+
const stored = await this.loadFromStorage(bfd);
|
|
22283
|
+
if (stored != null) return stored;
|
|
22284
|
+
const cached = await this.loadFromCache(bfd);
|
|
22285
|
+
if (cached != null) return cached;
|
|
22286
|
+
const downloaded = await this.loadFromRemote(bfd);
|
|
22287
|
+
if (downloaded != null) return downloaded;
|
|
22095
22288
|
throw new WERR_INVALID_PARAMETER("data", `defined. Unable to retrieve data for ${bfd.fileName}`);
|
|
22096
22289
|
}
|
|
22097
|
-
|
|
22098
|
-
if (
|
|
22099
|
-
|
|
22290
|
+
async loadFromStorage(bfd) {
|
|
22291
|
+
if (this.storage == null || !bfd.fileId) return void 0;
|
|
22292
|
+
const stored = await this.storage.getBulkFileData(bfd.fileId);
|
|
22293
|
+
if (stored == null) throw new WERR_INVALID_PARAMETER("fileId", `valid, data not found for fileId ${bfd.fileId}`);
|
|
22294
|
+
const validated = await this.validateRetrievedData(bfd, stored);
|
|
22295
|
+
this.stats.storageHits++;
|
|
22296
|
+
return validated;
|
|
22297
|
+
}
|
|
22298
|
+
async loadFromCache(bfd) {
|
|
22299
|
+
if (this.cache == null) return void 0;
|
|
22300
|
+
const cached = await this.cache.get(bfd);
|
|
22301
|
+
if (cached == null) {
|
|
22302
|
+
this.stats.persistentCacheMisses++;
|
|
22303
|
+
return;
|
|
22304
|
+
}
|
|
22305
|
+
try {
|
|
22306
|
+
const validated = await this.validateRetrievedData(bfd, cached);
|
|
22307
|
+
this.stats.persistentCacheHits++;
|
|
22308
|
+
await this.cache.promoteValidated?.(bfd, validated);
|
|
22309
|
+
return validated;
|
|
22310
|
+
} catch (error) {
|
|
22311
|
+
if (!(error instanceof BulkFileDataValidationError)) throw error;
|
|
22312
|
+
this.stats.persistentCacheRejects++;
|
|
22313
|
+
let rejectedData = error.data;
|
|
22314
|
+
if (!(rejectedData instanceof Uint8Array) && cached.byteLength > 0) rejectedData = cached;
|
|
22315
|
+
await this.cache.quarantine?.(bfd, String(error), rejectedData);
|
|
22316
|
+
this.log(`Rejected corrupt bulk-header cache entry ${bfd.fileName}: ${String(error)}`);
|
|
22317
|
+
return;
|
|
22318
|
+
}
|
|
22319
|
+
}
|
|
22320
|
+
async loadFromRemote(bfd) {
|
|
22321
|
+
if (this.fetch == null || !bfd.sourceUrl) return void 0;
|
|
22322
|
+
const expectedBytes = bfd.count * 80;
|
|
22323
|
+
await this.downloadBudget?.consume(expectedBytes);
|
|
22324
|
+
const url = this.fetch.pathJoin(bfd.sourceUrl, bfd.fileName);
|
|
22325
|
+
const downloaded = await this.fetch.download(url, expectedBytes, { beforeRetry: async () => await this.downloadBudget?.consume(expectedBytes) });
|
|
22326
|
+
if (downloaded == null) throw new WERR_INVALID_PARAMETER("sourceUrl", `data not found for sourceUrl ${url}`);
|
|
22327
|
+
const validated = await this.validateRetrievedData(bfd, downloaded);
|
|
22328
|
+
this.stats.downloads++;
|
|
22329
|
+
this.stats.downloadedBytes += validated.length;
|
|
22330
|
+
await this.cache?.set(bfd, validated);
|
|
22331
|
+
return validated;
|
|
22332
|
+
}
|
|
22333
|
+
async validateRetrievedData(bfd, data, expectedFileHash = bfd.fileHash) {
|
|
22100
22334
|
const candidate = {
|
|
22101
22335
|
...bfd,
|
|
22102
22336
|
data
|
|
22103
22337
|
};
|
|
22104
|
-
this.validateBfdHeaders(candidate);
|
|
22338
|
+
const validated = await this.validateBfdHeaders(candidate, expectedFileHash);
|
|
22339
|
+
bfd.lastHash = candidate.lastHash;
|
|
22340
|
+
bfd.lastChainWork = candidate.lastChainWork;
|
|
22341
|
+
return validated;
|
|
22105
22342
|
}
|
|
22106
22343
|
ensureMaxRetained() {
|
|
22107
22344
|
if (this.maxRetained === void 0) return;
|
|
@@ -22137,17 +22374,24 @@ var BulkFileDataManager = class BulkFileDataManager {
|
|
|
22137
22374
|
i++;
|
|
22138
22375
|
const data = await reader.read();
|
|
22139
22376
|
if (data == null || data.length === 0) break;
|
|
22140
|
-
const
|
|
22141
|
-
|
|
22142
|
-
|
|
22377
|
+
const validated = await this.validator.validate({
|
|
22378
|
+
fileName: toFileName(i),
|
|
22379
|
+
data,
|
|
22380
|
+
count: data.length / 80,
|
|
22381
|
+
firstHeight,
|
|
22382
|
+
prevHash: lastHeaderHash,
|
|
22383
|
+
prevChainWork: lastChainWork,
|
|
22384
|
+
chain
|
|
22385
|
+
});
|
|
22386
|
+
await toFs.writeFile(toPath(i), validated.data);
|
|
22143
22387
|
const file = {
|
|
22144
22388
|
chain,
|
|
22145
|
-
count: data.length / 80,
|
|
22146
|
-
fileHash,
|
|
22389
|
+
count: validated.data.length / 80,
|
|
22390
|
+
fileHash: validated.fileHash,
|
|
22147
22391
|
fileName: toFileName(i),
|
|
22148
22392
|
firstHeight,
|
|
22149
|
-
lastChainWork:
|
|
22150
|
-
lastHash:
|
|
22393
|
+
lastChainWork: validated.lastChainWork,
|
|
22394
|
+
lastHash: validated.lastHeaderHash,
|
|
22151
22395
|
prevChainWork: lastChainWork,
|
|
22152
22396
|
prevHash: lastHeaderHash,
|
|
22153
22397
|
sourceUrl
|
|
@@ -22159,7 +22403,16 @@ var BulkFileDataManager = class BulkFileDataManager {
|
|
|
22159
22403
|
}
|
|
22160
22404
|
await toFs.writeFile(toJsonPath(), asUint8Array(JSON.stringify(toBulkFiles), "utf8"));
|
|
22161
22405
|
}
|
|
22406
|
+
async destroy() {
|
|
22407
|
+
await this.validator.destroy?.();
|
|
22408
|
+
}
|
|
22162
22409
|
};
|
|
22410
|
+
function snapshotBfd(file) {
|
|
22411
|
+
return {
|
|
22412
|
+
...file,
|
|
22413
|
+
data: file.data
|
|
22414
|
+
};
|
|
22415
|
+
}
|
|
22163
22416
|
function selectBulkHeaderFiles(files, chain, maxPerFile) {
|
|
22164
22417
|
const r = [];
|
|
22165
22418
|
let height = 0;
|
|
@@ -27028,7 +27281,8 @@ function createDefaultBulkFileDataManager(params) {
|
|
|
27028
27281
|
maxRetained: params.maxRetained,
|
|
27029
27282
|
fromKnownSourceUrl: params.cdnUrl,
|
|
27030
27283
|
cache: params.sources.bulkFileCache,
|
|
27031
|
-
downloadBudget: params.sources.bulkFileDownloadBudget
|
|
27284
|
+
downloadBudget: params.sources.bulkFileDownloadBudget,
|
|
27285
|
+
validator: params.sources.bulkFileDataValidator
|
|
27032
27286
|
});
|
|
27033
27287
|
}
|
|
27034
27288
|
function createDefaultChaintracksStorageOptions(params) {
|
|
@@ -27553,6 +27807,7 @@ var FixedWindowBulkFileDownloadBudget = class {
|
|
|
27553
27807
|
return {
|
|
27554
27808
|
maxBytes: this.maxBytes,
|
|
27555
27809
|
consumedBytes: this.consumedBytes,
|
|
27810
|
+
remainingBytes: this.maxBytes - this.consumedBytes,
|
|
27556
27811
|
windowStartedAt: this.windowStartedAt,
|
|
27557
27812
|
windowMsecs: this.windowMsecs
|
|
27558
27813
|
};
|
|
@@ -30784,6 +31039,7 @@ var SetupClient = class SetupClient {
|
|
|
30784
31039
|
};
|
|
30785
31040
|
//#endregion
|
|
30786
31041
|
//#region ../src/CWIStyleWalletManager.ts
|
|
31042
|
+
const CWI_COMPONENT = "wallet-toolbox.cwi-manager";
|
|
30787
31043
|
/**
|
|
30788
31044
|
* Number of rounds used in PBKDF2 for deriving password keys.
|
|
30789
31045
|
*/
|
|
@@ -31016,11 +31272,11 @@ var OverlayUMPTokenInteractor = class {
|
|
|
31016
31272
|
* @param hash The 32-byte SHA-256 hash of the presentation key.
|
|
31017
31273
|
* @returns A UMPToken object (including currentOutpoint) if found, otherwise undefined.
|
|
31018
31274
|
*/
|
|
31019
|
-
async findByPresentationKeyHash(hash) {
|
|
31020
|
-
return
|
|
31275
|
+
async findByPresentationKeyHash(hash, options) {
|
|
31276
|
+
return this.findToken({
|
|
31021
31277
|
service: "ls_users",
|
|
31022
31278
|
query: { presentationHash: _bsv_sdk.Utils.toHex(hash) }
|
|
31023
|
-
}, "presentation");
|
|
31279
|
+
}, "presentation", options);
|
|
31024
31280
|
}
|
|
31025
31281
|
/**
|
|
31026
31282
|
* Finds a UMP token on-chain by the given recovery key hash, if it exists.
|
|
@@ -31029,13 +31285,13 @@ var OverlayUMPTokenInteractor = class {
|
|
|
31029
31285
|
* @param hash The 32-byte SHA-256 hash of the recovery key.
|
|
31030
31286
|
* @returns A UMPToken object (including currentOutpoint) if found, otherwise undefined.
|
|
31031
31287
|
*/
|
|
31032
|
-
async findByRecoveryKeyHash(hash) {
|
|
31033
|
-
return
|
|
31288
|
+
async findByRecoveryKeyHash(hash, options) {
|
|
31289
|
+
return this.findToken({
|
|
31034
31290
|
service: "ls_users",
|
|
31035
31291
|
query: { recoveryHash: _bsv_sdk.Utils.toHex(hash) }
|
|
31036
|
-
}, "recovery");
|
|
31292
|
+
}, "recovery", options);
|
|
31037
31293
|
}
|
|
31038
|
-
async findToken(question, lookupKind) {
|
|
31294
|
+
async findToken(question, lookupKind, options) {
|
|
31039
31295
|
const correlationId = this.telemetry.enabled === true ? this.telemetry.createCorrelationId() : void 0;
|
|
31040
31296
|
const startedAt = Date.now();
|
|
31041
31297
|
this.telemetry.capture({
|
|
@@ -31052,34 +31308,39 @@ var OverlayUMPTokenInteractor = class {
|
|
|
31052
31308
|
correlationId
|
|
31053
31309
|
});
|
|
31054
31310
|
} catch (error) {
|
|
31055
|
-
const diagnostics = this.
|
|
31056
|
-
this.
|
|
31311
|
+
const diagnostics = this.emptyStats(correlationId);
|
|
31312
|
+
this.lookupFailed(lookupKind, "lookup-unavailable", diagnostics, startedAt, error);
|
|
31057
31313
|
throw new UMPTokenLookupError("lookup-unavailable", diagnostics, { cause: error });
|
|
31058
31314
|
}
|
|
31059
|
-
const diagnostics = this.
|
|
31315
|
+
const diagnostics = this.diagnosticsFor(resolution);
|
|
31060
31316
|
const tokens = this.parseLookupAnswers(resolution.answer);
|
|
31061
31317
|
const expectedHash = question.query[lookupKind === "presentation" ? "presentationHash" : "recoveryHash"].toLowerCase();
|
|
31062
31318
|
const matchingTokens = tokens.filter((token) => _bsv_sdk.Utils.toHex(lookupKind === "presentation" ? token.presentationHash : token.recoveryHash).toLowerCase() === expectedHash);
|
|
31063
31319
|
if (matchingTokens.length > 1) {
|
|
31064
31320
|
const newest = this.resolveNewestToken(matchingTokens, resolution.answer.outputs);
|
|
31065
31321
|
if (newest != null) {
|
|
31066
|
-
this.
|
|
31322
|
+
this.lookupDone(lookupKind, "found", diagnostics, startedAt, { supersededTokens: matchingTokens.length - 1 });
|
|
31067
31323
|
return newest;
|
|
31068
31324
|
}
|
|
31325
|
+
const pinned = options?.pinnedOutpoint ? matchingTokens.find((token) => token.currentOutpoint === options.pinnedOutpoint) : void 0;
|
|
31326
|
+
if (pinned != null) {
|
|
31327
|
+
this.lookupDone(lookupKind, "found", diagnostics, startedAt);
|
|
31328
|
+
return pinned;
|
|
31329
|
+
}
|
|
31069
31330
|
const reason = "token-ambiguous";
|
|
31070
|
-
this.
|
|
31331
|
+
this.lookupFailed(lookupKind, reason, diagnostics, startedAt);
|
|
31071
31332
|
throw new UMPTokenLookupError(reason, diagnostics);
|
|
31072
31333
|
}
|
|
31073
31334
|
if (matchingTokens.length === 1) {
|
|
31074
|
-
this.
|
|
31335
|
+
this.lookupDone(lookupKind, "found", diagnostics, startedAt);
|
|
31075
31336
|
return matchingTokens[0];
|
|
31076
31337
|
}
|
|
31077
31338
|
if (resolution.progress.emptyHosts > 0) {
|
|
31078
|
-
this.
|
|
31339
|
+
this.lookupDone(lookupKind, "not-found", diagnostics, startedAt);
|
|
31079
31340
|
return;
|
|
31080
31341
|
}
|
|
31081
31342
|
const reason = resolution.answer.outputs.length > 0 ? "token-malformed" : "lookup-incomplete";
|
|
31082
|
-
this.
|
|
31343
|
+
this.lookupFailed(lookupKind, reason, diagnostics, startedAt);
|
|
31083
31344
|
throw new UMPTokenLookupError(reason, diagnostics);
|
|
31084
31345
|
}
|
|
31085
31346
|
/**
|
|
@@ -31110,7 +31371,7 @@ var OverlayUMPTokenInteractor = class {
|
|
|
31110
31371
|
spent: /* @__PURE__ */ new Set()
|
|
31111
31372
|
};
|
|
31112
31373
|
evidence.txs.push(tx);
|
|
31113
|
-
this.
|
|
31374
|
+
this.collectSpends(tx, evidence.spent, /* @__PURE__ */ new Set());
|
|
31114
31375
|
evidenceByCandidate.set(outpoint, evidence);
|
|
31115
31376
|
} catch {}
|
|
31116
31377
|
if (evidenceByCandidate.size !== candidates.size) return void 0;
|
|
@@ -31119,7 +31380,7 @@ var OverlayUMPTokenInteractor = class {
|
|
|
31119
31380
|
const provenContinuations = survivors.filter((outpoint) => {
|
|
31120
31381
|
const evidence = evidenceByCandidate.get(outpoint);
|
|
31121
31382
|
const token = candidates.get(outpoint);
|
|
31122
|
-
return evidence != null && token != null && evidence.txs.some((tx) => this.
|
|
31383
|
+
return evidence != null && token != null && evidence.txs.some((tx) => this.consumesIdentity(tx, token));
|
|
31123
31384
|
});
|
|
31124
31385
|
if (provenContinuations.length !== 1) return void 0;
|
|
31125
31386
|
return candidates.get(provenContinuations[0]);
|
|
@@ -31130,7 +31391,7 @@ var OverlayUMPTokenInteractor = class {
|
|
|
31130
31391
|
* hash — on-chain proof that the candidate is an update of a same-identity
|
|
31131
31392
|
* predecessor rather than an independently minted token.
|
|
31132
31393
|
*/
|
|
31133
|
-
|
|
31394
|
+
consumesIdentity(tx, token) {
|
|
31134
31395
|
const presentationHash = _bsv_sdk.Utils.toHex(token.presentationHash);
|
|
31135
31396
|
const recoveryHash = _bsv_sdk.Utils.toHex(token.recoveryHash);
|
|
31136
31397
|
for (const input of tx.inputs) {
|
|
@@ -31156,7 +31417,7 @@ var OverlayUMPTokenInteractor = class {
|
|
|
31156
31417
|
* renditions are absent from the lookup answer. Iterative so arbitrarily
|
|
31157
31418
|
* long update chains cannot exhaust the call stack.
|
|
31158
31419
|
*/
|
|
31159
|
-
|
|
31420
|
+
collectSpends(tx, spent, visited) {
|
|
31160
31421
|
const pending = [tx];
|
|
31161
31422
|
while (pending.length > 0) {
|
|
31162
31423
|
const current = pending.pop();
|
|
@@ -31171,7 +31432,7 @@ var OverlayUMPTokenInteractor = class {
|
|
|
31171
31432
|
}
|
|
31172
31433
|
}
|
|
31173
31434
|
}
|
|
31174
|
-
|
|
31435
|
+
emptyStats(correlationId) {
|
|
31175
31436
|
return {
|
|
31176
31437
|
hostCount: 0,
|
|
31177
31438
|
completedHosts: 0,
|
|
@@ -31184,7 +31445,7 @@ var OverlayUMPTokenInteractor = class {
|
|
|
31184
31445
|
...correlationId !== void 0 ? { correlationId } : {}
|
|
31185
31446
|
};
|
|
31186
31447
|
}
|
|
31187
|
-
|
|
31448
|
+
diagnosticsFor(resolution) {
|
|
31188
31449
|
const progress = resolution.progress;
|
|
31189
31450
|
return {
|
|
31190
31451
|
hostCount: progress.hostCount,
|
|
@@ -31198,7 +31459,7 @@ var OverlayUMPTokenInteractor = class {
|
|
|
31198
31459
|
...progress.correlationId !== void 0 ? { correlationId: progress.correlationId } : {}
|
|
31199
31460
|
};
|
|
31200
31461
|
}
|
|
31201
|
-
|
|
31462
|
+
lookupAttrs(diagnostics) {
|
|
31202
31463
|
return {
|
|
31203
31464
|
hostCount: diagnostics.hostCount,
|
|
31204
31465
|
completedHosts: diagnostics.completedHosts,
|
|
@@ -31210,7 +31471,7 @@ var OverlayUMPTokenInteractor = class {
|
|
|
31210
31471
|
outputCount: diagnostics.outputCount
|
|
31211
31472
|
};
|
|
31212
31473
|
}
|
|
31213
|
-
|
|
31474
|
+
lookupDone(lookupKind, result, diagnostics, startedAt, extraAttributes = {}) {
|
|
31214
31475
|
this.telemetry.capture({
|
|
31215
31476
|
name: "wallet-toolbox.ump.lookup.completed",
|
|
31216
31477
|
component: "wallet-toolbox.ump",
|
|
@@ -31220,12 +31481,12 @@ var OverlayUMPTokenInteractor = class {
|
|
|
31220
31481
|
lookupKind,
|
|
31221
31482
|
result,
|
|
31222
31483
|
durationMs: Date.now() - startedAt,
|
|
31223
|
-
...this.
|
|
31484
|
+
...this.lookupAttrs(diagnostics),
|
|
31224
31485
|
...extraAttributes
|
|
31225
31486
|
}
|
|
31226
31487
|
});
|
|
31227
31488
|
}
|
|
31228
|
-
|
|
31489
|
+
lookupFailed(lookupKind, reason, diagnostics, startedAt, error) {
|
|
31229
31490
|
this.telemetry.capture({
|
|
31230
31491
|
name: "wallet-toolbox.ump.lookup.indeterminate",
|
|
31231
31492
|
component: "wallet-toolbox.ump",
|
|
@@ -31235,7 +31496,7 @@ var OverlayUMPTokenInteractor = class {
|
|
|
31235
31496
|
lookupKind,
|
|
31236
31497
|
reason,
|
|
31237
31498
|
durationMs: Date.now() - startedAt,
|
|
31238
|
-
...this.
|
|
31499
|
+
...this.lookupAttrs(diagnostics)
|
|
31239
31500
|
},
|
|
31240
31501
|
error
|
|
31241
31502
|
});
|
|
@@ -31253,27 +31514,27 @@ var OverlayUMPTokenInteractor = class {
|
|
|
31253
31514
|
* @returns The outpoint of the newly created UMP token (e.g. "abcd1234...ef.0").
|
|
31254
31515
|
*/
|
|
31255
31516
|
async buildAndSend(wallet, adminOriginator, token, oldTokenToConsume) {
|
|
31256
|
-
const fields = this.
|
|
31517
|
+
const fields = this.tokenFields(token);
|
|
31257
31518
|
const tokenOutput = [{
|
|
31258
31519
|
lockingScript: (await new _bsv_sdk.PushDrop(wallet, adminOriginator).lock(fields, [2, "admin user management token"], "1", "self", true, true)).toHex(),
|
|
31259
31520
|
satoshis: 1,
|
|
31260
31521
|
outputDescription: "New UMP token output"
|
|
31261
31522
|
}];
|
|
31262
|
-
const { resolvedOldToken, inputToken } = await this.
|
|
31523
|
+
const { resolvedOldToken, inputToken } = await this.resolveOldInput(oldTokenToConsume);
|
|
31263
31524
|
const inputs = resolvedOldToken?.currentOutpoint ? [{
|
|
31264
31525
|
outpoint: resolvedOldToken.currentOutpoint,
|
|
31265
31526
|
unlockingScriptLength: 73,
|
|
31266
31527
|
inputDescription: "Consume old UMP token"
|
|
31267
31528
|
}] : [];
|
|
31268
|
-
const createResult = await this.
|
|
31269
|
-
if (!createResult.signableTransaction) return
|
|
31529
|
+
const createResult = await this.createAction(wallet, adminOriginator, inputs, tokenOutput, inputToken, resolvedOldToken);
|
|
31530
|
+
if (!createResult.signableTransaction) return this.broadcastFinal(createResult);
|
|
31270
31531
|
const reference = createResult.signableTransaction.reference;
|
|
31271
31532
|
const partialTx = _bsv_sdk.Transaction.fromBEEF(createResult.signableTransaction.tx);
|
|
31272
|
-
if (resolvedOldToken?.currentOutpoint) return
|
|
31273
|
-
return
|
|
31533
|
+
if (resolvedOldToken?.currentOutpoint) return this.renewToken(wallet, adminOriginator, reference, partialTx);
|
|
31534
|
+
return this.broadcastNew(wallet, adminOriginator, reference);
|
|
31274
31535
|
}
|
|
31275
31536
|
/** Assembles the ordered number[][] fields array from a UMPToken. */
|
|
31276
|
-
|
|
31537
|
+
tokenFields(token) {
|
|
31277
31538
|
const fields = [];
|
|
31278
31539
|
fields[0] = token.passwordSalt;
|
|
31279
31540
|
fields[1] = token.passwordPresentationPrimary;
|
|
@@ -31300,20 +31561,20 @@ var OverlayUMPTokenInteractor = class {
|
|
|
31300
31561
|
return fields;
|
|
31301
31562
|
}
|
|
31302
31563
|
/** Looks up the old token on the overlay; returns undefined resolved token if not found. */
|
|
31303
|
-
async
|
|
31564
|
+
async resolveOldInput(oldTokenToConsume) {
|
|
31304
31565
|
if (!oldTokenToConsume?.currentOutpoint) return {
|
|
31305
31566
|
resolvedOldToken: void 0,
|
|
31306
31567
|
inputToken: void 0
|
|
31307
31568
|
};
|
|
31308
31569
|
const inputToken = await this.findByOutpoint(oldTokenToConsume.currentOutpoint);
|
|
31309
|
-
if (inputToken == null) throw new Error("
|
|
31570
|
+
if (inputToken == null) throw new Error("Previous UMP token unavailable; update refused.");
|
|
31310
31571
|
return {
|
|
31311
31572
|
resolvedOldToken: oldTokenToConsume,
|
|
31312
31573
|
inputToken
|
|
31313
31574
|
};
|
|
31314
31575
|
}
|
|
31315
31576
|
/** Creates the UMP action without dropping a required old-token input on failure. */
|
|
31316
|
-
async
|
|
31577
|
+
async createAction(wallet, adminOriginator, inputs, outputs, inputToken, resolvedOldToken) {
|
|
31317
31578
|
try {
|
|
31318
31579
|
return await wallet.createAction({
|
|
31319
31580
|
description: resolvedOldToken == null ? "Create new UMP token" : "Renew UMP token (consume old, create new)",
|
|
@@ -31340,43 +31601,43 @@ var OverlayUMPTokenInteractor = class {
|
|
|
31340
31601
|
}
|
|
31341
31602
|
}
|
|
31342
31603
|
/** Handles a fully-finalized (no signable tx) createAction result — broadcasts and returns outpoint. */
|
|
31343
|
-
async
|
|
31604
|
+
async broadcastFinal(createResult) {
|
|
31344
31605
|
const finalTxid = createResult.txid || (createResult.tx != null ? _bsv_sdk.Transaction.fromAtomicBEEF(createResult.tx).id("hex") : void 0);
|
|
31345
|
-
if (!finalTxid) throw new Error("
|
|
31346
|
-
if (createResult.tx == null) throw new Error("
|
|
31606
|
+
if (!finalTxid) throw new Error("UMP transaction was not finalized.");
|
|
31607
|
+
if (createResult.tx == null) throw new Error("UMP transaction data missing.");
|
|
31347
31608
|
const broadcastTx = _bsv_sdk.Transaction.fromAtomicBEEF(createResult.tx);
|
|
31348
31609
|
const result = await this.broadcaster.broadcast(broadcastTx);
|
|
31349
|
-
this.
|
|
31610
|
+
this.assertBroadcast(result, "create-finalized");
|
|
31350
31611
|
return `${finalTxid}.0`;
|
|
31351
31612
|
}
|
|
31352
31613
|
/** Signs the old-token input and broadcasts — used during UMP token renewal. */
|
|
31353
|
-
async
|
|
31614
|
+
async renewToken(wallet, adminOriginator, reference, partialTx) {
|
|
31354
31615
|
const unlockingScript = await new _bsv_sdk.PushDrop(wallet, adminOriginator).unlock([2, "admin user management token"], "1", "self").sign(partialTx, 0);
|
|
31355
31616
|
const signResult = await wallet.signAction({
|
|
31356
31617
|
reference,
|
|
31357
31618
|
spends: { 0: { unlockingScript: unlockingScript.toHex() } }
|
|
31358
31619
|
}, adminOriginator);
|
|
31359
31620
|
const finalTxid = signResult.txid || (signResult.tx == null ? "" : _bsv_sdk.Transaction.fromAtomicBEEF(signResult.tx).id("hex"));
|
|
31360
|
-
if (!finalTxid) throw new Error("Could not finalize
|
|
31361
|
-
if (signResult.tx == null) throw new Error("
|
|
31621
|
+
if (!finalTxid) throw new Error("Could not finalize renewed UMP token.");
|
|
31622
|
+
if (signResult.tx == null) throw new Error("Renewed UMP token transaction data missing.");
|
|
31362
31623
|
const result = await this.broadcaster.broadcast(_bsv_sdk.Transaction.fromAtomicBEEF(signResult.tx));
|
|
31363
|
-
this.
|
|
31624
|
+
this.assertBroadcast(result, "renew");
|
|
31364
31625
|
return `${finalTxid}.0`;
|
|
31365
31626
|
}
|
|
31366
31627
|
/** Signs without input spending and broadcasts — used when creating a brand-new UMP token. */
|
|
31367
|
-
async
|
|
31628
|
+
async broadcastNew(wallet, adminOriginator, reference) {
|
|
31368
31629
|
const signResult = await wallet.signAction({
|
|
31369
31630
|
reference,
|
|
31370
31631
|
spends: {}
|
|
31371
31632
|
}, adminOriginator);
|
|
31372
31633
|
const finalTxid = signResult.txid || (signResult.tx == null ? "" : _bsv_sdk.Transaction.fromAtomicBEEF(signResult.tx).id("hex"));
|
|
31373
|
-
if (!finalTxid) throw new Error("
|
|
31374
|
-
if (signResult.tx == null) throw new Error("
|
|
31634
|
+
if (!finalTxid) throw new Error("Could not finalize new UMP token.");
|
|
31635
|
+
if (signResult.tx == null) throw new Error("New UMP token transaction data missing.");
|
|
31375
31636
|
const result = await this.broadcaster.broadcast(_bsv_sdk.Transaction.fromAtomicBEEF(signResult.tx));
|
|
31376
|
-
this.
|
|
31637
|
+
this.assertBroadcast(result, "create");
|
|
31377
31638
|
return `${finalTxid}.0`;
|
|
31378
31639
|
}
|
|
31379
|
-
|
|
31640
|
+
assertBroadcast(result, operation) {
|
|
31380
31641
|
const succeeded = result.status === "success";
|
|
31381
31642
|
this.telemetry.capture({
|
|
31382
31643
|
name: succeeded ? "wallet-toolbox.ump.broadcast.completed" : "wallet-toolbox.ump.broadcast.failed",
|
|
@@ -31405,12 +31666,12 @@ var OverlayUMPTokenInteractor = class {
|
|
|
31405
31666
|
if (answer.type !== "output-list" || answer.outputs.length === 0) return [];
|
|
31406
31667
|
const tokens = [];
|
|
31407
31668
|
for (const output of answer.outputs) {
|
|
31408
|
-
const token = this.
|
|
31669
|
+
const token = this.parseOutput(output);
|
|
31409
31670
|
if (token != null) tokens.push(token);
|
|
31410
31671
|
}
|
|
31411
31672
|
return tokens;
|
|
31412
31673
|
}
|
|
31413
|
-
|
|
31674
|
+
parseOutput(output) {
|
|
31414
31675
|
try {
|
|
31415
31676
|
const tx = _bsv_sdk.Transaction.fromBEEF(output.beef);
|
|
31416
31677
|
const txOutput = tx.outputs[output.outputIndex];
|
|
@@ -31460,14 +31721,14 @@ var OverlayUMPTokenInteractor = class {
|
|
|
31460
31721
|
correlationId
|
|
31461
31722
|
});
|
|
31462
31723
|
} catch (error) {
|
|
31463
|
-
const diagnostics = this.
|
|
31464
|
-
this.
|
|
31724
|
+
const diagnostics = this.emptyStats(correlationId);
|
|
31725
|
+
this.lookupFailed("outpoint", "lookup-unavailable", diagnostics, startedAt, error);
|
|
31465
31726
|
throw new UMPTokenLookupError("lookup-unavailable", diagnostics, { cause: error });
|
|
31466
31727
|
}
|
|
31467
31728
|
if (resolution.answer.outputs.length === 0) {
|
|
31468
31729
|
if (resolution.progress.emptyHosts === 0) {
|
|
31469
|
-
const diagnostics = this.
|
|
31470
|
-
this.
|
|
31730
|
+
const diagnostics = this.diagnosticsFor(resolution);
|
|
31731
|
+
this.lookupFailed("outpoint", "lookup-incomplete", diagnostics, startedAt);
|
|
31471
31732
|
throw new UMPTokenLookupError("lookup-incomplete", diagnostics);
|
|
31472
31733
|
}
|
|
31473
31734
|
return;
|
|
@@ -31609,20 +31870,20 @@ var CWIStyleWalletManager = class {
|
|
|
31609
31870
|
if (this._initSnapshot !== void 0) {
|
|
31610
31871
|
this.telemetry.capture({
|
|
31611
31872
|
name: "wallet-toolbox.snapshot.initialization.started",
|
|
31612
|
-
component:
|
|
31873
|
+
component: CWI_COMPONENT,
|
|
31613
31874
|
severity: "debug"
|
|
31614
31875
|
});
|
|
31615
31876
|
try {
|
|
31616
31877
|
await this.loadSnapshot(this._initSnapshot);
|
|
31617
31878
|
this.telemetry.capture({
|
|
31618
31879
|
name: "wallet-toolbox.snapshot.initialization.completed",
|
|
31619
|
-
component:
|
|
31880
|
+
component: CWI_COMPONENT,
|
|
31620
31881
|
severity: "info"
|
|
31621
31882
|
});
|
|
31622
31883
|
} catch (error) {
|
|
31623
31884
|
this.telemetry.capture({
|
|
31624
31885
|
name: "wallet-toolbox.snapshot.initialization.failed",
|
|
31625
|
-
component:
|
|
31886
|
+
component: CWI_COMPONENT,
|
|
31626
31887
|
severity: "error",
|
|
31627
31888
|
error
|
|
31628
31889
|
});
|
|
@@ -31631,9 +31892,11 @@ var CWIStyleWalletManager = class {
|
|
|
31631
31892
|
}
|
|
31632
31893
|
}
|
|
31633
31894
|
/**
|
|
31634
|
-
* Provides the presentation key.
|
|
31895
|
+
* Provides the presentation key. A WAB operator pin may be supplied by the
|
|
31896
|
+
* authentication manager; normal lookup and lineage resolution always run
|
|
31897
|
+
* before this ambiguity-only fallback.
|
|
31635
31898
|
*/
|
|
31636
|
-
async providePresentationKey(key) {
|
|
31899
|
+
async providePresentationKey(key, lookupOptions) {
|
|
31637
31900
|
if (this.authenticated) throw new Error("User is already authenticated");
|
|
31638
31901
|
if (this.authenticationMode === "recovery-key-and-password") throw new Error("Presentation key is not needed in this mode");
|
|
31639
31902
|
if (key.length !== 32 || key.some((byte) => !Number.isInteger(byte) || byte < 0 || byte > 255)) throw new TypeError("Presentation key must contain exactly 32 bytes.");
|
|
@@ -31642,17 +31905,17 @@ var CWIStyleWalletManager = class {
|
|
|
31642
31905
|
const startedAt = Date.now();
|
|
31643
31906
|
this.telemetry.capture({
|
|
31644
31907
|
name: "wallet-toolbox.authentication.account-lookup.started",
|
|
31645
|
-
component:
|
|
31908
|
+
component: CWI_COMPONENT,
|
|
31646
31909
|
severity: "debug",
|
|
31647
31910
|
attributes: { lookupKind: "presentation" }
|
|
31648
31911
|
});
|
|
31649
31912
|
let token;
|
|
31650
31913
|
try {
|
|
31651
|
-
token = await this.UMPTokenInteractor.findByPresentationKeyHash(hash);
|
|
31914
|
+
token = await this.UMPTokenInteractor.findByPresentationKeyHash(hash, lookupOptions);
|
|
31652
31915
|
} catch (error) {
|
|
31653
31916
|
this.telemetry.capture({
|
|
31654
31917
|
name: "wallet-toolbox.authentication.account-lookup.failed",
|
|
31655
|
-
component:
|
|
31918
|
+
component: CWI_COMPONENT,
|
|
31656
31919
|
severity: "warn",
|
|
31657
31920
|
attributes: {
|
|
31658
31921
|
lookupKind: "presentation",
|
|
@@ -31672,7 +31935,7 @@ var CWIStyleWalletManager = class {
|
|
|
31672
31935
|
}
|
|
31673
31936
|
this.telemetry.capture({
|
|
31674
31937
|
name: "wallet-toolbox.authentication.account-lookup.completed",
|
|
31675
|
-
component:
|
|
31938
|
+
component: CWI_COMPONENT,
|
|
31676
31939
|
severity: "info",
|
|
31677
31940
|
attributes: {
|
|
31678
31941
|
lookupKind: "presentation",
|
|
@@ -31688,11 +31951,11 @@ var CWIStyleWalletManager = class {
|
|
|
31688
31951
|
if (this.authenticated) throw new Error("User is already authenticated");
|
|
31689
31952
|
if (this.authenticationMode === "presentation-key-and-recovery-key") throw new Error("Password is not needed in this mode");
|
|
31690
31953
|
if (this.authenticationFlow === "unknown") throw new Error("Determine account status with a presentation or recovery key before providing a password.");
|
|
31691
|
-
if (this.authenticationFlow === "existing-user") await this.
|
|
31692
|
-
else await this.
|
|
31954
|
+
if (this.authenticationFlow === "existing-user") await this.unlockExisting(password);
|
|
31955
|
+
else await this.createNewUser(password);
|
|
31693
31956
|
}
|
|
31694
31957
|
/** Handles the password step for an existing user — derives keys, sets up infrastructure. */
|
|
31695
|
-
async
|
|
31958
|
+
async unlockExisting(password) {
|
|
31696
31959
|
if (this.currentUMPToken == null) throw new Error("Provide presentation or recovery key first.");
|
|
31697
31960
|
const derivedPasswordKey = await derivePasswordKey(this.currentUMPToken, _bsv_sdk.Utils.toArray(password, "utf8"));
|
|
31698
31961
|
let rootPrimaryKey;
|
|
@@ -31705,11 +31968,11 @@ var CWIStyleWalletManager = class {
|
|
|
31705
31968
|
rootPrimaryKey = new _bsv_sdk.SymmetricKey(this.XOR(this.recoveryKey, derivedPasswordKey)).decrypt(this.currentUMPToken.passwordRecoveryPrimary);
|
|
31706
31969
|
rootPrivilegedKey = new _bsv_sdk.SymmetricKey(this.XOR(rootPrimaryKey, derivedPasswordKey)).decrypt(this.currentUMPToken.passwordPrimaryPrivileged);
|
|
31707
31970
|
}
|
|
31708
|
-
await this.
|
|
31971
|
+
await this.setupRoot(rootPrimaryKey, rootPrivilegedKey);
|
|
31709
31972
|
await this.switchProfile(this.activeProfileId);
|
|
31710
31973
|
}
|
|
31711
31974
|
/** Handles the password step for a new user — generates keys, builds UMP token, publishes on-chain. */
|
|
31712
|
-
async
|
|
31975
|
+
async createNewUser(password) {
|
|
31713
31976
|
if (this.authenticationMode !== "presentation-key-and-password") throw new Error("New-user flow requires presentation key and password mode.");
|
|
31714
31977
|
if (this.presentationKey == null) throw new Error("No presentation key provided for new-user flow.");
|
|
31715
31978
|
const recoveryKey = (0, _bsv_sdk.Random)(32);
|
|
@@ -31748,14 +32011,14 @@ var CWIStyleWalletManager = class {
|
|
|
31748
32011
|
passwordKdf: this.kdfConfig
|
|
31749
32012
|
};
|
|
31750
32013
|
this.currentUMPToken = newToken;
|
|
31751
|
-
await this.
|
|
32014
|
+
await this.setupRoot(rootPrimaryKey);
|
|
31752
32015
|
await this.switchProfile(DEFAULT_PROFILE_ID);
|
|
31753
32016
|
if (this.newWalletFunder != null && this.underlying != null) try {
|
|
31754
32017
|
await this.newWalletFunder(this.presentationKey, this.underlying, this.adminOriginator);
|
|
31755
32018
|
} catch (error) {
|
|
31756
32019
|
this.telemetry.capture({
|
|
31757
32020
|
name: "wallet-toolbox.authentication.new-wallet-funding.failed",
|
|
31758
|
-
component:
|
|
32021
|
+
component: CWI_COMPONENT,
|
|
31759
32022
|
severity: "error",
|
|
31760
32023
|
error: /* @__PURE__ */ new Error("New wallet funding failed.")
|
|
31761
32024
|
});
|
|
@@ -31786,7 +32049,7 @@ var CWIStyleWalletManager = class {
|
|
|
31786
32049
|
const xorKey = this.XOR(this.presentationKey, recoveryKey);
|
|
31787
32050
|
const rootPrimaryKey = new _bsv_sdk.SymmetricKey(xorKey).decrypt(this.currentUMPToken.presentationRecoveryPrimary);
|
|
31788
32051
|
const rootPrivilegedKey = new _bsv_sdk.SymmetricKey(xorKey).decrypt(this.currentUMPToken.presentationRecoveryPrivileged);
|
|
31789
|
-
await this.
|
|
32052
|
+
await this.setupRoot(rootPrimaryKey, rootPrivilegedKey);
|
|
31790
32053
|
await this.switchProfile(this.activeProfileId);
|
|
31791
32054
|
}
|
|
31792
32055
|
}
|
|
@@ -31817,7 +32080,7 @@ var CWIStyleWalletManager = class {
|
|
|
31817
32080
|
if (snapshot.length > 16777216) throw new Error("Snapshot exceeds the maximum supported size.");
|
|
31818
32081
|
this.telemetry.capture({
|
|
31819
32082
|
name: "wallet-toolbox.snapshot.saved",
|
|
31820
|
-
component:
|
|
32083
|
+
component: CWI_COMPONENT,
|
|
31821
32084
|
severity: "info",
|
|
31822
32085
|
attributes: {
|
|
31823
32086
|
formatVersion: 2,
|
|
@@ -31855,12 +32118,12 @@ var CWIStyleWalletManager = class {
|
|
|
31855
32118
|
const tokenBytes = payloadReader.read(tokenLen);
|
|
31856
32119
|
const token = this.deserializeUMPToken(tokenBytes);
|
|
31857
32120
|
this.currentUMPToken = token;
|
|
31858
|
-
await this.
|
|
32121
|
+
await this.setupRoot(rootPrimaryKey);
|
|
31859
32122
|
await this.switchProfile(activeProfileId);
|
|
31860
32123
|
this.authenticationFlow = "existing-user";
|
|
31861
32124
|
this.telemetry.capture({
|
|
31862
32125
|
name: "wallet-toolbox.snapshot.loaded",
|
|
31863
|
-
component:
|
|
32126
|
+
component: CWI_COMPONENT,
|
|
31864
32127
|
severity: "info",
|
|
31865
32128
|
attributes: {
|
|
31866
32129
|
formatVersion: version,
|
|
@@ -31871,7 +32134,7 @@ var CWIStyleWalletManager = class {
|
|
|
31871
32134
|
this.destroy();
|
|
31872
32135
|
this.telemetry.capture({
|
|
31873
32136
|
name: "wallet-toolbox.snapshot.load-failed",
|
|
31874
|
-
component:
|
|
32137
|
+
component: CWI_COMPONENT,
|
|
31875
32138
|
severity: "error",
|
|
31876
32139
|
error
|
|
31877
32140
|
});
|
|
@@ -31888,7 +32151,7 @@ var CWIStyleWalletManager = class {
|
|
|
31888
32151
|
if (refreshed == null) return false;
|
|
31889
32152
|
if (refreshed.currentOutpoint && currentToken.currentOutpoint && refreshed.currentOutpoint === currentToken.currentOutpoint) return false;
|
|
31890
32153
|
this.currentUMPToken = refreshed;
|
|
31891
|
-
await this.
|
|
32154
|
+
await this.setupRoot(this.rootPrimaryKey);
|
|
31892
32155
|
this.saveSnapshot();
|
|
31893
32156
|
return true;
|
|
31894
32157
|
}
|
|
@@ -31947,7 +32210,7 @@ var CWIStyleWalletManager = class {
|
|
|
31947
32210
|
createdAt: Math.floor(Date.now() / 1e3)
|
|
31948
32211
|
};
|
|
31949
32212
|
this.profiles.push(newProfile);
|
|
31950
|
-
await this.
|
|
32213
|
+
await this.updateFactors(this.currentUMPToken.passwordSalt, await this.getFactor("passwordKey"), await this.getFactor("presentationKey"), await this.getFactor("recoveryKey"), this.rootPrimaryKey, await this.getFactor("privilegedKey"), this.profiles);
|
|
31951
32214
|
return newProfile.id;
|
|
31952
32215
|
}
|
|
31953
32216
|
/**
|
|
@@ -31964,7 +32227,7 @@ var CWIStyleWalletManager = class {
|
|
|
31964
32227
|
if (profileIndex === -1) throw new Error("Profile not found.");
|
|
31965
32228
|
this.profiles.splice(profileIndex, 1);
|
|
31966
32229
|
if (this.activeProfileId.every((x, i) => x === profileId[i])) await this.switchProfile(DEFAULT_PROFILE_ID);
|
|
31967
|
-
await this.
|
|
32230
|
+
await this.updateFactors(this.currentUMPToken.passwordSalt, await this.getFactor("passwordKey"), await this.getFactor("presentationKey"), await this.getFactor("recoveryKey"), this.rootPrimaryKey, await this.getFactor("privilegedKey"), this.profiles);
|
|
31968
32231
|
}
|
|
31969
32232
|
/**
|
|
31970
32233
|
* Switches the active profile. This re-derives keys and rebuilds the underlying wallet.
|
|
@@ -32005,14 +32268,14 @@ var CWIStyleWalletManager = class {
|
|
|
32005
32268
|
const recoveryKey = await this.getFactor("recoveryKey");
|
|
32006
32269
|
const presentationKey = await this.getFactor("presentationKey");
|
|
32007
32270
|
const rootPrivilegedKey = await this.getFactor("privilegedKey");
|
|
32008
|
-
await this.
|
|
32271
|
+
await this.updateFactors(passwordSalt, newPasswordKey, presentationKey, recoveryKey, this.rootPrimaryKey, rootPrivilegedKey, this.profiles);
|
|
32009
32272
|
}
|
|
32010
32273
|
/**
|
|
32011
32274
|
* Retrieves the current recovery key. Requires privileged access.
|
|
32012
32275
|
*/
|
|
32013
32276
|
async getRecoveryKey() {
|
|
32014
32277
|
if (!this.authenticated || this.currentUMPToken == null || this.rootPrivilegedKeyManager == null) throw new Error("Not authenticated or missing required data.");
|
|
32015
|
-
return
|
|
32278
|
+
return this.getFactor("recoveryKey");
|
|
32016
32279
|
}
|
|
32017
32280
|
/**
|
|
32018
32281
|
* Changes the user's recovery key. Prompts user to save the new key.
|
|
@@ -32024,7 +32287,7 @@ var CWIStyleWalletManager = class {
|
|
|
32024
32287
|
const rootPrivilegedKey = await this.getFactor("privilegedKey");
|
|
32025
32288
|
const newRecoveryKey = (0, _bsv_sdk.Random)(32);
|
|
32026
32289
|
await this.recoveryKeySaver(newRecoveryKey);
|
|
32027
|
-
await this.
|
|
32290
|
+
await this.updateFactors(this.currentUMPToken.passwordSalt, passwordKey, presentationKey, newRecoveryKey, this.rootPrimaryKey, rootPrivilegedKey, this.profiles);
|
|
32028
32291
|
}
|
|
32029
32292
|
/**
|
|
32030
32293
|
* Changes the user's presentation key.
|
|
@@ -32035,7 +32298,7 @@ var CWIStyleWalletManager = class {
|
|
|
32035
32298
|
const recoveryKey = await this.getFactor("recoveryKey");
|
|
32036
32299
|
const passwordKey = await this.getFactor("passwordKey");
|
|
32037
32300
|
const rootPrivilegedKey = await this.getFactor("privilegedKey");
|
|
32038
|
-
await this.
|
|
32301
|
+
await this.updateFactors(this.currentUMPToken.passwordSalt, passwordKey, newPresentationKey, recoveryKey, this.rootPrimaryKey, rootPrivilegedKey, this.profiles);
|
|
32039
32302
|
if (this.presentationKey != null) this.presentationKey = newPresentationKey;
|
|
32040
32303
|
}
|
|
32041
32304
|
/**
|
|
@@ -32081,7 +32344,7 @@ var CWIStyleWalletManager = class {
|
|
|
32081
32344
|
} catch (error) {
|
|
32082
32345
|
this.telemetry.capture({
|
|
32083
32346
|
name: "wallet-toolbox.authentication.factor-decryption.failed",
|
|
32084
|
-
component:
|
|
32347
|
+
component: CWI_COMPONENT,
|
|
32085
32348
|
severity: "error",
|
|
32086
32349
|
attributes: { factor: factorName },
|
|
32087
32350
|
error
|
|
@@ -32094,7 +32357,7 @@ var CWIStyleWalletManager = class {
|
|
|
32094
32357
|
* Recomputes UMP token fields with updated factors and profiles, then publishes the update.
|
|
32095
32358
|
* This operation requires the *root* privileged key and the *default* profile wallet.
|
|
32096
32359
|
*/
|
|
32097
|
-
async
|
|
32360
|
+
async updateFactors(passwordSalt, passwordKey, presentationKey, recoveryKey, rootPrimaryKey, rootPrivilegedKey, profiles) {
|
|
32098
32361
|
if (!this.authenticated || this.rootPrimaryKey == null || this.currentUMPToken == null) throw new Error("Wallet is not properly authenticated or missing data for update.");
|
|
32099
32362
|
const oldTokenToConsume = { ...this.currentUMPToken };
|
|
32100
32363
|
if (!oldTokenToConsume.currentOutpoint) throw new Error("Cannot update UMP token: Old token has no outpoint.");
|
|
@@ -32145,7 +32408,7 @@ var CWIStyleWalletManager = class {
|
|
|
32145
32408
|
if (!currentActiveId.every((x) => x === 0)) {
|
|
32146
32409
|
this.telemetry.capture({
|
|
32147
32410
|
name: "wallet-toolbox.ump.profile-switch.started",
|
|
32148
|
-
component:
|
|
32411
|
+
component: CWI_COMPONENT,
|
|
32149
32412
|
severity: "debug",
|
|
32150
32413
|
attributes: { reason: "token-update" }
|
|
32151
32414
|
});
|
|
@@ -32161,7 +32424,7 @@ var CWIStyleWalletManager = class {
|
|
|
32161
32424
|
await this.switchProfile(currentActiveId);
|
|
32162
32425
|
this.telemetry.capture({
|
|
32163
32426
|
name: "wallet-toolbox.ump.profile-switch.completed",
|
|
32164
|
-
component:
|
|
32427
|
+
component: CWI_COMPONENT,
|
|
32165
32428
|
severity: "debug",
|
|
32166
32429
|
attributes: { reason: "token-update" }
|
|
32167
32430
|
});
|
|
@@ -32288,9 +32551,9 @@ var CWIStyleWalletManager = class {
|
|
|
32288
32551
|
* @param rootPrimaryKey The user's root primary key (32 bytes).
|
|
32289
32552
|
* @param ephemeralRootPrivilegedKey Optional root privileged key (e.g., during recovery flows).
|
|
32290
32553
|
*/
|
|
32291
|
-
async
|
|
32554
|
+
async setupRoot(rootKey, ephemeralRootPrivilegedKey) {
|
|
32292
32555
|
if (this.currentUMPToken == null) throw new Error("A UMP token must exist before setting up root infrastructure!");
|
|
32293
|
-
this.rootPrimaryKey =
|
|
32556
|
+
this.rootPrimaryKey = rootKey;
|
|
32294
32557
|
let oneTimePrivilegedKey = ephemeralRootPrivilegedKey == null ? void 0 : new _bsv_sdk.PrivateKey(ephemeralRootPrivilegedKey);
|
|
32295
32558
|
this.rootPrivilegedKeyManager = new PrivilegedKeyManager(async (reason) => {
|
|
32296
32559
|
if (oneTimePrivilegedKey != null) {
|
|
@@ -32311,7 +32574,7 @@ var CWIStyleWalletManager = class {
|
|
|
32311
32574
|
});
|
|
32312
32575
|
this.profiles = [];
|
|
32313
32576
|
if (this.currentUMPToken.profilesEncrypted != null && this.currentUMPToken.profilesEncrypted.length > 0) try {
|
|
32314
|
-
const decryptedProfileBytes = new _bsv_sdk.SymmetricKey(
|
|
32577
|
+
const decryptedProfileBytes = new _bsv_sdk.SymmetricKey(rootKey).decrypt(this.currentUMPToken.profilesEncrypted);
|
|
32315
32578
|
const profilesJson = _bsv_sdk.Utils.toUTF8(decryptedProfileBytes);
|
|
32316
32579
|
const profiles = JSON.parse(profilesJson);
|
|
32317
32580
|
if (!Array.isArray(profiles) || profiles.length > 1e3 || !profiles.every(isValidProfile)) throw new Error("Decrypted profile data is invalid or exceeds supported bounds.");
|
|
@@ -32320,7 +32583,7 @@ var CWIStyleWalletManager = class {
|
|
|
32320
32583
|
this.profiles = [];
|
|
32321
32584
|
this.telemetry.capture({
|
|
32322
32585
|
name: "wallet-toolbox.profile.load-failed",
|
|
32323
|
-
component:
|
|
32586
|
+
component: CWI_COMPONENT,
|
|
32324
32587
|
severity: "error",
|
|
32325
32588
|
error
|
|
32326
32589
|
});
|
|
@@ -32329,98 +32592,98 @@ var CWIStyleWalletManager = class {
|
|
|
32329
32592
|
}
|
|
32330
32593
|
this.authenticated = true;
|
|
32331
32594
|
}
|
|
32332
|
-
|
|
32595
|
+
assertReady(originator) {
|
|
32333
32596
|
if (!this.authenticated) throw new Error("User is not authenticated.");
|
|
32334
32597
|
if (this.underlying == null) throw new Error("Underlying wallet for the active profile is not initialized.");
|
|
32335
32598
|
if (originator === this.adminOriginator) throw new Error("External applications are not allowed to use the admin originator.");
|
|
32336
32599
|
}
|
|
32337
32600
|
async getPublicKey(args, originator) {
|
|
32338
|
-
this.
|
|
32339
|
-
return
|
|
32601
|
+
this.assertReady(originator);
|
|
32602
|
+
return this.underlying.getPublicKey(args, originator);
|
|
32340
32603
|
}
|
|
32341
32604
|
async revealCounterpartyKeyLinkage(args, originator) {
|
|
32342
|
-
this.
|
|
32343
|
-
return
|
|
32605
|
+
this.assertReady(originator);
|
|
32606
|
+
return this.underlying.revealCounterpartyKeyLinkage(args, originator);
|
|
32344
32607
|
}
|
|
32345
32608
|
async revealSpecificKeyLinkage(args, originator) {
|
|
32346
|
-
this.
|
|
32347
|
-
return
|
|
32609
|
+
this.assertReady(originator);
|
|
32610
|
+
return this.underlying.revealSpecificKeyLinkage(args, originator);
|
|
32348
32611
|
}
|
|
32349
32612
|
async encrypt(args, originator) {
|
|
32350
|
-
this.
|
|
32351
|
-
return
|
|
32613
|
+
this.assertReady(originator);
|
|
32614
|
+
return this.underlying.encrypt(args, originator);
|
|
32352
32615
|
}
|
|
32353
32616
|
async decrypt(args, originator) {
|
|
32354
|
-
this.
|
|
32355
|
-
return
|
|
32617
|
+
this.assertReady(originator);
|
|
32618
|
+
return this.underlying.decrypt(args, originator);
|
|
32356
32619
|
}
|
|
32357
32620
|
async createHmac(args, originator) {
|
|
32358
|
-
this.
|
|
32359
|
-
return
|
|
32621
|
+
this.assertReady(originator);
|
|
32622
|
+
return this.underlying.createHmac(args, originator);
|
|
32360
32623
|
}
|
|
32361
32624
|
async verifyHmac(args, originator) {
|
|
32362
|
-
this.
|
|
32363
|
-
return
|
|
32625
|
+
this.assertReady(originator);
|
|
32626
|
+
return this.underlying.verifyHmac(args, originator);
|
|
32364
32627
|
}
|
|
32365
32628
|
async createSignature(args, originator) {
|
|
32366
|
-
this.
|
|
32367
|
-
return
|
|
32629
|
+
this.assertReady(originator);
|
|
32630
|
+
return this.underlying.createSignature(args, originator);
|
|
32368
32631
|
}
|
|
32369
32632
|
async verifySignature(args, originator) {
|
|
32370
|
-
this.
|
|
32371
|
-
return
|
|
32633
|
+
this.assertReady(originator);
|
|
32634
|
+
return this.underlying.verifySignature(args, originator);
|
|
32372
32635
|
}
|
|
32373
32636
|
async createAction(args, originator) {
|
|
32374
|
-
this.
|
|
32375
|
-
return
|
|
32637
|
+
this.assertReady(originator);
|
|
32638
|
+
return this.underlying.createAction(args, originator);
|
|
32376
32639
|
}
|
|
32377
32640
|
async signAction(args, originator) {
|
|
32378
|
-
this.
|
|
32379
|
-
return
|
|
32641
|
+
this.assertReady(originator);
|
|
32642
|
+
return this.underlying.signAction(args, originator);
|
|
32380
32643
|
}
|
|
32381
32644
|
async abortAction(args, originator) {
|
|
32382
|
-
this.
|
|
32383
|
-
return
|
|
32645
|
+
this.assertReady(originator);
|
|
32646
|
+
return this.underlying.abortAction(args, originator);
|
|
32384
32647
|
}
|
|
32385
32648
|
async listActions(args, originator) {
|
|
32386
|
-
this.
|
|
32387
|
-
return
|
|
32649
|
+
this.assertReady(originator);
|
|
32650
|
+
return this.underlying.listActions(args, originator);
|
|
32388
32651
|
}
|
|
32389
32652
|
async internalizeAction(args, originator) {
|
|
32390
|
-
this.
|
|
32391
|
-
return
|
|
32653
|
+
this.assertReady(originator);
|
|
32654
|
+
return this.underlying.internalizeAction(args, originator);
|
|
32392
32655
|
}
|
|
32393
32656
|
async listOutputs(args, originator) {
|
|
32394
|
-
this.
|
|
32395
|
-
return
|
|
32657
|
+
this.assertReady(originator);
|
|
32658
|
+
return this.underlying.listOutputs(args, originator);
|
|
32396
32659
|
}
|
|
32397
32660
|
async relinquishOutput(args, originator) {
|
|
32398
|
-
this.
|
|
32399
|
-
return
|
|
32661
|
+
this.assertReady(originator);
|
|
32662
|
+
return this.underlying.relinquishOutput(args, originator);
|
|
32400
32663
|
}
|
|
32401
32664
|
async acquireCertificate(args, originator) {
|
|
32402
|
-
this.
|
|
32403
|
-
return
|
|
32665
|
+
this.assertReady(originator);
|
|
32666
|
+
return this.underlying.acquireCertificate(args, originator);
|
|
32404
32667
|
}
|
|
32405
32668
|
async listCertificates(args, originator) {
|
|
32406
|
-
this.
|
|
32407
|
-
return
|
|
32669
|
+
this.assertReady(originator);
|
|
32670
|
+
return this.underlying.listCertificates(args, originator);
|
|
32408
32671
|
}
|
|
32409
32672
|
async proveCertificate(args, originator) {
|
|
32410
|
-
this.
|
|
32411
|
-
return
|
|
32673
|
+
this.assertReady(originator);
|
|
32674
|
+
return this.underlying.proveCertificate(args, originator);
|
|
32412
32675
|
}
|
|
32413
32676
|
async relinquishCertificate(args, originator) {
|
|
32414
|
-
this.
|
|
32415
|
-
return
|
|
32677
|
+
this.assertReady(originator);
|
|
32678
|
+
return this.underlying.relinquishCertificate(args, originator);
|
|
32416
32679
|
}
|
|
32417
32680
|
async discoverByIdentityKey(args, originator) {
|
|
32418
|
-
this.
|
|
32419
|
-
return
|
|
32681
|
+
this.assertReady(originator);
|
|
32682
|
+
return this.underlying.discoverByIdentityKey(args, originator);
|
|
32420
32683
|
}
|
|
32421
32684
|
async discoverByAttributes(args, originator) {
|
|
32422
|
-
this.
|
|
32423
|
-
return
|
|
32685
|
+
this.assertReady(originator);
|
|
32686
|
+
return this.underlying.discoverByAttributes(args, originator);
|
|
32424
32687
|
}
|
|
32425
32688
|
async isAuthenticated(_, originator) {
|
|
32426
32689
|
if (!this.authenticated) throw new Error("User is not authenticated.");
|
|
@@ -32430,23 +32693,23 @@ var CWIStyleWalletManager = class {
|
|
|
32430
32693
|
async waitForAuthentication(_, originator) {
|
|
32431
32694
|
if (originator === this.adminOriginator) throw new Error("External applications are not allowed to use the admin originator.");
|
|
32432
32695
|
while (!this.authenticated || this.underlying == null) await new Promise((resolve) => setTimeout(resolve, 100));
|
|
32433
|
-
return
|
|
32696
|
+
return this.underlying.waitForAuthentication({}, originator);
|
|
32434
32697
|
}
|
|
32435
32698
|
async getHeight(_, originator) {
|
|
32436
|
-
this.
|
|
32437
|
-
return
|
|
32699
|
+
this.assertReady(originator);
|
|
32700
|
+
return this.underlying.getHeight({}, originator);
|
|
32438
32701
|
}
|
|
32439
32702
|
async getHeaderForHeight(args, originator) {
|
|
32440
|
-
this.
|
|
32441
|
-
return
|
|
32703
|
+
this.assertReady(originator);
|
|
32704
|
+
return this.underlying.getHeaderForHeight(args, originator);
|
|
32442
32705
|
}
|
|
32443
32706
|
async getNetwork(_, originator) {
|
|
32444
|
-
this.
|
|
32445
|
-
return
|
|
32707
|
+
this.assertReady(originator);
|
|
32708
|
+
return this.underlying.getNetwork({}, originator);
|
|
32446
32709
|
}
|
|
32447
32710
|
async getVersion(_, originator) {
|
|
32448
|
-
this.
|
|
32449
|
-
return
|
|
32711
|
+
this.assertReady(originator);
|
|
32712
|
+
return this.underlying.getVersion({}, originator);
|
|
32450
32713
|
}
|
|
32451
32714
|
};
|
|
32452
32715
|
//#endregion
|
|
@@ -32790,6 +33053,8 @@ const DEFAULT_MAX_RESPONSE_BYTES = 1024 * 1024;
|
|
|
32790
33053
|
const MAX_CONFIGURED_TIMEOUT_MS = 12e4;
|
|
32791
33054
|
const MAX_CONFIGURED_REQUEST_BYTES = 10 * 1024 * 1024;
|
|
32792
33055
|
const MAX_CONFIGURED_RESPONSE_BYTES = 10 * 1024 * 1024;
|
|
33056
|
+
const WAB_COMPONENT = "wallet-toolbox.wab-transport";
|
|
33057
|
+
const WAB_REQUEST_EVENT = "wallet-toolbox.wab.request.";
|
|
32793
33058
|
const defaultFetch = typeof globalThis !== "undefined" && typeof globalThis.fetch === "function" ? globalThis.fetch.bind(globalThis) : void 0;
|
|
32794
33059
|
/**
|
|
32795
33060
|
* A privacy-safe WAB transport failure. Response bodies and request payloads
|
|
@@ -32828,8 +33093,8 @@ function normalizeServerUrl(serverUrl) {
|
|
|
32828
33093
|
} catch {
|
|
32829
33094
|
throw new WABClientError("WAB_INVALID_CONFIGURATION", "WAB server URL must be an absolute URL.", false);
|
|
32830
33095
|
}
|
|
32831
|
-
if (parsed.username !== "" || parsed.password !== "" || parsed.search !== "" || parsed.hash !== "") throw new WABClientError("WAB_INVALID_CONFIGURATION", "WAB
|
|
32832
|
-
if (parsed.protocol !== "https:" && !(parsed.protocol === "http:" && isLocalHostname(parsed.hostname))) throw new WABClientError("WAB_INVALID_CONFIGURATION", "WAB
|
|
33096
|
+
if (parsed.username !== "" || parsed.password !== "" || parsed.search !== "" || parsed.hash !== "") throw new WABClientError("WAB_INVALID_CONFIGURATION", "WAB URL cannot include credentials, query, or fragment.", false);
|
|
33097
|
+
if (parsed.protocol !== "https:" && !(parsed.protocol === "http:" && isLocalHostname(parsed.hostname))) throw new WABClientError("WAB_INVALID_CONFIGURATION", "WAB URL requires HTTPS except on localhost.", false);
|
|
32833
33098
|
let pathname = parsed.pathname;
|
|
32834
33099
|
while (pathname.endsWith("/")) pathname = pathname.slice(0, -1);
|
|
32835
33100
|
return {
|
|
@@ -32839,7 +33104,7 @@ function normalizeServerUrl(serverUrl) {
|
|
|
32839
33104
|
}
|
|
32840
33105
|
function normalizePositiveInteger(value, fallback, maximum, name) {
|
|
32841
33106
|
const resolved = value ?? fallback;
|
|
32842
|
-
if (!Number.isInteger(resolved) || resolved <= 0 || resolved > maximum) throw new WABClientError("WAB_INVALID_CONFIGURATION", `${name} must be
|
|
33107
|
+
if (!Number.isInteger(resolved) || resolved <= 0 || resolved > maximum) throw new WABClientError("WAB_INVALID_CONFIGURATION", `${name} must be an integer from 1 to ${maximum}.`, false);
|
|
32843
33108
|
return resolved;
|
|
32844
33109
|
}
|
|
32845
33110
|
function assertSafePath(path) {
|
|
@@ -32864,20 +33129,20 @@ var WABTransport = class {
|
|
|
32864
33129
|
serverUrl;
|
|
32865
33130
|
serverOrigin;
|
|
32866
33131
|
telemetry;
|
|
32867
|
-
|
|
32868
|
-
|
|
32869
|
-
|
|
32870
|
-
|
|
33132
|
+
fetcher;
|
|
33133
|
+
timeout;
|
|
33134
|
+
requestLimit;
|
|
33135
|
+
responseLimit;
|
|
32871
33136
|
constructor(serverUrl, options = {}) {
|
|
32872
33137
|
const normalized = normalizeServerUrl(serverUrl);
|
|
32873
33138
|
this.serverUrl = normalized.baseUrl;
|
|
32874
33139
|
this.serverOrigin = normalized.origin;
|
|
32875
|
-
const
|
|
32876
|
-
if (typeof
|
|
32877
|
-
this.
|
|
32878
|
-
this.
|
|
32879
|
-
this.
|
|
32880
|
-
this.
|
|
33140
|
+
const fetcher = options.fetch ?? defaultFetch;
|
|
33141
|
+
if (typeof fetcher !== "function") throw new WABClientError("WAB_INVALID_CONFIGURATION", "WABClient requires a fetch implementation.", false);
|
|
33142
|
+
this.fetcher = fetcher;
|
|
33143
|
+
this.timeout = normalizePositiveInteger(options.timeoutMs, DEFAULT_TIMEOUT_MS, MAX_CONFIGURED_TIMEOUT_MS, "timeoutMs");
|
|
33144
|
+
this.requestLimit = normalizePositiveInteger(options.maxRequestBytes, DEFAULT_MAX_REQUEST_BYTES, MAX_CONFIGURED_REQUEST_BYTES, "maxRequestBytes");
|
|
33145
|
+
this.responseLimit = normalizePositiveInteger(options.maxResponseBytes, DEFAULT_MAX_RESPONSE_BYTES, MAX_CONFIGURED_RESPONSE_BYTES, "maxResponseBytes");
|
|
32881
33146
|
this.telemetry = new _bsv_sdk.Telemetry(options.telemetry);
|
|
32882
33147
|
}
|
|
32883
33148
|
createCorrelationId() {
|
|
@@ -32885,38 +33150,9 @@ var WABTransport = class {
|
|
|
32885
33150
|
return isSafeCorrelationId(correlationId) ? correlationId : new _bsv_sdk.Telemetry().createCorrelationId();
|
|
32886
33151
|
}
|
|
32887
33152
|
async request(path, options) {
|
|
32888
|
-
const metadata = this.createRequestMetadata(path, options);
|
|
32889
|
-
this.captureRequestStarted(metadata);
|
|
32890
|
-
const body = this.encodeRequestBody(options, metadata);
|
|
32891
|
-
const timeout = this.startRequestTimeout(metadata.errorContext);
|
|
32892
|
-
const response = await this.fetchResponse(metadata, body, timeout);
|
|
32893
|
-
const responseContext = this.createResponseContext(response, metadata);
|
|
32894
|
-
this.assertSuccessfulResponse(response, responseContext, metadata, timeout);
|
|
32895
|
-
const responseText = await this.readResponseText(response, responseContext, metadata, timeout);
|
|
32896
|
-
const parsed = this.parseResponseObject(responseText, response, responseContext, metadata);
|
|
32897
|
-
this.telemetry.capture({
|
|
32898
|
-
name: "wallet-toolbox.wab.request.completed",
|
|
32899
|
-
component: "wallet-toolbox.wab-transport",
|
|
32900
|
-
severity: "info",
|
|
32901
|
-
correlationId: metadata.correlationId,
|
|
32902
|
-
attributes: {
|
|
32903
|
-
operation: metadata.operation,
|
|
32904
|
-
method: metadata.method,
|
|
32905
|
-
route: metadata.path,
|
|
32906
|
-
serverOrigin: this.serverOrigin,
|
|
32907
|
-
status: response.status,
|
|
32908
|
-
endpointMarkerPresent: responseContext.endpointMarkerPresent,
|
|
32909
|
-
responseCorrelationMatched: responseContext.responseCorrelationMatched,
|
|
32910
|
-
responseBytes: new TextEncoder().encode(responseText).byteLength,
|
|
32911
|
-
durationMs: Date.now() - metadata.startedAt
|
|
32912
|
-
}
|
|
32913
|
-
});
|
|
32914
|
-
return parsed;
|
|
32915
|
-
}
|
|
32916
|
-
createRequestMetadata(path, options) {
|
|
32917
33153
|
assertSafePath(path);
|
|
32918
33154
|
const correlationId = options.correlationId != null && isSafeCorrelationId(options.correlationId) ? options.correlationId : this.createCorrelationId();
|
|
32919
|
-
|
|
33155
|
+
const metadata = {
|
|
32920
33156
|
method: options.method ?? "POST",
|
|
32921
33157
|
path,
|
|
32922
33158
|
operation: options.operation,
|
|
@@ -32928,46 +33164,73 @@ var WABTransport = class {
|
|
|
32928
33164
|
route: path
|
|
32929
33165
|
}
|
|
32930
33166
|
};
|
|
32931
|
-
}
|
|
32932
|
-
captureRequestStarted(metadata) {
|
|
32933
33167
|
this.telemetry.capture({
|
|
32934
|
-
name:
|
|
32935
|
-
component:
|
|
33168
|
+
name: `${WAB_REQUEST_EVENT}started`,
|
|
33169
|
+
component: WAB_COMPONENT,
|
|
32936
33170
|
severity: "debug",
|
|
33171
|
+
correlationId,
|
|
33172
|
+
attributes: {
|
|
33173
|
+
operation: metadata.operation,
|
|
33174
|
+
method: metadata.method,
|
|
33175
|
+
route: path,
|
|
33176
|
+
serverOrigin: this.serverOrigin
|
|
33177
|
+
}
|
|
33178
|
+
});
|
|
33179
|
+
const body = this.bodyFor(options, metadata);
|
|
33180
|
+
const timeout = this.startTimer(metadata.errorContext);
|
|
33181
|
+
const response = await this.fetch(metadata, body, timeout);
|
|
33182
|
+
const responseContext = {
|
|
33183
|
+
...metadata.errorContext,
|
|
33184
|
+
endpointMarkerPresent: isWabResponse(response),
|
|
33185
|
+
responseCorrelationMatched: response.headers.get("X-Correlation-ID") === correlationId
|
|
33186
|
+
};
|
|
33187
|
+
this.checkResponse(response, responseContext, metadata, timeout);
|
|
33188
|
+
const responseText = await this.readText(response, responseContext, metadata, timeout);
|
|
33189
|
+
const parsed = this.parse(responseText, response, responseContext, metadata);
|
|
33190
|
+
this.telemetry.capture({
|
|
33191
|
+
name: `${WAB_REQUEST_EVENT}completed`,
|
|
33192
|
+
component: WAB_COMPONENT,
|
|
33193
|
+
severity: "info",
|
|
32937
33194
|
correlationId: metadata.correlationId,
|
|
32938
33195
|
attributes: {
|
|
32939
33196
|
operation: metadata.operation,
|
|
32940
33197
|
method: metadata.method,
|
|
32941
33198
|
route: metadata.path,
|
|
32942
|
-
serverOrigin: this.serverOrigin
|
|
33199
|
+
serverOrigin: this.serverOrigin,
|
|
33200
|
+
status: response.status,
|
|
33201
|
+
endpointMarkerPresent: responseContext.endpointMarkerPresent,
|
|
33202
|
+
responseCorrelationMatched: responseContext.responseCorrelationMatched,
|
|
33203
|
+
responseBytes: new TextEncoder().encode(responseText).byteLength,
|
|
33204
|
+
durationMs: Date.now() - metadata.startedAt
|
|
32943
33205
|
}
|
|
32944
33206
|
});
|
|
33207
|
+
return parsed;
|
|
32945
33208
|
}
|
|
32946
|
-
|
|
33209
|
+
bodyFor(options, metadata) {
|
|
32947
33210
|
let body;
|
|
32948
33211
|
try {
|
|
32949
33212
|
body = options.body === void 0 ? void 0 : JSON.stringify(options.body);
|
|
32950
33213
|
} catch (cause) {
|
|
32951
|
-
const error = new WABClientError("WAB_INVALID_REQUEST", "WAB request
|
|
33214
|
+
const error = new WABClientError("WAB_INVALID_REQUEST", "WAB request encoding failed.", false, void 0, {
|
|
32952
33215
|
...metadata.errorContext,
|
|
32953
33216
|
cause
|
|
32954
33217
|
});
|
|
32955
|
-
this.
|
|
33218
|
+
this.report(metadata, error);
|
|
32956
33219
|
throw error;
|
|
32957
33220
|
}
|
|
32958
33221
|
if (options.body !== void 0 && body === void 0) {
|
|
32959
|
-
const error = new WABClientError("WAB_INVALID_REQUEST", "WAB request
|
|
32960
|
-
this.
|
|
33222
|
+
const error = new WABClientError("WAB_INVALID_REQUEST", "WAB request is not JSON-serializable.", false, void 0, metadata.errorContext);
|
|
33223
|
+
this.report(metadata, error);
|
|
32961
33224
|
throw error;
|
|
32962
33225
|
}
|
|
32963
|
-
if (body != null && new TextEncoder().encode(body).byteLength > this.
|
|
32964
|
-
const error = new WABClientError("WAB_REQUEST_TOO_LARGE", "WAB request
|
|
32965
|
-
this.
|
|
33226
|
+
if (body != null && new TextEncoder().encode(body).byteLength > this.requestLimit) {
|
|
33227
|
+
const error = new WABClientError("WAB_REQUEST_TOO_LARGE", "WAB request exceeds its size limit.", false, void 0, metadata.errorContext);
|
|
33228
|
+
this.report(metadata, error);
|
|
32966
33229
|
throw error;
|
|
32967
33230
|
}
|
|
32968
33231
|
return body;
|
|
32969
33232
|
}
|
|
32970
|
-
|
|
33233
|
+
startTimer(errorContext) {
|
|
32971
33234
|
const timeout = {
|
|
32972
33235
|
controller: new AbortController(),
|
|
32973
33236
|
timedOut: false
|
|
@@ -32977,12 +33240,12 @@ var WABTransport = class {
|
|
|
32977
33240
|
timeout.timedOut = true;
|
|
32978
33241
|
timeout.controller.abort();
|
|
32979
33242
|
reject(new WABClientError("WAB_TIMEOUT", "WAB request timed out.", true, void 0, errorContext));
|
|
32980
|
-
}, this.
|
|
33243
|
+
}, this.timeout);
|
|
32981
33244
|
});
|
|
32982
33245
|
return timeout;
|
|
32983
33246
|
}
|
|
32984
|
-
async
|
|
32985
|
-
const requestPromise = Promise.resolve().then(() => this.
|
|
33247
|
+
async fetch(metadata, body, timeout) {
|
|
33248
|
+
const requestPromise = Promise.resolve().then(() => this.fetcher(`${this.serverUrl}${metadata.path}`, {
|
|
32986
33249
|
method: metadata.method,
|
|
32987
33250
|
headers: {
|
|
32988
33251
|
Accept: "application/json",
|
|
@@ -33006,33 +33269,26 @@ var WABTransport = class {
|
|
|
33006
33269
|
...metadata.errorContext,
|
|
33007
33270
|
cause
|
|
33008
33271
|
});
|
|
33009
|
-
else error = new WABClientError("WAB_NETWORK_ERROR", "WAB request failed before
|
|
33272
|
+
else error = new WABClientError("WAB_NETWORK_ERROR", "WAB request failed before response.", true, void 0, {
|
|
33010
33273
|
...metadata.errorContext,
|
|
33011
33274
|
cause
|
|
33012
33275
|
});
|
|
33013
|
-
this.
|
|
33276
|
+
this.report(metadata, error);
|
|
33014
33277
|
throw error;
|
|
33015
33278
|
}
|
|
33016
33279
|
}
|
|
33017
|
-
|
|
33018
|
-
return {
|
|
33019
|
-
...metadata.errorContext,
|
|
33020
|
-
endpointMarkerPresent: isWabResponse(response),
|
|
33021
|
-
responseCorrelationMatched: response.headers.get("X-Correlation-ID") === metadata.correlationId
|
|
33022
|
-
};
|
|
33023
|
-
}
|
|
33024
|
-
assertSuccessfulResponse(response, responseContext, metadata, timeout) {
|
|
33280
|
+
checkResponse(response, responseContext, metadata, timeout) {
|
|
33025
33281
|
if (response.ok) return;
|
|
33026
33282
|
if (timeout.timer !== void 0) clearTimeout(timeout.timer);
|
|
33027
33283
|
const endpointMismatch = response.status === 404 && responseContext.endpointMarkerPresent !== true;
|
|
33028
|
-
const error = new WABClientError(endpointMismatch ? "WAB_ENDPOINT_MISMATCH" : "WAB_HTTP_ERROR", endpointMismatch ? "
|
|
33029
|
-
this.
|
|
33284
|
+
const error = new WABClientError(endpointMismatch ? "WAB_ENDPOINT_MISMATCH" : "WAB_HTTP_ERROR", endpointMismatch ? "WAB endpoint is incompatible." : `WAB request failed with HTTP status ${response.status}.`, isRetryableStatus(response.status), response.status, responseContext);
|
|
33285
|
+
this.report(metadata, error);
|
|
33030
33286
|
response.body?.cancel().catch(() => {});
|
|
33031
33287
|
throw error;
|
|
33032
33288
|
}
|
|
33033
|
-
async
|
|
33289
|
+
async readText(response, responseContext, metadata, timeout) {
|
|
33034
33290
|
try {
|
|
33035
|
-
return await Promise.race([this.
|
|
33291
|
+
return await Promise.race([this.read(response, responseContext), timeout.promise]);
|
|
33036
33292
|
} catch (cause) {
|
|
33037
33293
|
let error;
|
|
33038
33294
|
if (cause instanceof WABClientError) error = cause;
|
|
@@ -33040,17 +33296,17 @@ var WABTransport = class {
|
|
|
33040
33296
|
...responseContext,
|
|
33041
33297
|
cause
|
|
33042
33298
|
});
|
|
33043
|
-
else error = new WABClientError("WAB_INVALID_RESPONSE", "WAB response
|
|
33299
|
+
else error = new WABClientError("WAB_INVALID_RESPONSE", "WAB response read failed.", true, response.status, {
|
|
33044
33300
|
...responseContext,
|
|
33045
33301
|
cause
|
|
33046
33302
|
});
|
|
33047
|
-
this.
|
|
33303
|
+
this.report(metadata, error);
|
|
33048
33304
|
throw error;
|
|
33049
33305
|
} finally {
|
|
33050
33306
|
if (timeout.timer !== void 0) clearTimeout(timeout.timer);
|
|
33051
33307
|
}
|
|
33052
33308
|
}
|
|
33053
|
-
|
|
33309
|
+
parse(responseText, response, responseContext, metadata) {
|
|
33054
33310
|
let parsed;
|
|
33055
33311
|
try {
|
|
33056
33312
|
parsed = JSON.parse(responseText);
|
|
@@ -33059,37 +33315,32 @@ var WABTransport = class {
|
|
|
33059
33315
|
...responseContext,
|
|
33060
33316
|
cause
|
|
33061
33317
|
});
|
|
33062
|
-
this.
|
|
33318
|
+
this.report(metadata, error);
|
|
33063
33319
|
throw error;
|
|
33064
33320
|
}
|
|
33065
33321
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
33066
33322
|
const error = new WABClientError("WAB_INVALID_RESPONSE", "WAB response must be a JSON object.", true, response.status, responseContext);
|
|
33067
|
-
this.
|
|
33323
|
+
this.report(metadata, error);
|
|
33068
33324
|
throw error;
|
|
33069
33325
|
}
|
|
33070
33326
|
return parsed;
|
|
33071
33327
|
}
|
|
33072
|
-
|
|
33073
|
-
this.captureFailure(metadata.operation, metadata.method, metadata.path, metadata.correlationId, metadata.startedAt, error);
|
|
33074
|
-
}
|
|
33075
|
-
async readBoundedResponse(response, responseContext) {
|
|
33076
|
-
await this.rejectOversizedDeclaredResponse(response, responseContext);
|
|
33077
|
-
const reader = response.body?.getReader();
|
|
33078
|
-
if (reader == null) return await this.readBoundedArrayBuffer(response, responseContext);
|
|
33079
|
-
return await this.readBoundedStream(reader, response, responseContext);
|
|
33080
|
-
}
|
|
33081
|
-
async rejectOversizedDeclaredResponse(response, responseContext) {
|
|
33328
|
+
async read(response, responseContext) {
|
|
33082
33329
|
const contentLength = Number(response.headers.get("content-length"));
|
|
33083
|
-
if (
|
|
33084
|
-
|
|
33085
|
-
|
|
33330
|
+
if (Number.isFinite(contentLength) && contentLength > this.responseLimit) {
|
|
33331
|
+
await this.stopBody(response);
|
|
33332
|
+
throw this.sizeError(response, responseContext);
|
|
33333
|
+
}
|
|
33334
|
+
const reader = response.body?.getReader();
|
|
33335
|
+
if (reader == null) return this.readBuffer(response, responseContext);
|
|
33336
|
+
return this.readStream(reader, response, responseContext);
|
|
33086
33337
|
}
|
|
33087
|
-
async
|
|
33338
|
+
async readBuffer(response, responseContext) {
|
|
33088
33339
|
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
33089
|
-
if (bytes.byteLength > this.
|
|
33340
|
+
if (bytes.byteLength > this.responseLimit) throw this.sizeError(response, responseContext);
|
|
33090
33341
|
return new TextDecoder().decode(bytes);
|
|
33091
33342
|
}
|
|
33092
|
-
async
|
|
33343
|
+
async readStream(reader, response, responseContext) {
|
|
33093
33344
|
const chunks = [];
|
|
33094
33345
|
let total = 0;
|
|
33095
33346
|
while (true) {
|
|
@@ -33097,15 +33348,12 @@ var WABTransport = class {
|
|
|
33097
33348
|
if (done) break;
|
|
33098
33349
|
if (value == null) continue;
|
|
33099
33350
|
total += value.byteLength;
|
|
33100
|
-
if (total > this.
|
|
33101
|
-
await this.
|
|
33102
|
-
throw this.
|
|
33351
|
+
if (total > this.responseLimit) {
|
|
33352
|
+
await this.stopReader(reader);
|
|
33353
|
+
throw this.sizeError(response, responseContext);
|
|
33103
33354
|
}
|
|
33104
33355
|
chunks.push(value);
|
|
33105
33356
|
}
|
|
33106
|
-
return this.decodeChunks(chunks, total);
|
|
33107
|
-
}
|
|
33108
|
-
decodeChunks(chunks, total) {
|
|
33109
33357
|
const bytes = new Uint8Array(total);
|
|
33110
33358
|
let offset = 0;
|
|
33111
33359
|
for (const chunk of chunks) {
|
|
@@ -33114,35 +33362,35 @@ var WABTransport = class {
|
|
|
33114
33362
|
}
|
|
33115
33363
|
return new TextDecoder().decode(bytes);
|
|
33116
33364
|
}
|
|
33117
|
-
|
|
33118
|
-
return new WABClientError("WAB_RESPONSE_TOO_LARGE", "WAB response
|
|
33365
|
+
sizeError(response, responseContext) {
|
|
33366
|
+
return new WABClientError("WAB_RESPONSE_TOO_LARGE", "WAB response exceeds its size limit.", false, response.status, responseContext);
|
|
33119
33367
|
}
|
|
33120
|
-
async
|
|
33368
|
+
async stopBody(response) {
|
|
33121
33369
|
try {
|
|
33122
33370
|
await response.body?.cancel();
|
|
33123
33371
|
} catch {}
|
|
33124
33372
|
}
|
|
33125
|
-
async
|
|
33373
|
+
async stopReader(reader) {
|
|
33126
33374
|
try {
|
|
33127
33375
|
await reader.cancel();
|
|
33128
33376
|
} catch {}
|
|
33129
33377
|
}
|
|
33130
|
-
|
|
33378
|
+
report(metadata, error) {
|
|
33131
33379
|
this.telemetry.capture({
|
|
33132
|
-
name:
|
|
33133
|
-
component:
|
|
33380
|
+
name: `${WAB_REQUEST_EVENT}failed`,
|
|
33381
|
+
component: WAB_COMPONENT,
|
|
33134
33382
|
severity: error.retryable ? "warn" : "error",
|
|
33135
|
-
correlationId,
|
|
33383
|
+
correlationId: metadata.correlationId,
|
|
33136
33384
|
attributes: {
|
|
33137
|
-
operation,
|
|
33138
|
-
method,
|
|
33139
|
-
route: path,
|
|
33385
|
+
operation: metadata.operation,
|
|
33386
|
+
method: metadata.method,
|
|
33387
|
+
route: metadata.path,
|
|
33140
33388
|
serverOrigin: this.serverOrigin,
|
|
33141
33389
|
retryable: error.retryable,
|
|
33142
33390
|
...error.status !== void 0 ? { status: error.status } : {},
|
|
33143
33391
|
...error.endpointMarkerPresent !== void 0 ? { endpointMarkerPresent: error.endpointMarkerPresent } : {},
|
|
33144
33392
|
...error.responseCorrelationMatched !== void 0 ? { responseCorrelationMatched: error.responseCorrelationMatched } : {},
|
|
33145
|
-
durationMs: Date.now() - startedAt
|
|
33393
|
+
durationMs: Date.now() - metadata.startedAt
|
|
33146
33394
|
},
|
|
33147
33395
|
error
|
|
33148
33396
|
});
|
|
@@ -33251,8 +33499,8 @@ var WABClient = class {
|
|
|
33251
33499
|
constructor(serverUrl, options = {}) {
|
|
33252
33500
|
this.transport = new WABTransport(serverUrl, options);
|
|
33253
33501
|
}
|
|
33254
|
-
|
|
33255
|
-
return
|
|
33502
|
+
getInfo() {
|
|
33503
|
+
return this.transport.request("/info", {
|
|
33256
33504
|
method: "GET",
|
|
33257
33505
|
operation: "get-info"
|
|
33258
33506
|
});
|
|
@@ -33262,15 +33510,15 @@ var WABClient = class {
|
|
|
33262
33510
|
}
|
|
33263
33511
|
async startAuthMethod(authMethod, presentationKey, payload, correlationId) {
|
|
33264
33512
|
assertHexIdentifier(presentationKey, "presentationKey");
|
|
33265
|
-
return
|
|
33513
|
+
return authMethod.startAuth(this.transport.serverUrl, presentationKey, payload, this.transport, correlationId);
|
|
33266
33514
|
}
|
|
33267
33515
|
async completeAuthMethod(authMethod, presentationKey, payload, correlationId) {
|
|
33268
33516
|
assertHexIdentifier(presentationKey, "presentationKey");
|
|
33269
|
-
return
|
|
33517
|
+
return authMethod.completeAuth(this.transport.serverUrl, presentationKey, payload, this.transport, correlationId);
|
|
33270
33518
|
}
|
|
33271
33519
|
async listLinkedMethods(presentationKey) {
|
|
33272
33520
|
assertHexIdentifier(presentationKey, "presentationKey");
|
|
33273
|
-
return
|
|
33521
|
+
return this.transport.request("/user/linkedMethods", {
|
|
33274
33522
|
operation: "list-linked-methods",
|
|
33275
33523
|
body: { presentationKey }
|
|
33276
33524
|
});
|
|
@@ -33278,7 +33526,7 @@ var WABClient = class {
|
|
|
33278
33526
|
async unlinkMethod(presentationKey, authMethodId) {
|
|
33279
33527
|
assertHexIdentifier(presentationKey, "presentationKey");
|
|
33280
33528
|
if (!Number.isSafeInteger(authMethodId) || authMethodId <= 0) throw new TypeError("authMethodId must be a positive safe integer.");
|
|
33281
|
-
return
|
|
33529
|
+
return this.transport.request("/user/unlinkMethod", {
|
|
33282
33530
|
operation: "unlink-method",
|
|
33283
33531
|
body: {
|
|
33284
33532
|
presentationKey,
|
|
@@ -33288,14 +33536,14 @@ var WABClient = class {
|
|
|
33288
33536
|
}
|
|
33289
33537
|
async requestFaucet(presentationKey) {
|
|
33290
33538
|
assertHexIdentifier(presentationKey, "presentationKey");
|
|
33291
|
-
return
|
|
33539
|
+
return this.transport.request("/faucet/request", {
|
|
33292
33540
|
operation: "request-faucet",
|
|
33293
33541
|
body: { presentationKey }
|
|
33294
33542
|
});
|
|
33295
33543
|
}
|
|
33296
33544
|
async deleteUser(presentationKey) {
|
|
33297
33545
|
assertHexIdentifier(presentationKey, "presentationKey");
|
|
33298
|
-
return
|
|
33546
|
+
return this.transport.request("/user/delete", {
|
|
33299
33547
|
operation: "delete-user",
|
|
33300
33548
|
body: { presentationKey }
|
|
33301
33549
|
});
|
|
@@ -33304,7 +33552,7 @@ var WABClient = class {
|
|
|
33304
33552
|
assertMethodType(methodType);
|
|
33305
33553
|
assertHexIdentifier(userIdHash, "userIdHash");
|
|
33306
33554
|
const normalizedPayload = normalizeAuthPayload(methodType, payload);
|
|
33307
|
-
return
|
|
33555
|
+
return this.transport.request("/auth/start", {
|
|
33308
33556
|
operation: "start-share-auth",
|
|
33309
33557
|
body: {
|
|
33310
33558
|
methodType,
|
|
@@ -33317,7 +33565,7 @@ var WABClient = class {
|
|
|
33317
33565
|
assertMethodType(methodType);
|
|
33318
33566
|
assertHexIdentifier(userIdHash, "userIdHash");
|
|
33319
33567
|
const normalizedPayload = normalizeAuthPayload(methodType, payload);
|
|
33320
|
-
return
|
|
33568
|
+
return this.transport.request("/share/store", {
|
|
33321
33569
|
operation: "store-share",
|
|
33322
33570
|
body: {
|
|
33323
33571
|
methodType,
|
|
@@ -33331,7 +33579,7 @@ var WABClient = class {
|
|
|
33331
33579
|
assertMethodType(methodType);
|
|
33332
33580
|
assertHexIdentifier(userIdHash, "userIdHash");
|
|
33333
33581
|
const normalizedPayload = normalizeAuthPayload(methodType, payload);
|
|
33334
|
-
return
|
|
33582
|
+
return this.transport.request("/share/retrieve", {
|
|
33335
33583
|
operation: "retrieve-share",
|
|
33336
33584
|
body: {
|
|
33337
33585
|
methodType,
|
|
@@ -33344,7 +33592,7 @@ var WABClient = class {
|
|
|
33344
33592
|
assertMethodType(methodType);
|
|
33345
33593
|
assertHexIdentifier(userIdHash, "userIdHash");
|
|
33346
33594
|
const normalizedPayload = normalizeAuthPayload(methodType, payload);
|
|
33347
|
-
return
|
|
33595
|
+
return this.transport.request("/share/update", {
|
|
33348
33596
|
operation: "update-share",
|
|
33349
33597
|
body: {
|
|
33350
33598
|
methodType,
|
|
@@ -33358,7 +33606,7 @@ var WABClient = class {
|
|
|
33358
33606
|
assertMethodType(methodType);
|
|
33359
33607
|
assertHexIdentifier(userIdHash, "userIdHash");
|
|
33360
33608
|
const normalizedPayload = normalizeAuthPayload(methodType, payload);
|
|
33361
|
-
return
|
|
33609
|
+
return this.transport.request("/share/delete", {
|
|
33362
33610
|
operation: "delete-share-user",
|
|
33363
33611
|
body: {
|
|
33364
33612
|
methodType,
|
|
@@ -33372,9 +33620,13 @@ var WABClient = class {
|
|
|
33372
33620
|
//#region ../src/WalletAuthenticationManager.ts
|
|
33373
33621
|
const DEFAULT_AUTH_SESSION_TTL_MS = 600 * 1e3;
|
|
33374
33622
|
const MAX_AUTH_SESSION_TTL_MS = 3600 * 1e3;
|
|
33623
|
+
const AUTH_COMPONENT = "wallet-toolbox.authentication-manager";
|
|
33624
|
+
const AUTH_EVENT = "wallet-toolbox.authentication.";
|
|
33625
|
+
const EXISTING_USER = "existing-user";
|
|
33626
|
+
const NEW_USER = "new-user";
|
|
33375
33627
|
var WABAccountContinuityError = class extends Error {
|
|
33376
33628
|
code = "WERR_WAB_ACCOUNT_CONTINUITY";
|
|
33377
|
-
constructor(message = "WAB and UMP
|
|
33629
|
+
constructor(message = "WAB and UMP accounts disagree; retry or recover.") {
|
|
33378
33630
|
super(message);
|
|
33379
33631
|
this.name = "WABAccountContinuityError";
|
|
33380
33632
|
}
|
|
@@ -33389,6 +33641,7 @@ var WalletAuthenticationManager = class extends CWIStyleWalletManager {
|
|
|
33389
33641
|
wabClient;
|
|
33390
33642
|
authMethod;
|
|
33391
33643
|
authSession;
|
|
33644
|
+
phoneChangeSession;
|
|
33392
33645
|
authSessionTtlMs;
|
|
33393
33646
|
constructor(...[adminOriginator, walletBuilder, interactor, recoveryKeySaver, passwordRetriever, wabClient, authMethod, stateSnapshot, options = {}]) {
|
|
33394
33647
|
super(adminOriginator, walletBuilder, interactor, recoveryKeySaver, passwordRetriever, async (presentationKey, wallet, adminOriginator) => {
|
|
@@ -33411,7 +33664,7 @@ var WalletAuthenticationManager = class extends CWIStyleWalletManager {
|
|
|
33411
33664
|
description: "Fund wallet",
|
|
33412
33665
|
options: { acceptDelayedBroadcast: false }
|
|
33413
33666
|
}, adminOriginator);
|
|
33414
|
-
if (faucetRedeemTXCreationResult.signableTransaction == null) throw new Error("Faucet redemption
|
|
33667
|
+
if (faucetRedeemTXCreationResult.signableTransaction == null) throw new Error("Faucet redemption was not signable.");
|
|
33415
33668
|
const faucetRedeemTX = _bsv_sdk.Transaction.fromAtomicBEEF(faucetRedeemTXCreationResult.signableTransaction.tx);
|
|
33416
33669
|
const faucetRedemptionPuzzle = new _bsv_sdk.RPuzzle();
|
|
33417
33670
|
const randomRedemptionPrivateKey = _bsv_sdk.PrivateKey.fromRandom();
|
|
@@ -33444,7 +33697,7 @@ var WalletAuthenticationManager = class extends CWIStyleWalletManager {
|
|
|
33444
33697
|
* using the chosen AuthMethodInteractor.
|
|
33445
33698
|
*/
|
|
33446
33699
|
async startAuth(payload) {
|
|
33447
|
-
if (this.authMethod == null) throw new Error("No
|
|
33700
|
+
if (this.authMethod == null) throw new Error("No WAB authentication method selected.");
|
|
33448
33701
|
const authMethod = this.authMethod;
|
|
33449
33702
|
if (this.authenticated) throw new Error("User is already authenticated");
|
|
33450
33703
|
this.cancelAuth();
|
|
@@ -33457,8 +33710,8 @@ var WalletAuthenticationManager = class extends CWIStyleWalletManager {
|
|
|
33457
33710
|
...correlationId !== void 0 ? { correlationId } : {}
|
|
33458
33711
|
};
|
|
33459
33712
|
this.telemetry.capture({
|
|
33460
|
-
name:
|
|
33461
|
-
component:
|
|
33713
|
+
name: `${AUTH_EVENT}wab-start.started`,
|
|
33714
|
+
component: AUTH_COMPONENT,
|
|
33462
33715
|
severity: "debug",
|
|
33463
33716
|
correlationId,
|
|
33464
33717
|
attributes: { methodType: authMethod.methodType }
|
|
@@ -33470,8 +33723,8 @@ var WalletAuthenticationManager = class extends CWIStyleWalletManager {
|
|
|
33470
33723
|
throw new Error(message);
|
|
33471
33724
|
}
|
|
33472
33725
|
this.telemetry.capture({
|
|
33473
|
-
name:
|
|
33474
|
-
component:
|
|
33726
|
+
name: `${AUTH_EVENT}wab-start.completed`,
|
|
33727
|
+
component: AUTH_COMPONENT,
|
|
33475
33728
|
severity: "info",
|
|
33476
33729
|
correlationId,
|
|
33477
33730
|
attributes: { methodType: authMethod.methodType }
|
|
@@ -33479,8 +33732,8 @@ var WalletAuthenticationManager = class extends CWIStyleWalletManager {
|
|
|
33479
33732
|
} catch (error) {
|
|
33480
33733
|
this.cancelAuth();
|
|
33481
33734
|
this.telemetry.capture({
|
|
33482
|
-
name:
|
|
33483
|
-
component:
|
|
33735
|
+
name: `${AUTH_EVENT}wab-start.failed`,
|
|
33736
|
+
component: AUTH_COMPONENT,
|
|
33484
33737
|
severity: "warn",
|
|
33485
33738
|
correlationId,
|
|
33486
33739
|
attributes: { methodType: authMethod.methodType },
|
|
@@ -33493,22 +33746,22 @@ var WalletAuthenticationManager = class extends CWIStyleWalletManager {
|
|
|
33493
33746
|
* Completes the WAB-based flow, retrieving the final presentationKey from WAB if successful.
|
|
33494
33747
|
*/
|
|
33495
33748
|
async completeAuth(payload) {
|
|
33496
|
-
if (this.authMethod == null || this.authSession == null) throw new Error("
|
|
33749
|
+
if (this.authMethod == null || this.authSession == null) throw new Error("Start WAB authentication first.");
|
|
33497
33750
|
const authMethod = this.authMethod;
|
|
33498
33751
|
if (this.authSession.methodType !== authMethod.methodType) {
|
|
33499
33752
|
this.cancelAuth();
|
|
33500
|
-
throw new Error("
|
|
33753
|
+
throw new Error("WAB authentication method changed; restart.");
|
|
33501
33754
|
}
|
|
33502
33755
|
if (Date.now() >= this.authSession.expiresAt) {
|
|
33503
33756
|
this.cancelAuth();
|
|
33504
|
-
throw new Error("
|
|
33757
|
+
throw new Error("WAB authentication expired; restart.");
|
|
33505
33758
|
}
|
|
33506
33759
|
const session = this.authSession;
|
|
33507
33760
|
const result = await this.wabClient.completeAuthMethod(authMethod, session.presentationKey, payload, session.correlationId);
|
|
33508
33761
|
if (result.success !== true || result.presentationKey == null || result.presentationKey.length === 0) {
|
|
33509
33762
|
this.telemetry.capture({
|
|
33510
|
-
name:
|
|
33511
|
-
component:
|
|
33763
|
+
name: `${AUTH_EVENT}wab-complete.rejected`,
|
|
33764
|
+
component: AUTH_COMPONENT,
|
|
33512
33765
|
severity: "warn",
|
|
33513
33766
|
correlationId: session.correlationId,
|
|
33514
33767
|
attributes: { methodType: session.methodType }
|
|
@@ -33523,11 +33776,11 @@ var WalletAuthenticationManager = class extends CWIStyleWalletManager {
|
|
|
33523
33776
|
this.cancelAuth();
|
|
33524
33777
|
const wabAccountStatus = this.inferAccountStatus(result, session.presentationKey);
|
|
33525
33778
|
try {
|
|
33526
|
-
await this.
|
|
33779
|
+
await this.provideWABPresentationKey(result, wabAccountStatus);
|
|
33527
33780
|
} catch (error) {
|
|
33528
33781
|
this.telemetry.capture({
|
|
33529
|
-
name:
|
|
33530
|
-
component:
|
|
33782
|
+
name: `${AUTH_EVENT}ump-continuity.failed`,
|
|
33783
|
+
component: AUTH_COMPONENT,
|
|
33531
33784
|
severity: "warn",
|
|
33532
33785
|
correlationId: session.correlationId,
|
|
33533
33786
|
attributes: {
|
|
@@ -33538,18 +33791,18 @@ var WalletAuthenticationManager = class extends CWIStyleWalletManager {
|
|
|
33538
33791
|
});
|
|
33539
33792
|
throw error;
|
|
33540
33793
|
}
|
|
33541
|
-
if (wabAccountStatus ===
|
|
33794
|
+
if (wabAccountStatus === EXISTING_USER && this.authenticationFlow !== EXISTING_USER) {
|
|
33542
33795
|
super.destroy();
|
|
33543
33796
|
const error = new WABAccountContinuityError();
|
|
33544
33797
|
this.telemetry.capture({
|
|
33545
|
-
name:
|
|
33546
|
-
component:
|
|
33798
|
+
name: `${AUTH_EVENT}account-continuity.mismatch`,
|
|
33799
|
+
component: AUTH_COMPONENT,
|
|
33547
33800
|
severity: "error",
|
|
33548
33801
|
correlationId: session.correlationId,
|
|
33549
33802
|
attributes: {
|
|
33550
33803
|
methodType: session.methodType,
|
|
33551
33804
|
wabAccountStatus,
|
|
33552
|
-
umpAccountStatus:
|
|
33805
|
+
umpAccountStatus: NEW_USER
|
|
33553
33806
|
},
|
|
33554
33807
|
error
|
|
33555
33808
|
});
|
|
@@ -33557,8 +33810,8 @@ var WalletAuthenticationManager = class extends CWIStyleWalletManager {
|
|
|
33557
33810
|
}
|
|
33558
33811
|
const continuity = wabAccountStatus === this.authenticationFlow ? "matched" : "ump-existing";
|
|
33559
33812
|
this.telemetry.capture({
|
|
33560
|
-
name:
|
|
33561
|
-
component:
|
|
33813
|
+
name: `${AUTH_EVENT}completed`,
|
|
33814
|
+
component: AUTH_COMPONENT,
|
|
33562
33815
|
severity: continuity === "matched" ? "info" : "warn",
|
|
33563
33816
|
correlationId: session.correlationId,
|
|
33564
33817
|
attributes: {
|
|
@@ -33572,23 +33825,131 @@ var WalletAuthenticationManager = class extends CWIStyleWalletManager {
|
|
|
33572
33825
|
cancelAuth() {
|
|
33573
33826
|
this.authSession = void 0;
|
|
33574
33827
|
}
|
|
33828
|
+
readPendingPhoneChange(result) {
|
|
33829
|
+
const presentationKey = result.pendingPresentationKey;
|
|
33830
|
+
const changeId = result.pendingPhoneChangeId;
|
|
33831
|
+
if (presentationKey === void 0 && changeId === void 0) return void 0;
|
|
33832
|
+
if (!/^[0-9a-fA-F]{64}$/.test(presentationKey ?? "") || !Number.isSafeInteger(changeId) || changeId <= 0) throw new WABAccountContinuityError("WAB returned invalid pending phone-change data.");
|
|
33833
|
+
return {
|
|
33834
|
+
presentationKey,
|
|
33835
|
+
changeId
|
|
33836
|
+
};
|
|
33837
|
+
}
|
|
33838
|
+
async provideWABPresentationKey(result, wabAccountStatus) {
|
|
33839
|
+
const umpTokenOutpoint = typeof result.umpTokenOutpoint === "string" ? result.umpTokenOutpoint : void 0;
|
|
33840
|
+
const lookupOptions = umpTokenOutpoint == null ? void 0 : { pinnedOutpoint: umpTokenOutpoint };
|
|
33841
|
+
const pending = this.readPendingPhoneChange(result);
|
|
33842
|
+
let usePending = false;
|
|
33843
|
+
try {
|
|
33844
|
+
await this.providePresentationKey(_bsv_sdk.Utils.toArray(result.presentationKey, "hex"), lookupOptions);
|
|
33845
|
+
} catch (error) {
|
|
33846
|
+
if (pending == null) throw error;
|
|
33847
|
+
usePending = true;
|
|
33848
|
+
}
|
|
33849
|
+
if (pending != null && (usePending || wabAccountStatus === EXISTING_USER && this.authenticationFlow !== EXISTING_USER)) {
|
|
33850
|
+
await this.providePresentationKey(_bsv_sdk.Utils.toArray(pending.presentationKey, "hex"), lookupOptions);
|
|
33851
|
+
if (this.authenticationFlow === EXISTING_USER) await this.finalizePendingPhoneChange(result.presentationKey, pending);
|
|
33852
|
+
}
|
|
33853
|
+
}
|
|
33854
|
+
async finalizePendingPhoneChange(currentPresentationKey, pending) {
|
|
33855
|
+
const finalized = await this.phoneChange("finalize", {
|
|
33856
|
+
changeId: pending.changeId,
|
|
33857
|
+
presentationKey: currentPresentationKey,
|
|
33858
|
+
newPresentationKey: pending.presentationKey
|
|
33859
|
+
});
|
|
33860
|
+
if (finalized.success !== true || finalized.changeId !== pending.changeId) throw new WABAccountContinuityError(finalized.message || "WAB could not finalize the pending phone change.");
|
|
33861
|
+
}
|
|
33862
|
+
/**
|
|
33863
|
+
* Starts OTP verification for a replacement phone number. The same number
|
|
33864
|
+
* is valid and intentionally produces a fresh presentation key/hash.
|
|
33865
|
+
*/
|
|
33866
|
+
async startPhoneNumberChange(phoneNumber) {
|
|
33867
|
+
if (!this.authenticated) throw new Error("Not authenticated");
|
|
33868
|
+
const normalizedPhone = phoneNumber.trim();
|
|
33869
|
+
const currentPresentationKey = _bsv_sdk.Utils.toHex(await this.getFactor("presentationKey"));
|
|
33870
|
+
const response = await this.phoneChange("start", {
|
|
33871
|
+
presentationKey: currentPresentationKey,
|
|
33872
|
+
phoneNumber: normalizedPhone
|
|
33873
|
+
});
|
|
33874
|
+
if (response.success !== true) throw new Error(response.message || "Phone change failed");
|
|
33875
|
+
this.phoneChangeSession = {
|
|
33876
|
+
phoneNumber: normalizedPhone,
|
|
33877
|
+
presentationKey: currentPresentationKey
|
|
33878
|
+
};
|
|
33879
|
+
}
|
|
33880
|
+
/**
|
|
33881
|
+
* Completes phone verification and stages the WAB association before
|
|
33882
|
+
* publishing the UMP key rotation. WAB retains both the current and pending
|
|
33883
|
+
* presentation keys until finalization, so either side of an interrupted
|
|
33884
|
+
* transition remains recoverable on the next verified login.
|
|
33885
|
+
*/
|
|
33886
|
+
async completePhoneNumberChange(otp) {
|
|
33887
|
+
const session = this.phoneChangeSession;
|
|
33888
|
+
if (session == null) throw new Error("No phone change");
|
|
33889
|
+
if (session.changeToken == null) {
|
|
33890
|
+
const authorization = await this.phoneChange("complete", {
|
|
33891
|
+
presentationKey: session.presentationKey,
|
|
33892
|
+
phoneNumber: session.phoneNumber,
|
|
33893
|
+
otp: otp.trim()
|
|
33894
|
+
});
|
|
33895
|
+
if (authorization.success !== true) throw new Error(authorization.message || "Phone change failed");
|
|
33896
|
+
if (/^[0-9a-fA-F]{64}$/.test(authorization.pendingPresentationKey ?? "") && Number.isSafeInteger(authorization.pendingPhoneChangeId) && authorization.pendingPhoneChangeId > 0) {
|
|
33897
|
+
session.newKey = _bsv_sdk.Utils.toArray(authorization.pendingPresentationKey, "hex");
|
|
33898
|
+
session.changeId = authorization.pendingPhoneChangeId;
|
|
33899
|
+
} else if (typeof authorization.changeToken === "string" && authorization.changeToken.length > 0) session.changeToken = authorization.changeToken;
|
|
33900
|
+
else throw new Error(authorization.message || "Phone change failed");
|
|
33901
|
+
}
|
|
33902
|
+
session.newKey ??= (0, _bsv_sdk.Random)(32);
|
|
33903
|
+
if (session.changeId == null) {
|
|
33904
|
+
const committed = await this.phoneChange("commit", {
|
|
33905
|
+
changeToken: session.changeToken,
|
|
33906
|
+
presentationKey: session.presentationKey,
|
|
33907
|
+
newPresentationKey: _bsv_sdk.Utils.toHex(session.newKey)
|
|
33908
|
+
});
|
|
33909
|
+
if (committed.success !== true || !Number.isSafeInteger(committed.changeId) || committed.changeId <= 0) throw new Error(committed.message || "Phone change failed");
|
|
33910
|
+
session.changeId = committed.changeId;
|
|
33911
|
+
}
|
|
33912
|
+
const changeId = session.changeId;
|
|
33913
|
+
if (session.umpUpdated !== true) {
|
|
33914
|
+
await this.changePresentationKey(session.newKey);
|
|
33915
|
+
session.umpUpdated = true;
|
|
33916
|
+
}
|
|
33917
|
+
const finalized = await this.phoneChange("finalize", {
|
|
33918
|
+
changeId,
|
|
33919
|
+
presentationKey: session.presentationKey,
|
|
33920
|
+
newPresentationKey: _bsv_sdk.Utils.toHex(session.newKey)
|
|
33921
|
+
});
|
|
33922
|
+
if (finalized.success !== true || finalized.changeId !== changeId) throw new Error(finalized.message || "Phone change failed");
|
|
33923
|
+
this.phoneChangeSession = void 0;
|
|
33924
|
+
return { changeId };
|
|
33925
|
+
}
|
|
33926
|
+
cancelPhoneNumberChange() {
|
|
33927
|
+
this.phoneChangeSession = void 0;
|
|
33928
|
+
}
|
|
33575
33929
|
destroy() {
|
|
33576
33930
|
this.cancelAuth();
|
|
33931
|
+
this.cancelPhoneNumberChange();
|
|
33577
33932
|
super.destroy();
|
|
33578
33933
|
}
|
|
33934
|
+
phoneChange(phase, body) {
|
|
33935
|
+
return this.wabClient.transport.request(`/auth/phone-change/${phase}`, {
|
|
33936
|
+
operation: "phone-change",
|
|
33937
|
+
body
|
|
33938
|
+
});
|
|
33939
|
+
}
|
|
33579
33940
|
inferAccountStatus(result, temporaryPresentationKey) {
|
|
33580
33941
|
if (result.presentationKey == null) throw new WABAccountContinuityError("WAB did not return a presentation key.");
|
|
33581
33942
|
const keyMatchesTemporary = this.constantTimeHexEqual(result.presentationKey, temporaryPresentationKey);
|
|
33582
33943
|
const rawAccountStatus = result.accountStatus;
|
|
33583
|
-
if (rawAccountStatus !== void 0 && rawAccountStatus !==
|
|
33944
|
+
if (rawAccountStatus !== void 0 && rawAccountStatus !== NEW_USER && rawAccountStatus !== EXISTING_USER) throw new WABAccountContinuityError("WAB returned an invalid account status.");
|
|
33584
33945
|
const rawExistingUser = result.existingUser;
|
|
33585
|
-
if (rawExistingUser !== void 0 && typeof rawExistingUser !== "boolean") throw new WABAccountContinuityError("WAB returned
|
|
33586
|
-
if (rawAccountStatus !== void 0 && rawExistingUser !== void 0 && rawAccountStatus ===
|
|
33946
|
+
if (rawExistingUser !== void 0 && typeof rawExistingUser !== "boolean") throw new WABAccountContinuityError("WAB returned invalid existing-user data.");
|
|
33947
|
+
if (rawAccountStatus !== void 0 && rawExistingUser !== void 0 && rawAccountStatus === EXISTING_USER !== rawExistingUser) throw new WABAccountContinuityError("WAB returned conflicting account status.");
|
|
33587
33948
|
let compatibilityStatus;
|
|
33588
|
-
if (typeof rawExistingUser === "boolean") compatibilityStatus = rawExistingUser ?
|
|
33949
|
+
if (typeof rawExistingUser === "boolean") compatibilityStatus = rawExistingUser ? EXISTING_USER : NEW_USER;
|
|
33589
33950
|
const explicitStatus = rawAccountStatus ?? compatibilityStatus;
|
|
33590
|
-
if (explicitStatus ===
|
|
33591
|
-
return explicitStatus ?? (keyMatchesTemporary ?
|
|
33951
|
+
if (explicitStatus === NEW_USER && !keyMatchesTemporary || explicitStatus === EXISTING_USER && keyMatchesTemporary) throw new WABAccountContinuityError("WAB returned conflicting account status.");
|
|
33952
|
+
return explicitStatus ?? (keyMatchesTemporary ? NEW_USER : EXISTING_USER);
|
|
33592
33953
|
}
|
|
33593
33954
|
constantTimeHexEqual(left, right) {
|
|
33594
33955
|
if (left.length !== right.length) return false;
|
|
@@ -36828,6 +37189,7 @@ exports.BHServiceClient = BHServiceClient;
|
|
|
36828
37189
|
exports.BRC153_REFERENCE_PREFIX = BRC153_REFERENCE_PREFIX;
|
|
36829
37190
|
exports.BulkFileDataManager = BulkFileDataManager;
|
|
36830
37191
|
exports.BulkFileDataReader = BulkFileDataReader;
|
|
37192
|
+
exports.BulkFileDataValidationError = BulkFileDataValidationError;
|
|
36831
37193
|
exports.BulkFilesReader = BulkFilesReader;
|
|
36832
37194
|
exports.BulkFilesReaderFs = BulkFilesReaderFs;
|
|
36833
37195
|
exports.BulkFilesReaderStorage = BulkFilesReaderStorage;
|
|
@@ -36876,6 +37238,7 @@ exports.EntityUser = EntityUser;
|
|
|
36876
37238
|
exports.FixedWindowBulkFileDownloadBudget = FixedWindowBulkFileDownloadBudget;
|
|
36877
37239
|
exports.GoChaintracksServiceClient = GoChaintracksServiceClient;
|
|
36878
37240
|
exports.HeightRange = HeightRange;
|
|
37241
|
+
exports.InlineBulkFileDataValidator = InlineBulkFileDataValidator;
|
|
36879
37242
|
exports.KDF_MAX_HASH_LENGTH = KDF_MAX_HASH_LENGTH;
|
|
36880
37243
|
exports.LEGACY_MANAGED_CHANGE_MINIMUM_SATOSHIS = LEGACY_MANAGED_CHANGE_MINIMUM_SATOSHIS;
|
|
36881
37244
|
exports.LiveIngestorBase = LiveIngestorBase;
|