bitski-provider
Advanced tools
Comparing version 2.0.0 to 2.1.0-next.0
# bitski-provider | ||
## 2.1.0-next.0 | ||
### Minor Changes | ||
- [#347](https://github.com/BitskiCo/bitski-js/pull/347) [`38b66b0`](https://github.com/BitskiCo/bitski-js/commit/38b66b0c5a460a7f28fdb520d5c39d1f795e3d61) Thanks [@pzuraq](https://github.com/pzuraq)! - Add the ability for the SDK to handle multiple sequential signing reuests | ||
## 2.0.0 | ||
@@ -4,0 +10,0 @@ |
@@ -43,3 +43,3 @@ import SafeEventEmitter from '@metamask/safe-event-emitter'; | ||
this.activeSubs = new Set(); | ||
this.config = Object.assign(Object.assign({}, config), { fetch: (_a = config.fetch) !== null && _a !== void 0 ? _a : fetch, additionalHeaders: Object.assign({ 'X-API-KEY': config.clientId, 'X-CLIENT-ID': config.clientId, 'X-CLIENT-VERSION': "bitski-provider-v2.0.0" }, ((_b = config.additionalHeaders) !== null && _b !== void 0 ? _b : {})), apiBaseUrl: (_c = config.apiBaseUrl) !== null && _c !== void 0 ? _c : BITSKI_API_BASE_URL, signerBaseUrl: (_d = config.signerBaseUrl) !== null && _d !== void 0 ? _d : BITSKI_SIGNER_BASE_URL, store: (_e = config.store) !== null && _e !== void 0 ? _e : new LocalStorageStore(), sign: (_f = config.sign) !== null && _f !== void 0 ? _f : createBrowserSigner() }); | ||
this.config = Object.assign(Object.assign({}, config), { fetch: (_a = config.fetch) !== null && _a !== void 0 ? _a : fetch, additionalHeaders: Object.assign({ 'X-API-KEY': config.clientId, 'X-CLIENT-ID': config.clientId, 'X-CLIENT-VERSION': "bitski-provider-v2.1.0-next.0" }, ((_b = config.additionalHeaders) !== null && _b !== void 0 ? _b : {})), apiBaseUrl: (_c = config.apiBaseUrl) !== null && _c !== void 0 ? _c : BITSKI_API_BASE_URL, signerBaseUrl: (_d = config.signerBaseUrl) !== null && _d !== void 0 ? _d : BITSKI_SIGNER_BASE_URL, store: (_e = config.store) !== null && _e !== void 0 ? _e : new LocalStorageStore(), sign: (_f = config.sign) !== null && _f !== void 0 ? _f : createBrowserSigner() }); | ||
this.store = new BitskiProviderStateStore(this.config.store); | ||
@@ -46,0 +46,0 @@ // Setup the engine |
@@ -32,2 +32,3 @@ import css from '../styles/dialog'; | ||
constructor(content, dynamicContent = false) { | ||
this.dynamicContent = dynamicContent; | ||
// check for an element passed as content or a selector corresponding to an element | ||
@@ -38,14 +39,2 @@ this.content = this.parseContent(content); | ||
injectStyles(); | ||
// Inject dialog content | ||
this.injectTemplate(this.container, this.content); | ||
// Show a spinner if content is dynamic | ||
if (dynamicContent) { | ||
this.setLoading(true); | ||
} | ||
// Add close handlers | ||
this.addCloseHandlers(); | ||
// A short delay is required before triggering animations | ||
setTimeout(() => { | ||
this.show(); | ||
}, 10); | ||
} | ||
@@ -64,2 +53,16 @@ /** | ||
} | ||
open() { | ||
// Inject dialog content | ||
this.injectTemplate(this.container, this.content); | ||
// Show a spinner if content is dynamic | ||
if (this.dynamicContent) { | ||
this.setLoading(true); | ||
} | ||
// Add close handlers | ||
this.addCloseHandlers(); | ||
// A short delay is required before triggering animations | ||
setTimeout(() => { | ||
this.show(); | ||
}, 10); | ||
} | ||
/** | ||
@@ -66,0 +69,0 @@ * Dismisses the dialog without triggering the close handler. |
@@ -6,6 +6,3 @@ import { ethErrors } from 'eth-rpc-errors'; | ||
import { createBitskiTransaction } from '../utils/transaction'; | ||
// Global state, this manages the currently open signer popup. There should never | ||
// be more than one, so it's ok for this to be module scoped. | ||
let currentRequestDialog; | ||
let currentRequest; | ||
const SIGN_REQUEST_QUEUE = []; | ||
if (typeof window !== 'undefined') { | ||
@@ -50,35 +47,42 @@ window.addEventListener('message', (event) => { | ||
const showIframe = (transaction, context) => { | ||
return new Promise((fulfill, reject) => { | ||
const url = `${context.config.signerBaseUrl}/transactions/${transaction.id}`; | ||
const iframe = document.createElement('iframe'); | ||
iframe.style.position = 'absolute'; | ||
iframe.style.top = '0'; | ||
iframe.style.left = '0'; | ||
iframe.style.width = '100%'; | ||
iframe.style.height = '100%'; | ||
iframe.frameBorder = '0'; | ||
iframe.src = url; | ||
// Dismiss any existing dialogs to prevent UI glitches. | ||
if (currentRequestDialog && currentRequest) { | ||
currentRequestDialog.close(); | ||
const [, reject] = currentRequest; | ||
reject(ethErrors.provider.userRejectedRequest('Another signing request was made before this one was completed')); | ||
} | ||
currentRequest = [fulfill, reject]; | ||
currentRequestDialog = new Dialog(iframe, true); | ||
let resolve, reject; | ||
const promise = new Promise((res, rej) => { | ||
resolve = res; | ||
reject = rej; | ||
}); | ||
const url = `${context.config.signerBaseUrl}/transactions/${transaction.id}`; | ||
const iframe = document.createElement('iframe'); | ||
iframe.style.position = 'absolute'; | ||
iframe.style.top = '0'; | ||
iframe.style.left = '0'; | ||
iframe.style.width = '100%'; | ||
iframe.style.height = '100%'; | ||
iframe.frameBorder = '0'; | ||
iframe.src = url; | ||
const dialog = new Dialog(iframe, true); | ||
if (SIGN_REQUEST_QUEUE.length > 0) { | ||
const lastRequest = SIGN_REQUEST_QUEUE[SIGN_REQUEST_QUEUE.length - 1]; | ||
lastRequest.promise.then(() => { | ||
dialog.open(); | ||
}); | ||
} | ||
else { | ||
dialog.open(); | ||
} | ||
SIGN_REQUEST_QUEUE.push({ | ||
resolve, | ||
reject, | ||
promise, | ||
dialog, | ||
}); | ||
return promise; | ||
}; | ||
const handleCallback = (callback) => { | ||
// Ignore messages when we don't have a current request in flight | ||
if (currentRequest === undefined) { | ||
const currentRequest = SIGN_REQUEST_QUEUE.shift(); | ||
if (!currentRequest) { | ||
return; | ||
} | ||
const [fulfill, reject] = currentRequest; | ||
const { resolve, reject, dialog } = currentRequest; | ||
// Dismiss current dialog | ||
if (currentRequestDialog) { | ||
currentRequestDialog.close(); | ||
} | ||
// Clear state | ||
currentRequest = undefined; | ||
currentRequestDialog = undefined; | ||
dialog.close(); | ||
// Call the callback to complete the request | ||
@@ -89,3 +93,3 @@ if (callback.error) { | ||
else { | ||
fulfill(callback.result); | ||
resolve(callback.result); | ||
} | ||
@@ -92,0 +96,0 @@ }; |
@@ -49,3 +49,3 @@ "use strict"; | ||
this.activeSubs = new Set(); | ||
this.config = Object.assign(Object.assign({}, config), { fetch: (_a = config.fetch) !== null && _a !== void 0 ? _a : fetch, additionalHeaders: Object.assign({ 'X-API-KEY': config.clientId, 'X-CLIENT-ID': config.clientId, 'X-CLIENT-VERSION': "bitski-provider-v2.0.0" }, ((_b = config.additionalHeaders) !== null && _b !== void 0 ? _b : {})), apiBaseUrl: (_c = config.apiBaseUrl) !== null && _c !== void 0 ? _c : constants_1.BITSKI_API_BASE_URL, signerBaseUrl: (_d = config.signerBaseUrl) !== null && _d !== void 0 ? _d : constants_1.BITSKI_SIGNER_BASE_URL, store: (_e = config.store) !== null && _e !== void 0 ? _e : new store_1.LocalStorageStore(), sign: (_f = config.sign) !== null && _f !== void 0 ? _f : (0, browser_1.default)() }); | ||
this.config = Object.assign(Object.assign({}, config), { fetch: (_a = config.fetch) !== null && _a !== void 0 ? _a : fetch, additionalHeaders: Object.assign({ 'X-API-KEY': config.clientId, 'X-CLIENT-ID': config.clientId, 'X-CLIENT-VERSION': "bitski-provider-v2.1.0-next.0" }, ((_b = config.additionalHeaders) !== null && _b !== void 0 ? _b : {})), apiBaseUrl: (_c = config.apiBaseUrl) !== null && _c !== void 0 ? _c : constants_1.BITSKI_API_BASE_URL, signerBaseUrl: (_d = config.signerBaseUrl) !== null && _d !== void 0 ? _d : constants_1.BITSKI_SIGNER_BASE_URL, store: (_e = config.store) !== null && _e !== void 0 ? _e : new store_1.LocalStorageStore(), sign: (_f = config.sign) !== null && _f !== void 0 ? _f : (0, browser_1.default)() }); | ||
this.store = new store_1.BitskiProviderStateStore(this.config.store); | ||
@@ -52,0 +52,0 @@ // Setup the engine |
@@ -5,2 +5,3 @@ /** | ||
export declare class Dialog { | ||
private dynamicContent; | ||
private content; | ||
@@ -22,2 +23,3 @@ private container; | ||
hide(): void; | ||
open(): void; | ||
/** | ||
@@ -24,0 +26,0 @@ * Dismisses the dialog without triggering the close handler. |
@@ -38,2 +38,3 @@ "use strict"; | ||
constructor(content, dynamicContent = false) { | ||
this.dynamicContent = dynamicContent; | ||
// check for an element passed as content or a selector corresponding to an element | ||
@@ -44,14 +45,2 @@ this.content = this.parseContent(content); | ||
injectStyles(); | ||
// Inject dialog content | ||
this.injectTemplate(this.container, this.content); | ||
// Show a spinner if content is dynamic | ||
if (dynamicContent) { | ||
this.setLoading(true); | ||
} | ||
// Add close handlers | ||
this.addCloseHandlers(); | ||
// A short delay is required before triggering animations | ||
setTimeout(() => { | ||
this.show(); | ||
}, 10); | ||
} | ||
@@ -70,2 +59,16 @@ /** | ||
} | ||
open() { | ||
// Inject dialog content | ||
this.injectTemplate(this.container, this.content); | ||
// Show a spinner if content is dynamic | ||
if (this.dynamicContent) { | ||
this.setLoading(true); | ||
} | ||
// Add close handlers | ||
this.addCloseHandlers(); | ||
// A short delay is required before triggering animations | ||
setTimeout(() => { | ||
this.show(); | ||
}, 10); | ||
} | ||
/** | ||
@@ -72,0 +75,0 @@ * Dismisses the dialog without triggering the close handler. |
@@ -8,6 +8,3 @@ "use strict"; | ||
const transaction_1 = require("../utils/transaction"); | ||
// Global state, this manages the currently open signer popup. There should never | ||
// be more than one, so it's ok for this to be module scoped. | ||
let currentRequestDialog; | ||
let currentRequest; | ||
const SIGN_REQUEST_QUEUE = []; | ||
if (typeof window !== 'undefined') { | ||
@@ -52,35 +49,42 @@ window.addEventListener('message', (event) => { | ||
const showIframe = (transaction, context) => { | ||
return new Promise((fulfill, reject) => { | ||
const url = `${context.config.signerBaseUrl}/transactions/${transaction.id}`; | ||
const iframe = document.createElement('iframe'); | ||
iframe.style.position = 'absolute'; | ||
iframe.style.top = '0'; | ||
iframe.style.left = '0'; | ||
iframe.style.width = '100%'; | ||
iframe.style.height = '100%'; | ||
iframe.frameBorder = '0'; | ||
iframe.src = url; | ||
// Dismiss any existing dialogs to prevent UI glitches. | ||
if (currentRequestDialog && currentRequest) { | ||
currentRequestDialog.close(); | ||
const [, reject] = currentRequest; | ||
reject(eth_rpc_errors_1.ethErrors.provider.userRejectedRequest('Another signing request was made before this one was completed')); | ||
} | ||
currentRequest = [fulfill, reject]; | ||
currentRequestDialog = new dialog_1.Dialog(iframe, true); | ||
let resolve, reject; | ||
const promise = new Promise((res, rej) => { | ||
resolve = res; | ||
reject = rej; | ||
}); | ||
const url = `${context.config.signerBaseUrl}/transactions/${transaction.id}`; | ||
const iframe = document.createElement('iframe'); | ||
iframe.style.position = 'absolute'; | ||
iframe.style.top = '0'; | ||
iframe.style.left = '0'; | ||
iframe.style.width = '100%'; | ||
iframe.style.height = '100%'; | ||
iframe.frameBorder = '0'; | ||
iframe.src = url; | ||
const dialog = new dialog_1.Dialog(iframe, true); | ||
if (SIGN_REQUEST_QUEUE.length > 0) { | ||
const lastRequest = SIGN_REQUEST_QUEUE[SIGN_REQUEST_QUEUE.length - 1]; | ||
lastRequest.promise.then(() => { | ||
dialog.open(); | ||
}); | ||
} | ||
else { | ||
dialog.open(); | ||
} | ||
SIGN_REQUEST_QUEUE.push({ | ||
resolve, | ||
reject, | ||
promise, | ||
dialog, | ||
}); | ||
return promise; | ||
}; | ||
const handleCallback = (callback) => { | ||
// Ignore messages when we don't have a current request in flight | ||
if (currentRequest === undefined) { | ||
const currentRequest = SIGN_REQUEST_QUEUE.shift(); | ||
if (!currentRequest) { | ||
return; | ||
} | ||
const [fulfill, reject] = currentRequest; | ||
const { resolve, reject, dialog } = currentRequest; | ||
// Dismiss current dialog | ||
if (currentRequestDialog) { | ||
currentRequestDialog.close(); | ||
} | ||
// Clear state | ||
currentRequest = undefined; | ||
currentRequestDialog = undefined; | ||
dialog.close(); | ||
// Call the callback to complete the request | ||
@@ -91,3 +95,3 @@ if (callback.error) { | ||
else { | ||
fulfill(callback.result); | ||
resolve(callback.result); | ||
} | ||
@@ -94,0 +98,0 @@ }; |
@@ -12,3 +12,3 @@ { | ||
}, | ||
"version": "2.0.0", | ||
"version": "2.1.0-next.0", | ||
"scripts": { | ||
@@ -15,0 +15,0 @@ "test": "jest", |
@@ -37,3 +37,3 @@ import css from '../styles/dialog'; | ||
*/ | ||
constructor(content: HTMLElement | string, dynamicContent = false) { | ||
constructor(content: HTMLElement | string, private dynamicContent = false) { | ||
// check for an element passed as content or a selector corresponding to an element | ||
@@ -46,2 +46,18 @@ this.content = this.parseContent(content); | ||
injectStyles(); | ||
} | ||
/** | ||
* Show the dialog | ||
*/ | ||
public show(): void { | ||
this.container.classList.add('bitski-visible', 'bitski-loaded'); | ||
} | ||
/** | ||
* Hides the dialog, but does not remove | ||
*/ | ||
public hide(): void { | ||
this.container.classList.remove('bitski-visible', 'bitski-loaded'); | ||
} | ||
public open(): void { | ||
// Inject dialog content | ||
@@ -51,3 +67,3 @@ this.injectTemplate(this.container, this.content); | ||
// Show a spinner if content is dynamic | ||
if (dynamicContent) { | ||
if (this.dynamicContent) { | ||
this.setLoading(true); | ||
@@ -66,15 +82,2 @@ } | ||
/** | ||
* Show the dialog | ||
*/ | ||
public show(): void { | ||
this.container.classList.add('bitski-visible', 'bitski-loaded'); | ||
} | ||
/** | ||
* Hides the dialog, but does not remove | ||
*/ | ||
public hide(): void { | ||
this.container.classList.remove('bitski-visible', 'bitski-loaded'); | ||
} | ||
/** | ||
* Dismisses the dialog without triggering the close handler. | ||
@@ -81,0 +84,0 @@ */ |
@@ -8,7 +8,12 @@ import { ethErrors } from 'eth-rpc-errors'; | ||
// Global state, this manages the currently open signer popup. There should never | ||
// be more than one, so it's ok for this to be module scoped. | ||
let currentRequestDialog: Dialog | undefined; | ||
let currentRequest: [(signed: any) => void, (error: Error) => void] | undefined; | ||
// Global state, this manages the currently open signer popup. | ||
interface SignRequestState { | ||
dialog: Dialog; | ||
promise: Promise<string>; | ||
resolve: (v: string | PromiseLike<string>) => void; | ||
reject: (e: unknown) => void; | ||
} | ||
const SIGN_REQUEST_QUEUE: SignRequestState[] = []; | ||
if (typeof window !== 'undefined') { | ||
@@ -72,47 +77,53 @@ window.addEventListener('message', (event: MessageEvent) => { | ||
): Promise<string> => { | ||
return new Promise((fulfill, reject) => { | ||
const url = `${context.config.signerBaseUrl}/transactions/${transaction.id}`; | ||
let resolve, reject; | ||
const promise = new Promise<string>((res, rej) => { | ||
resolve = res; | ||
reject = rej; | ||
}); | ||
const iframe = document.createElement('iframe'); | ||
iframe.style.position = 'absolute'; | ||
iframe.style.top = '0'; | ||
iframe.style.left = '0'; | ||
iframe.style.width = '100%'; | ||
iframe.style.height = '100%'; | ||
iframe.frameBorder = '0'; | ||
iframe.src = url; | ||
const url = `${context.config.signerBaseUrl}/transactions/${transaction.id}`; | ||
// Dismiss any existing dialogs to prevent UI glitches. | ||
if (currentRequestDialog && currentRequest) { | ||
currentRequestDialog.close(); | ||
const [, reject] = currentRequest; | ||
reject( | ||
ethErrors.provider.userRejectedRequest( | ||
'Another signing request was made before this one was completed', | ||
), | ||
); | ||
} | ||
const iframe = document.createElement('iframe'); | ||
iframe.style.position = 'absolute'; | ||
iframe.style.top = '0'; | ||
iframe.style.left = '0'; | ||
iframe.style.width = '100%'; | ||
iframe.style.height = '100%'; | ||
iframe.frameBorder = '0'; | ||
iframe.src = url; | ||
currentRequest = [fulfill, reject]; | ||
currentRequestDialog = new Dialog(iframe, true); | ||
const dialog = new Dialog(iframe, true); | ||
if (SIGN_REQUEST_QUEUE.length > 0) { | ||
const lastRequest = SIGN_REQUEST_QUEUE[SIGN_REQUEST_QUEUE.length - 1]; | ||
lastRequest.promise.then(() => { | ||
dialog.open(); | ||
}); | ||
} else { | ||
dialog.open(); | ||
} | ||
SIGN_REQUEST_QUEUE.push({ | ||
resolve, | ||
reject, | ||
promise, | ||
dialog, | ||
}); | ||
return promise; | ||
}; | ||
const handleCallback = (callback: any): void => { | ||
// Ignore messages when we don't have a current request in flight | ||
if (currentRequest === undefined) { | ||
const currentRequest = SIGN_REQUEST_QUEUE.shift(); | ||
if (!currentRequest) { | ||
return; | ||
} | ||
const [fulfill, reject] = currentRequest; | ||
const { resolve, reject, dialog } = currentRequest; | ||
// Dismiss current dialog | ||
if (currentRequestDialog) { | ||
currentRequestDialog.close(); | ||
} | ||
dialog.close(); | ||
// Clear state | ||
currentRequest = undefined; | ||
currentRequestDialog = undefined; | ||
// Call the callback to complete the request | ||
@@ -122,3 +133,3 @@ if (callback.error) { | ||
} else { | ||
fulfill(callback.result); | ||
resolve(callback.result); | ||
} | ||
@@ -125,0 +136,0 @@ }; |
@@ -6,2 +6,3 @@ import { Dialog } from '../src/components/dialog'; | ||
const dialog = new Dialog(div); | ||
dialog.open(); | ||
expect((dialog as any).content).toBe(div); | ||
@@ -13,2 +14,3 @@ expect((dialog as any).container.contains(div)).toEqual(true); | ||
const dialog = new Dialog('Hello World'); | ||
dialog.open(); | ||
expect((dialog as any).content).toBeDefined(); | ||
@@ -23,2 +25,3 @@ expect((dialog as any).content.innerText).toEqual('Hello World'); | ||
const dialog = new Dialog('#test-div'); | ||
dialog.open(); | ||
expect((dialog as any).content).toBe(div); | ||
@@ -31,2 +34,3 @@ expect((dialog as any).container.contains(div)).toEqual(true); | ||
const spy = jest.spyOn(dialog, 'close'); | ||
dialog.open(); | ||
(dialog as any).container.dispatchEvent(new Event('click')); | ||
@@ -39,2 +43,3 @@ expect(spy).toHaveBeenCalled(); | ||
const spy = jest.spyOn(dialog, 'close'); | ||
dialog.open(); | ||
document.dispatchEvent(new KeyboardEvent('keyup', { key: 'E' })); | ||
@@ -48,2 +53,3 @@ expect(spy).not.toHaveBeenCalled(); | ||
const dialog = new Dialog('foo'); | ||
dialog.open(); | ||
const removeSpy = jest.spyOn((dialog as any).container, 'remove'); | ||
@@ -50,0 +56,0 @@ dialog.close(); |
@@ -766,3 +766,3 @@ import { EthMethod } from 'eth-provider-types'; | ||
test('sign() should close existing dialog if one is already open', async () => { | ||
test('sign() should enqueue multiple sign requests', async () => { | ||
expect.assertions(8); | ||
@@ -802,14 +802,11 @@ const provider = createTestProvider({ sign: createBrowserSigner() }); | ||
provider | ||
.request({ | ||
method: EthMethod.eth_signTransaction, | ||
params: [txn], | ||
}) | ||
.catch((e) => | ||
expect(e).toMatchObject({ | ||
message: 'Another signing request was made before this one was completed', | ||
}), | ||
); | ||
const result1 = provider.request({ | ||
method: EthMethod.eth_signTransaction, | ||
params: [txn], | ||
}); | ||
await sleep(0); | ||
const result2 = provider.request({ | ||
method: EthMethod.eth_signTransaction, | ||
params: [txn], | ||
}); | ||
@@ -827,8 +824,19 @@ triggerMessage( | ||
const result = await provider.request({ | ||
method: EthMethod.eth_signTransaction, | ||
params: [txn], | ||
}); | ||
await sleep(0); | ||
expect(result).toBe('0x123'); | ||
triggerMessage( | ||
new MessageEvent('message', { | ||
data: { | ||
id: 0, | ||
jsonrpc: '2.0', | ||
result: '0x456', | ||
}, | ||
origin: 'https://sign.bitski.com', | ||
}), | ||
); | ||
const [result1Value, result2Value] = await Promise.all([result1, result2]); | ||
expect(result1Value).toBe('0x123'); | ||
expect(result2Value).toBe('0x456'); | ||
}); | ||
@@ -835,0 +843,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
364461
7683
2