edge-core-js
Advanced tools
Comparing version 1.1.0 to 1.2.0
# edge-core-js | ||
## v1.2.0 (2023-06-15) | ||
- added: Add an `EdgeCurrencyWallet.streamTransactions` method. | ||
- deprecated: Pagination options for `getTransactions`. Use `streamTransactions` if you need pagination. | ||
- fixed: Add the correct URI to `changeUsername`, so it works. | ||
- fixed: Send a 'transactionsChanged' event when editing metadata. | ||
## v1.1.0 (2023-06-08) | ||
@@ -4,0 +11,0 @@ |
@@ -1,2 +0,2 @@ | ||
import { shareData } from 'yaob' | ||
import { close, shareData } from 'yaob' | ||
@@ -9,2 +9,17 @@ | ||
/** | ||
@@ -86,1 +101,45 @@ * Client-side EdgeAccount methods. | ||
shareData({ fixUsername }) | ||
/** | ||
* Synchronously constructs a transaction stream. | ||
* This method creates a secret internal stream, | ||
* which differs slightly from the AsyncIterableIterator protocol | ||
* because of YAOB limitations. | ||
* It then wraps the internal stream object with the correct API. | ||
*/ | ||
export function streamTransactions( | ||
opts = {} | ||
) { | ||
let stream | ||
let streamClosed = false | ||
const out = { | ||
next: async () => { | ||
if (stream == null) stream = await this.$internalStreamTransactions(opts) | ||
if (!streamClosed) { | ||
const out = await stream.next() | ||
if (!out.done) return out | ||
close(stream) | ||
streamClosed = true | ||
} | ||
return { done: true, value: undefined } | ||
}, | ||
/** | ||
* Closes the iterator early if the client doesn't want all the results. | ||
* This is necessary to prevent memory leaks over the bridge. | ||
*/ | ||
return: async () => { | ||
if (stream != null && !streamClosed) { | ||
close(stream) | ||
streamClosed = true | ||
} | ||
return { done: true, value: undefined } | ||
}, | ||
[Symbol.asyncIterator]: () => out | ||
} | ||
return out | ||
} | ||
shareData({ streamTransactions }, 'CurrencyWalletSync') |
@@ -5,2 +5,7 @@ function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }import { add, div, lte, mul, sub } from 'biggystring' | ||
import { | ||
streamTransactions | ||
} from '../../../client-side' | ||
import { upgradeCurrencyCode } from '../../../types/type-helpers' | ||
@@ -31,2 +36,3 @@ | ||
import { mergeDeeply } from '../../../util/util' | ||
@@ -226,6 +232,19 @@ import { makeMetaTokens } from '../../account/custom-tokens' | ||
}, | ||
async getTransactions( | ||
opts = {} | ||
async $internalStreamTransactions( | ||
opts | ||
) { | ||
const { currencyCode = plugin.currencyInfo.currencyCode } = opts | ||
const { | ||
afterDate, | ||
batchSize = 10, | ||
beforeDate, | ||
firstBatchSize = batchSize, | ||
searchString, | ||
tokenId, | ||
unfilteredStart | ||
} = opts | ||
const { currencyCode } = | ||
tokenId == null | ||
? this.currencyInfo | ||
: this.currencyConfig.allTokens[tokenId] | ||
@@ -259,49 +278,98 @@ // Load transactions from the engine if necessary: | ||
} = state | ||
const { startIndex = 0, startEntries = sortedTxidHashes.length } = opts | ||
// Iterate over the sorted transactions until we have enough output: | ||
const out = [] | ||
for ( | ||
let i = startIndex, lastFile = startIndex; | ||
i < sortedTxidHashes.length && out.length < startEntries; | ||
++i | ||
) { | ||
// Load a batch of files if we need that: | ||
if (i >= lastFile) { | ||
const loadEnd = lastFile + startEntries | ||
const missingTxIdHashes = sortedTxidHashes | ||
.slice(lastFile, loadEnd) | ||
.filter(txidHash => files[txidHash] == null) | ||
const missingFiles = await loadTxFiles(input, missingTxIdHashes) | ||
Object.assign(files, missingFiles) | ||
lastFile = loadEnd | ||
} | ||
let i = _nullishCoalesce(unfilteredStart, () => ( 0)) | ||
let isFirst = true | ||
let lastFile = 0 | ||
return bridgifyObject({ | ||
async next() { | ||
const thisBatchSize = isFirst ? firstBatchSize : batchSize | ||
const out = [] | ||
while (i < sortedTxidHashes.length && out.length < thisBatchSize) { | ||
// Load a batch of files if we need that: | ||
if (i >= lastFile) { | ||
const missingTxIdHashes = sortedTxidHashes | ||
.slice(lastFile, lastFile + thisBatchSize) | ||
.filter(txidHash => files[txidHash] == null) | ||
const missingFiles = await loadTxFiles(input, missingTxIdHashes) | ||
Object.assign(files, missingFiles) | ||
lastFile = lastFile + thisBatchSize | ||
} | ||
const txidHash = sortedTxidHashes[i] | ||
const file = files[txidHash] | ||
const txid = _nullishCoalesce(_optionalChain([file, 'optionalAccess', _ => _.txid]), () => ( _optionalChain([txidHashes, 'access', _2 => _2[txidHash], 'optionalAccess', _3 => _3.txid]))) | ||
if (txid == null) continue | ||
const tx = txs[txid] | ||
const txidHash = sortedTxidHashes[i++] | ||
const file = files[txidHash] | ||
const txid = _nullishCoalesce(_optionalChain([file, 'optionalAccess', _ => _.txid]), () => ( _optionalChain([txidHashes, 'access', _2 => _2[txidHash], 'optionalAccess', _3 => _3.txid]))) | ||
if (txid == null) continue | ||
const tx = txs[txid] | ||
// Filter transactions based on the currency code: | ||
if ( | ||
tx == null || | ||
(tx.nativeAmount[currencyCode] == null && | ||
tx.networkFee[currencyCode] == null) | ||
) { | ||
continue | ||
// Filter transactions based on the currency code: | ||
if ( | ||
tx == null || | ||
(tx.nativeAmount[currencyCode] == null && | ||
tx.networkFee[currencyCode] == null) | ||
) { | ||
continue | ||
} | ||
// Filter transactions based on search criteria: | ||
const edgeTx = combineTxWithFile(input, tx, file, currencyCode) | ||
if (!searchStringFilter(ai, edgeTx, searchString)) continue | ||
if (!dateFilter(edgeTx, afterDate, beforeDate)) continue | ||
// Preserve the `getTransactions` hack if needed: | ||
if (unfilteredStart != null) { | ||
edgeTx.otherParams = { ...edgeTx.otherParams, unfilteredIndex: i } | ||
} | ||
out.push(edgeTx) | ||
} | ||
isFirst = false | ||
return { done: out.length === 0, value: out } | ||
} | ||
}) | ||
}, | ||
// add this tx / file to the output | ||
const edgeTx = combineTxWithFile(input, tx, file, currencyCode) | ||
if (searchStringFilter(ai, edgeTx, opts) && dateFilter(edgeTx, opts)) { | ||
out.push({ | ||
...edgeTx, | ||
otherParams: { ...edgeTx.otherParams, unfilteredIndex: i } | ||
}) | ||
async getTransactions( | ||
opts = {} | ||
) { | ||
const { | ||
currencyCode = plugin.currencyInfo.currencyCode, | ||
endDate: beforeDate, | ||
startDate: afterDate, | ||
searchString, | ||
startEntries, | ||
startIndex = 0 | ||
} = opts | ||
const { tokenId } = upgradeCurrencyCode({ | ||
allTokens: input.props.state.accounts[accountId].allTokens[pluginId], | ||
currencyInfo: plugin.currencyInfo, | ||
currencyCode | ||
}) | ||
const stream = await out.$internalStreamTransactions({ | ||
unfilteredStart: startIndex, | ||
batchSize: startEntries, | ||
afterDate, | ||
beforeDate, | ||
searchString, | ||
tokenId | ||
}) | ||
// We have no length, so iterate to get everything: | ||
if (startEntries == null) { | ||
const out = [] | ||
while (true) { | ||
const batch = await stream.next() | ||
if (batch.done) return out | ||
out.push(...batch.value) | ||
} | ||
} | ||
return out | ||
// We have a length, so the first batch is all we need: | ||
const batch = await stream.next() | ||
return batch.value | ||
}, | ||
streamTransactions, | ||
// Addresses: | ||
@@ -308,0 +376,0 @@ async getReceiveAddress( |
@@ -279,3 +279,3 @@ function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }import { lt } from 'biggystring' | ||
const reduxTx = mergeTx(tx, defaultCurrency, reduxTxs[txid]) | ||
if (compare(reduxTx, reduxTxs[txid])) continue | ||
if (compare(reduxTx, reduxTxs[txid]) && tx.metadata == null) continue | ||
@@ -282,0 +282,0 @@ // Ensure the transaction has metadata: |
export function dateFilter( | ||
tx, | ||
opts | ||
afterDate = new Date(0), | ||
beforeDate = new Date() | ||
) { | ||
const { startDate = new Date(0), endDate = new Date() } = opts | ||
return ( | ||
tx.date * 1000 >= startDate.valueOf() && tx.date * 1000 < endDate.valueOf() | ||
tx.date * 1000 >= afterDate.valueOf() && | ||
tx.date * 1000 < beforeDate.valueOf() | ||
) | ||
@@ -21,79 +18,74 @@ } | ||
tx, | ||
opts | ||
searchString | ||
) { | ||
const currencyState = ai.props.state.currency | ||
const { searchString } = opts | ||
if (searchString != null && searchString !== '') { | ||
// Sanitize search string | ||
let cleanString = searchString | ||
.toLowerCase() | ||
.replace('.', '') | ||
.replace(',', '') | ||
// Remove leading zeroes | ||
for (let i = 0; i < cleanString.length; i++) { | ||
if (cleanString[i] !== '0') { | ||
cleanString = cleanString.substring(i) | ||
break | ||
} | ||
if (searchString == null || searchString === '') return true | ||
// Sanitize search string | ||
let cleanString = searchString.toLowerCase().replace('.', '').replace(',', '') | ||
// Remove leading zeroes | ||
for (let i = 0; i < cleanString.length; i++) { | ||
if (cleanString[i] !== '0') { | ||
cleanString = cleanString.substring(i) | ||
break | ||
} | ||
} | ||
function checkNullTypeAndIndex(value) { | ||
if ( | ||
value == null || | ||
(typeof value !== 'string' && typeof value !== 'number') | ||
) | ||
return false | ||
if ( | ||
!value | ||
.toString() | ||
.toLowerCase() | ||
.replace('.', '') | ||
.replace(',', '') | ||
.includes(cleanString) | ||
) | ||
return false | ||
function checkNullTypeAndIndex(value) { | ||
if ( | ||
value == null || | ||
(typeof value !== 'string' && typeof value !== 'number') | ||
) | ||
return false | ||
if ( | ||
!value | ||
.toString() | ||
.toLowerCase() | ||
.replace('.', '') | ||
.replace(',', '') | ||
.includes(cleanString) | ||
) | ||
return false | ||
return true | ||
} | ||
if (checkNullTypeAndIndex(tx.nativeAmount)) return true | ||
if (tx.metadata != null) { | ||
const { | ||
category = '', | ||
name = '', | ||
notes = '', | ||
exchangeAmount = {} | ||
} = tx.metadata | ||
const txCurrencyWalletState = | ||
tx.walletId != null ? currencyState.wallets[tx.walletId] : undefined | ||
if ( | ||
checkNullTypeAndIndex(category) || | ||
checkNullTypeAndIndex(name) || | ||
checkNullTypeAndIndex(notes) || | ||
(txCurrencyWalletState != null && | ||
checkNullTypeAndIndex(exchangeAmount[txCurrencyWalletState.fiat])) | ||
) | ||
return true | ||
} | ||
if (checkNullTypeAndIndex(tx.nativeAmount)) return true | ||
if (tx.metadata != null) { | ||
const { | ||
category = '', | ||
name = '', | ||
notes = '', | ||
exchangeAmount = {} | ||
} = tx.metadata | ||
const txCurrencyWalletState = | ||
tx.walletId != null ? currencyState.wallets[tx.walletId] : undefined | ||
if ( | ||
checkNullTypeAndIndex(category) || | ||
checkNullTypeAndIndex(name) || | ||
checkNullTypeAndIndex(notes) || | ||
(txCurrencyWalletState != null && | ||
checkNullTypeAndIndex(exchangeAmount[txCurrencyWalletState.fiat])) | ||
) | ||
} | ||
if (tx.swapData != null) { | ||
const { displayName = '', pluginId = '' } = tx.swapData.plugin | ||
if (checkNullTypeAndIndex(displayName) || checkNullTypeAndIndex(pluginId)) | ||
return true | ||
} | ||
if (tx.spendTargets != null) { | ||
for (const target of tx.spendTargets) { | ||
const { publicAddress = '', memo = '' } = target | ||
if (checkNullTypeAndIndex(publicAddress) || checkNullTypeAndIndex(memo)) | ||
return true | ||
} | ||
if (tx.swapData != null) { | ||
const { displayName = '', pluginId = '' } = tx.swapData.plugin | ||
if (checkNullTypeAndIndex(displayName) || checkNullTypeAndIndex(pluginId)) | ||
return true | ||
} | ||
if (tx.ourReceiveAddresses.length > 0) { | ||
for (const address of tx.ourReceiveAddresses) { | ||
if (checkNullTypeAndIndex(address)) return true | ||
} | ||
if (tx.spendTargets != null) { | ||
for (const target of tx.spendTargets) { | ||
const { publicAddress = '', memo = '' } = target | ||
if (checkNullTypeAndIndex(publicAddress) || checkNullTypeAndIndex(memo)) | ||
return true | ||
} | ||
} | ||
if (tx.ourReceiveAddresses.length > 0) { | ||
for (const address of tx.ourReceiveAddresses) { | ||
if (checkNullTypeAndIndex(address)) return true | ||
} | ||
} | ||
if (checkNullTypeAndIndex(tx.txid)) return true | ||
return false | ||
} | ||
return true | ||
if (checkNullTypeAndIndex(tx.txid)) return true | ||
return false | ||
} |
@@ -89,3 +89,3 @@ function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }import { uncleaner } from 'cleaners' | ||
}, | ||
serverPath: '', | ||
serverPath: '/v2/login/username', | ||
stash: { | ||
@@ -92,0 +92,0 @@ ..._optionalChain([passwordKit, 'optionalAccess', _9 => _9.stash]), |
@@ -573,2 +573,3 @@ // @flow | ||
* which can be used as the starting point for subsequent queries. | ||
* @deprecated Use `streamTransactions` if you need pagination. | ||
*/ | ||
@@ -581,2 +582,3 @@ startIndex?: number; | ||
* We may return less than this when we reach the end. | ||
* @deprecated Use `streamTransactions` if you need pagination. | ||
*/ | ||
@@ -592,2 +594,28 @@ startEntries?: number; | ||
export type EdgeStreamTransactionOptions = { | ||
/** | ||
* The number of entries to return in each batch. | ||
* Defaults to something reasonable, like 10. | ||
*/ | ||
batchSize?: number; | ||
/** | ||
* The number entries to return on the first batch. | ||
* Defaults to `batchSize`. | ||
*/ | ||
firstBatchSize?: number; | ||
/** Only return transactions newer than this date */ | ||
afterDate?: Date; | ||
/** Only return transactions older than this date */ | ||
beforeDate?: Date; | ||
/** Only return transactions matching this string */ | ||
searchString?: string; | ||
/** The token to query, or undefined for the main currency */ | ||
tokenId?: string; | ||
} | ||
export type EdgeGetReceiveAddressOptions = EdgeCurrencyCodeOptions & { | ||
@@ -940,2 +968,5 @@ forceIndex?: number; | ||
) => Promise<EdgeTransaction[]>; | ||
+streamTransactions: ( | ||
opts?: EdgeStreamTransactionOptions | ||
) => AsyncGenerator<EdgeTransaction[]>; | ||
@@ -942,0 +973,0 @@ // Addresses: |
@@ -1674,1 +1674,32 @@ | ||
{ | ||
"name": "edge-core-js", | ||
"version": "1.1.0", | ||
"version": "1.2.0", | ||
"description": "Edge account & wallet management library", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
@@ -571,2 +571,3 @@ import type { Disklet } from 'disklet' | ||
* which can be used as the starting point for subsequent queries. | ||
* @deprecated Use `streamTransactions` if you need pagination. | ||
*/ | ||
@@ -579,2 +580,3 @@ startIndex?: number | ||
* We may return less than this when we reach the end. | ||
* @deprecated Use `streamTransactions` if you need pagination. | ||
*/ | ||
@@ -590,2 +592,28 @@ startEntries?: number | ||
export interface EdgeStreamTransactionOptions { | ||
/** | ||
* The number of entries to return in each batch. | ||
* Defaults to something reasonable, like 10. | ||
*/ | ||
batchSize?: number | ||
/** | ||
* The number entries to return on the first batch. | ||
* Defaults to `batchSize`. | ||
*/ | ||
firstBatchSize?: number | ||
/** Only return transactions newer than this date */ | ||
afterDate?: Date | ||
/** Only return transactions older than this date */ | ||
beforeDate?: Date | ||
/** Only return transactions matching this string */ | ||
searchString?: string | ||
/** The token to query, or undefined for the main currency */ | ||
tokenId?: string | ||
} | ||
export type EdgeGetReceiveAddressOptions = EdgeCurrencyCodeOptions & { | ||
@@ -934,2 +962,5 @@ forceIndex?: number | ||
) => Promise<EdgeTransaction[]> | ||
readonly streamTransactions: ( | ||
opts?: EdgeStreamTransactionOptions | ||
) => AsyncIterableIterator<EdgeTransaction[]> | ||
@@ -936,0 +967,0 @@ // Addresses: |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
3364009
31937