}
/**
+ * Adds a button to the DOM at the bottom of the body and scrolls it into view.
+ * Clicking it fires the passed onclick listener. An optional post-click delay
+ * can be configured in order to reset relevant timers e.g. user activation.
*
- * @param {string} text
- * @param {AsyncFunction} fn
- * @param {object} [opt]
- * @param {number} [opt.timeout]
+ * @param {string} label - button label
+ * @param {AsyncFunction} onclick - onclick handler
+ * @param {object} [opt] - configure click
+ * @param {number} [opt.delay] - seconds to wait after clicking before returning
* @returns {Promise<any>}
*/
-export async function click (text, fn, opt) {
- async function wait (timeout) {
- if (typeof timeout === 'number' && timeout > 0) {
- if (timeout > 300) {
- throw new RangeError('Click test should not wait more than 5 minutes', { cause: timeout })
+export async function click (label, onclick, opt) {
+ async function wait (delay) {
+ if (typeof delay === 'number' && delay > 0) {
+ if (delay > 300) {
+ throw new RangeError('Post-click delay 5 minutes or less', { cause: delay })
}
- console.log(`Click done, waiting ${timeout} seconds to reset transient user activation timer...`)
- return await new Promise(r => setTimeout(r, timeout * 1000))
+ console.log(`Click done, waiting ${delay} seconds to reset transient user activation timer...`)
+ return new Promise(r => setTimeout(r, delay * 1000))
}
- if (isNode) {
- await wait(opt?.timeout)
- return await fn()
- }
- const { timeout } = opt
- return new Promise((resolve, reject) => {
- const button = document.createElement('button')
- const hourglass = document.createTextNode('⏳')
- button.innerText = text
- button.onclick = async () => {
- button.disabled = true
- button.innerText = 'Waiting for device...'
- button.after(hourglass)
- try {
- const result = await fn()
- resolve(result)
- } catch (err) {
- reject(err)
- } finally {
- hourglass.remove()
- button.remove()
- await wait(opt?.timeout)
- }
- }
- document.body.appendChild(button)
- window?.scrollTo(0, document.body.scrollHeight)
- })
}
+ const { delay } = opt
+ if (isNode) {
+ await wait(delay)
+ return await onclick()
+ }
+ return new Promise((resolve, reject) => {
+ const button = document.createElement('button')
+ const hourglass = document.createTextNode('⏳')
+ button.innerText = label
+ button.onclick = async () => {
+ button.disabled = true
+ button.innerText = 'Waiting for device...'
+ button.after(hourglass)
+ try {
+ const result = await onclick()
+ resolve(result)
+ } catch (err) {
+ reject(err)
+ } finally {
+ hourglass.remove()
+ button.remove()
+ await wait(delay)
+ }
+ }
+ document.body.appendChild(button)
+ window?.scrollTo(0, document.body.scrollHeight)
+ })
}
export function stats (times) {
await assert.resolves(click(\r
'Sign and autogen PoW when processing',\r
async () => block.sign(OPEN_BLOCK.key),\r
- { timeout: 6 }\r
+ { delay: 6 }\r
))\r
\r
assert.nullish(block.work)\r
await assert.resolves(click(\r
'Sign with BLAKE2b',\r
async () => wallet.sign(1, block),\r
- { timeout: 6 }\r
+ { delay: 6 }\r
))\r
assert.ok(await block.verify(BLAKE2B_PUBLIC_1))\r
\r
await assert.resolves(click(\r
'Sign with BIP-44',\r
async () => wallet.sign(0, block),\r
- { timeout: 6 }\r
+ { delay: 6 }\r
))\r
assert.ok(await block.verify(PUBLIC_0))\r
\r
await assert.resolves(click(\r
'Sign with Exodus',\r
async () => wallet.sign(0, block),\r
- { timeout: 6 }\r
+ { delay: 6 }\r
))\r
assert.ok(await block.verify(EXODUS.PUBLIC_0))\r
\r
await assert.rejects(click(\r
'Fail to sign while locked',\r
async () => wallet.sign(0, block),\r
- { timeout: 6 }\r
+ { delay: 6 }\r
))\r
assert.ok(block.signature === undefined)\r
\r
await assert.resolves(click(\r
'Sign open block with key',\r
async () => block.sign(OPEN_BLOCK.key),\r
- { timeout: 6 }\r
+ { delay: 6 }\r
))\r
assert.equal(block.hash, OPEN_BLOCK.hash)\r
assert.equal(block.signature, OPEN_BLOCK.signature)\r
await assert.resolves(click(\r
'Sign receive block with key',\r
async () => block.sign(RECEIVE_BLOCK.key),\r
- { timeout: 6 }\r
+ { delay: 6 }\r
))\r
\r
assert.equal(block.hash, RECEIVE_BLOCK.hash)\r
await assert.resolves(click(\r
'Sign receive block without work',\r
async () => block.sign(RECEIVE_BLOCK.key),\r
- { timeout: 6 }\r
+ { delay: 6 }\r
))\r
\r
assert.equal(block.hash, RECEIVE_BLOCK.hash)\r
await assert.resolves(click(\r
'Sign Ledger-derived block using BIP-44 wallet',\r
async () => wallet.sign(0, block),\r
- { timeout: 6 }\r
+ { delay: 6 }\r
))\r
\r
assert.equal(block.signature, LEDGER_NANOS.OPEN_BLOCK.signature)\r
await assert.resolves(click(\r
'Sign send block with key',\r
async () => block.sign(SEND_BLOCK.key),\r
- { timeout: 6 }\r
+ { delay: 6 }\r
))\r
\r
assert.equal(block.hash, SEND_BLOCK.hash)\r
await assert.resolves(click(\r
'Sign send block without work',\r
async () => block.sign(SEND_BLOCK.key),\r
- { timeout: 6 }\r
+ { delay: 6 }\r
))\r
\r
assert.equal(block.hash, SEND_BLOCK.hash)\r
await assert.resolves(click(\r
'Sign change block with key',\r
async () => block.sign('781186FB9EF17DB6E3D1056550D9FAE5D5BBADA6A6BC370E4CBB938B1DC71DA3'), // Did not find a private key at nano docs for this address\r
- { timeout: 6 }\r
+ { delay: 6 }\r
))\r
\r
assert.equal(block.signature?.toUpperCase(), 'A3C3C66D6519CBC0A198E56855942DEACC6EF741021A1B11279269ADC587DE1DA53CD478B8A47553231104CF24D742E1BB852B0546B87038C19BAE20F9082B0D')\r
await assert.resolves(click(\r
'Sign change block without work',\r
async () => block.sign(PRIVATE_0),\r
- { timeout: 6 }\r
+ { delay: 6 }\r
))\r
\r
assert.equal(block.signature?.toUpperCase(), '2BD2F905E74B5BEE3E2277CED1D1E3F7535E5286B6E22F7B08A814AA9E5C4E1FEA69B61D60B435ADC2CE756E6EE5F5BE7EC691FE87E024A0B22A3D980CA5B305')\r
'fail to sign without args',\r
//@ts-expect-error\r
async () => block.sign(),\r
- { timeout: 6 }\r
+ { delay: 6 }\r
))\r
assert.nullish(block.signature)\r
\r
'fail to sign with null arg',\r
//@ts-expect-error\r
async () => block.sign(null),\r
- { timeout: 6 }\r
+ { delay: 6 }\r
))\r
assert.nullish(block.signature)\r
\r
await assert.rejects(click(\r
'fail to sign with invalid string length',\r
async () => block.sign('1'),\r
- { timeout: 6 }\r
+ { delay: 6 }\r
))\r
assert.nullish(block.signature)\r
\r
await assert.rejects(click(\r
'fail to sign with invalid string characters',\r
async () => block.sign('xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'),\r
- { timeout: 6 }\r
+ { delay: 6 }\r
))\r
assert.nullish(block.signature)\r
})\r
await click(
'Reset permissions, then click to continue',
- async () => new Promise(r => setTimeout(r, 5000))
+ async () => { },
+ { delay: 6 }
)
await assert.rejects(wallet.unlock())
assert.equal(wallet.isLocked, true)
await click(
'Click to finish Ledger tests by destroying wallet',
- async () => new Promise(r => setTimeout(r))
+ async () => { },
+ { delay: 0 }
)
await assert.resolves(wallet.destroy())
await assert.rejects(wallet.unlock())
'Sign and then verify Rolodex data/signature',
async () => {
signature = await Tools.sign(NANO_TEST_VECTORS.PRIVATE_0 + NANO_TEST_VECTORS.PUBLIC_0, data)
- }
+ },
+ { delay: 6 }
))
- console.log('Click done, waiting 6 seconds to reset transient user activation timer...')
- await new Promise(r => setTimeout(r, 6000))
await assert.resolves(Rolodex.add('JohnDoe', NANO_TEST_VECTORS.ADDRESS_0))
const result = await Rolodex.verify('JohnDoe', signature, data)
'Sign and then reject invalid Rolodex data/signature',
async () => {
signature = await Tools.sign(NANO_TEST_VECTORS.PRIVATE_0 + NANO_TEST_VECTORS.PUBLIC_0, data)
- }
+ },
+ { delay: 6 }
))
- console.log('Click done, waiting 6 seconds to reset transient user activation timer...')
- await new Promise(r => setTimeout(r, 6000))
await assert.resolves(Rolodex.add('JaneSmith', NANO_TEST_VECTORS.ADDRESS_1))
const result = await Rolodex.verify('JaneSmith', signature, data)
'Sign single string',\r
async () => {\r
result = Tools.sign(NANO_TEST_VECTORS.PRIVATE_0 + NANO_TEST_VECTORS.PUBLIC_0, m)\r
- }\r
+ },\r
+ { delay: 6 }\r
))\r
- console.log('Click done, waiting 6 seconds to reset transient user activation timer...')\r
- await new Promise(r => setTimeout(r, 6000))\r
\r
assert.equal(result.toLowerCase(), '0ceebbc0b9b3a270a30bad1eef4eddc0931effd8d5c0b7cab007fcc61f30d3ee9760cd93d7eeb5b654ecc0d9d17bc7a6d53bee54aa163a8272f425fb1184e30e')\r
})\r
'Sign arbitrary string',\r
async () => {\r
signature = await wallet.sign(0, data)\r
- }\r
+ },\r
+ { delay: 6 }\r
))\r
- console.log('Click done, waiting 6 seconds to reset transient user activation timer...')\r
- await new Promise(r => setTimeout(r, 6000))\r
assert.ok(await Tools.verify(account.publicKey, signature, data))\r
await assert.resolves(wallet.destroy())\r
})\r
\r
await assert.resolves(click(\r
'Sign to test subsequent verification success',\r
- async () => sendBlock.sign(wallet, 0)\r
+ async () => sendBlock.sign(wallet, 0),\r
+ { delay: 6 }\r
))\r
- console.log('Click done, waiting 6 seconds to reset transient user activation timer...')\r
- await new Promise(r => setTimeout(r, 6000))\r
\r
assert.ok(await sendBlock.verify(account.publicKey))\r
await assert.resolves(wallet.destroy())\r
\r
await assert.resolves(click(\r
'Sign to test subsequent verification failure',\r
- async () => sendBlock.sign(wallet, 0)\r
+ async () => sendBlock.sign(wallet, 0),\r
+ { delay: 6 }\r
))\r
- console.log('Click done, waiting 6 seconds to reset transient user activation timer...')\r
- await new Promise(r => setTimeout(r, 6000))\r
\r
assert.ok(await sendBlock.verify(account.publicKey))\r
\r