From eb505aff9cf0a3e338aec278be131df1b17fb66b Mon Sep 17 00:00:00 2001 From: Chris Hewell Garrett Date: Mon, 19 Dec 2022 17:30:31 -0500 Subject: [PATCH] Add destructor for store --- .changeset/wise-lamps-hang.md | 11 ++++++++ .../bitski-provider/src/bitski-provider.ts | 25 ++++++++++------- packages/bitski-provider/src/index.ts | 1 + packages/bitski-provider/src/store.ts | 12 ++++---- packages/bitski-provider/src/types.ts | 28 ++++++++++--------- .../bitski-provider/src/utils/transaction.ts | 4 +++ .../bitski/src/-private/auth/token-store.ts | 26 +++++++++-------- packages/bitski/src/index.ts | 2 +- packages/eth-provider-types/index.d.ts | 19 +++++++++++++ 9 files changed, 88 insertions(+), 40 deletions(-) create mode 100644 .changeset/wise-lamps-hang.md diff --git a/.changeset/wise-lamps-hang.md b/.changeset/wise-lamps-hang.md new file mode 100644 index 00000000..efafeefd --- /dev/null +++ b/.changeset/wise-lamps-hang.md @@ -0,0 +1,11 @@ +--- +'bitski-provider': patch +'bitski': patch +'eth-provider-types': patch +--- + +- Add destructor for store +- Also thread `Extra` type through provider and add `requestWithExtra` so + requests can be made with additional context. Also add a few missing eth + methods. +- Make stores able to return MaybePromise so they can be more efficient diff --git a/packages/bitski-provider/src/bitski-provider.ts b/packages/bitski-provider/src/bitski-provider.ts index 7d5f9650..9ea4b938 100644 --- a/packages/bitski-provider/src/bitski-provider.ts +++ b/packages/bitski-provider/src/bitski-provider.ts @@ -55,18 +55,18 @@ const SUB_INTERACTION_METHODS = new Set([ const UNSUB_METHODS = new Set([EthMethod.eth_unsubscribe, EthMethod.eth_uninstallFilter]); -export class BitskiProvider implements EthProvider { +export class BitskiProvider implements EthProvider { private engine = new JsonRpcEngine(); private events = new SafeEventEmitter(); private destructors: (() => void)[] = []; private requestId = 0; private store: BitskiProviderStateStore; - private config: InternalBitskiProviderConfig; + private config: InternalBitskiProviderConfig; private didEmitConnect = false; private activeSubs = new Set(); - constructor(config: BitskiProviderConfig) { + constructor(config: BitskiProviderConfig) { this.config = { ...config, @@ -122,8 +122,8 @@ export class BitskiProvider implements EthProvider { engine.push(createFetchRpcMiddleware()); } - async request(request: EthRequest): EthResult; - async request(request: EthRequest): EthResult { + async requestWithExtra(request: EthRequest, extra?: Extra): EthResult; + async requestWithExtra(request: EthRequest, extra?: Extra): EthResult { const { method, params } = request; let chainId: string; @@ -157,7 +157,7 @@ export class BitskiProvider implements EthProvider { default: try { - let result = await this.requestWithContext(chainId, request); + let result = await this.requestWithChain(chainId, request, { extra }); if (SUB_METHODS.has(method)) { // Ensure the subscription id is unique across chains @@ -177,6 +177,10 @@ export class BitskiProvider implements EthProvider { } } + async request(request: EthRequest): EthResult { + return this.requestWithExtra(request); + } + supportsSubscriptions(): boolean { return true; } @@ -207,20 +211,21 @@ export class BitskiProvider implements EthProvider { destroy(): void { this.destructors.forEach((destroy) => destroy()); + this.config.store.destroy?.(); } - private async requestWithContext( + private async requestWithChain( chainId: string, { method, params }: EthRequest, - opts?: { skipCache?: boolean }, + opts?: { skipCache?: boolean; extra?: Extra }, ): EthResult { const chain = expect(await this.store.findChain(chainId), 'expected a chain'); - const context: RequestContext = { + const context: RequestContext = { chain, config: this.config, emit: this.events.emit.bind(this.events), - request: (req, opts) => this.requestWithContext(chainId, req, opts), + request: (req, opts) => this.requestWithChain(chainId, req, opts), addDestructor: (destroy) => this.destructors.push(destroy), }; diff --git a/packages/bitski-provider/src/index.ts b/packages/bitski-provider/src/index.ts index 67ee0351..a34f5e06 100644 --- a/packages/bitski-provider/src/index.ts +++ b/packages/bitski-provider/src/index.ts @@ -1,4 +1,5 @@ export * from './types'; +export * from './constants'; export { LocalStorageStore } from './store'; export { createBitskiProvider, BitskiProvider } from './bitski-provider'; export { default as createBrowserSigner } from './signers/browser'; diff --git a/packages/bitski-provider/src/store.ts b/packages/bitski-provider/src/store.ts index 60a316a7..434d08dc 100644 --- a/packages/bitski-provider/src/store.ts +++ b/packages/bitski-provider/src/store.ts @@ -109,14 +109,16 @@ export class BitskiProviderStateStore { } private load(): void { - this.chains = this.store.getItem(CHAINS_STORAGE_KEY).then((chains) => { + this.chains = Promise.resolve(this.store.getItem(CHAINS_STORAGE_KEY)).then((chains) => { const result = array(chainDefinitionDecoder).decode(chains); return result.value ?? DEFAULT_CHAINS.slice(); }); - this.currentChainId = this.store.getItem(CURRENT_CHAIN_STORAGE_KEY).then((chainId) => { - const result = string.decode(chainId); - return result.value ?? '0x1'; - }); + this.currentChainId = Promise.resolve(this.store.getItem(CURRENT_CHAIN_STORAGE_KEY)).then( + (chainId) => { + const result = string.decode(chainId); + return result.value ?? '0x1'; + }, + ); } } diff --git a/packages/bitski-provider/src/types.ts b/packages/bitski-provider/src/types.ts index cb0c7faa..8c377ef7 100644 --- a/packages/bitski-provider/src/types.ts +++ b/packages/bitski-provider/src/types.ts @@ -7,7 +7,7 @@ import { EthRequest, EthResult, } from 'eth-provider-types'; -import { JsonRpcMiddleware, JsonRpcRequest, PendingJsonRpcResponse } from 'json-rpc-engine'; +import { JsonRpcRequest, PendingJsonRpcResponse } from 'json-rpc-engine'; export interface User { id: string; @@ -22,27 +22,29 @@ export interface EthChainDefinitionWithRpcUrl extends EthChainDefinition { // flexibility in storing access tokens and other persistent data. export interface BitskiProviderStore { // Empty the cache - clear(): Promise; + clear(): void | Promise; - keys(): string[]; + keys?(): string[] | Promise; // Get an item from the cache - getItem(key: string): Promise; + getItem(key: string): unknown | undefined | Promise; // Set an item in the cache - setItem(key: string, value: unknown): Promise; + setItem(key: string, value: unknown): void | Promise; // Remove the key from the cache - clearItem(key: string): Promise; + clearItem(key: string): void | Promise; onUpdate?(fn: () => void); + + destroy?(): void; } -export interface InternalBitskiProviderConfig { +export interface InternalBitskiProviderConfig { fetch: typeof fetch; additionalHeaders: Record; - prependMiddleware?: ProviderMiddleware[]; + prependMiddleware?: ProviderMiddleware[]; pollingInterval?: number; disableCaching?: boolean; disableValidation?: boolean; @@ -62,8 +64,8 @@ export interface InternalBitskiProviderConfig { type Optional = Omit & Partial; -export type BitskiProviderConfig = Optional< - InternalBitskiProviderConfig, +export type BitskiProviderConfig = Optional< + InternalBitskiProviderConfig, 'apiBaseUrl' | 'signerBaseUrl' | 'fetch' | 'additionalHeaders' | 'sign' | 'getUser' | 'store' >; @@ -73,14 +75,14 @@ export type BitskiProviderConfig = Optional< * configuration of the provider. This allows us to "switch" chains just by using * a different context. */ -export interface RequestContext { +export interface RequestContext { // The current chain the request is being made on chain: EthChainDefinitionWithRpcUrl; // The configuration of the provider - config: InternalBitskiProviderConfig; + config: InternalBitskiProviderConfig; - extra?: T; + extra?: Extra; // Allows middleware to emit an event on the provider emit: (eventName: T, ...params: EthEventParams[T]) => void; diff --git a/packages/bitski-provider/src/utils/transaction.ts b/packages/bitski-provider/src/utils/transaction.ts index 1c018c99..fde1e60e 100644 --- a/packages/bitski-provider/src/utils/transaction.ts +++ b/packages/bitski-provider/src/utils/transaction.ts @@ -9,6 +9,7 @@ export const enum TransactionKind { SignTransaction = 'ETH_SIGN_TRANSACTION', Sign = 'ETH_SIGN', SignTypedData = 'ETH_SIGN_TYPED_DATA', + SignTypedDataV1 = 'ETH_SIGN_TYPED_DATA_V1', SignTypedDataV3 = 'ETH_SIGN_TYPED_DATA_V3', SignTypedDataV4 = 'ETH_SIGN_TYPED_DATA_V4', } @@ -129,6 +130,7 @@ const createPayload = ( } case EthMethod.eth_signTypedData: + case EthMethod.eth_signTypedData_v1: case EthMethod.eth_signTypedData_v3: case EthMethod.eth_signTypedData_v4: if (params && params.length > 1) { @@ -157,6 +159,8 @@ const kindForMethod = (method: string): TransactionKind => { return TransactionKind.Sign; case EthMethod.eth_signTypedData: return TransactionKind.SignTypedData; + case EthMethod.eth_signTypedData_v1: + return TransactionKind.SignTypedDataV1; case EthMethod.eth_signTypedData_v3: return TransactionKind.SignTypedDataV3; case EthMethod.eth_signTypedData_v4: diff --git a/packages/bitski/src/-private/auth/token-store.ts b/packages/bitski/src/-private/auth/token-store.ts index e12d86d9..40dd8980 100644 --- a/packages/bitski/src/-private/auth/token-store.ts +++ b/packages/bitski/src/-private/auth/token-store.ts @@ -50,17 +50,21 @@ export class TokenStore { } public loadTokensFromCache(): void { - this.accessToken = this.store.getItem(this.accessTokenKey).then((accessTokenString) => { - const accessTokenResult = string.decode(accessTokenString); - if (accessTokenResult.value) { - return AccessToken.fromString(accessTokenResult.value); - } - }); - - this.idToken = this.store.getItem(this.idTokenKey).then((token) => string.decode(token).value); - this.refreshToken = this.store - .getItem(this.refreshTokenKey) - .then((token) => string.decode(token).value); + this.accessToken = Promise.resolve(this.store.getItem(this.accessTokenKey)).then( + (accessTokenString) => { + const accessTokenResult = string.decode(accessTokenString); + if (accessTokenResult.value) { + return AccessToken.fromString(accessTokenResult.value); + } + }, + ); + + this.idToken = Promise.resolve(this.store.getItem(this.idTokenKey)).then( + (token) => string.decode(token).value, + ); + this.refreshToken = Promise.resolve(this.store.getItem(this.refreshTokenKey)).then( + (token) => string.decode(token).value, + ); } public persistTokenResponse(response: TokenResponse): void { diff --git a/packages/bitski/src/index.ts b/packages/bitski/src/index.ts index 32873d06..72cc832d 100644 --- a/packages/bitski/src/index.ts +++ b/packages/bitski/src/index.ts @@ -112,7 +112,7 @@ export class Bitski { * This method should be called always, and immediately after the page loads. */ async initialize(): Promise { - const localStorageKeys = this.options?.store?.keys() || Object.keys(localStorage); + const localStorageKeys = (await this.options?.store?.keys?.()) || Object.keys(localStorage); if (!localStorageKeys.find((key) => key.startsWith('bitski'))) { return; diff --git a/packages/eth-provider-types/index.d.ts b/packages/eth-provider-types/index.d.ts index ddb3243c..f6d7d8ce 100644 --- a/packages/eth-provider-types/index.d.ts +++ b/packages/eth-provider-types/index.d.ts @@ -61,6 +61,8 @@ export const enum EthMethod { eth_unsubscribe = 'eth_unsubscribe', wallet_addEthereumChain = 'wallet_addEthereumChain', wallet_switchEthereumChain = 'wallet_switchEthereumChain', + wallet_requestPermissions = 'wallet_requestPermissions', + wallet_getPermissions = 'wallet_getPermissions', } export const enum EthBlockNumberTag { @@ -249,6 +251,19 @@ export interface SwitchEthereumChainParameter { chainId: string; // A 0x-prefixed hexadecimal string } +export interface Web3WalletPermission { + // The name of the method corresponding to the permission + parentCapability: string; + + // The date the permission was granted, in UNIX epoch time + date?: number; +} + +export interface RequestedPermissions { + // eslint-disable-next-line @typescript-eslint/ban-types + [methodName: string]: {}; // an empty object, for future extensibility +} + export type EthMethodParams = { [EthMethod.web3_clientVersion]: void; [EthMethod.web3_sha3]: void; @@ -327,6 +342,8 @@ export type EthMethodParams = { [EthMethod.eth_unsubscribe]: [subscriptionId: string]; [EthMethod.wallet_addEthereumChain]: [definition: EthChainDefinition]; [EthMethod.wallet_switchEthereumChain]: [SwitchEthereumChainParameter]; + [EthMethod.wallet_getPermissions]: void; + [EthMethod.wallet_requestPermissions]: RequestedPermissions[]; }; export type EthMethodResults = { @@ -387,6 +404,8 @@ export type EthMethodResults = { [EthMethod.eth_unsubscribe]: boolean; [EthMethod.wallet_addEthereumChain]: null; [EthMethod.wallet_switchEthereumChain]: null; + [EthMethod.wallet_getPermissions]: Web3WalletPermission[]; + [EthMethod.wallet_requestPermissions]: Web3WalletPermission[]; }; /***** RPC Errors *****/