Skip to content

Commit

Permalink
Add destructor for store
Browse files Browse the repository at this point in the history
  • Loading branch information
pzuraq committed Dec 20, 2022
1 parent 2ec0c5b commit eb505af
Show file tree
Hide file tree
Showing 9 changed files with 88 additions and 40 deletions.
11 changes: 11 additions & 0 deletions .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
25 changes: 15 additions & 10 deletions packages/bitski-provider/src/bitski-provider.ts
Expand Up @@ -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<Extra = unknown> 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<Extra>;
private didEmitConnect = false;
private activeSubs = new Set<string>();

constructor(config: BitskiProviderConfig) {
constructor(config: BitskiProviderConfig<Extra>) {
this.config = {
...config,

Expand Down Expand Up @@ -122,8 +122,8 @@ export class BitskiProvider implements EthProvider {
engine.push(createFetchRpcMiddleware());
}

async request<T extends EthMethod>(request: EthRequest<T>): EthResult<T>;
async request(request: EthRequest): EthResult<typeof request.method> {
async requestWithExtra<T extends EthMethod>(request: EthRequest<T>, extra?: Extra): EthResult<T>;
async requestWithExtra(request: EthRequest, extra?: Extra): EthResult<typeof request.method> {
const { method, params } = request;
let chainId: string;

Expand Down Expand Up @@ -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
Expand All @@ -177,6 +177,10 @@ export class BitskiProvider implements EthProvider {
}
}

async request<T extends EthMethod>(request: EthRequest<T>): EthResult<T> {
return this.requestWithExtra(request);
}

supportsSubscriptions(): boolean {
return true;
}
Expand Down Expand Up @@ -207,20 +211,21 @@ export class BitskiProvider implements EthProvider {

destroy(): void {
this.destructors.forEach((destroy) => destroy());
this.config.store.destroy?.();
}

private async requestWithContext<T extends EthMethod>(
private async requestWithChain<T extends EthMethod>(
chainId: string,
{ method, params }: EthRequest<T>,
opts?: { skipCache?: boolean },
opts?: { skipCache?: boolean; extra?: Extra },
): EthResult<T> {
const chain = expect(await this.store.findChain(chainId), 'expected a chain');

const context: RequestContext = {
const context: RequestContext<Extra> = {
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),
};

Expand Down
1 change: 1 addition & 0 deletions 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';
Expand Down
12 changes: 7 additions & 5 deletions packages/bitski-provider/src/store.ts
Expand Up @@ -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';
},
);
}
}
28 changes: 15 additions & 13 deletions packages/bitski-provider/src/types.ts
Expand Up @@ -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;
Expand All @@ -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<void>;
clear(): void | Promise<void>;

keys(): string[];
keys?(): string[] | Promise<string[]>;

// Get an item from the cache
getItem(key: string): Promise<unknown | undefined>;
getItem(key: string): unknown | undefined | Promise<unknown | undefined>;

// Set an item in the cache
setItem(key: string, value: unknown): Promise<void>;
setItem(key: string, value: unknown): void | Promise<void>;

// Remove the key from the cache
clearItem(key: string): Promise<void>;
clearItem(key: string): void | Promise<void>;

onUpdate?(fn: () => void);

destroy?(): void;
}

export interface InternalBitskiProviderConfig {
export interface InternalBitskiProviderConfig<Extra = unknown> {
fetch: typeof fetch;
additionalHeaders: Record<string, string>;

prependMiddleware?: ProviderMiddleware[];
prependMiddleware?: ProviderMiddleware<Extra>[];
pollingInterval?: number;
disableCaching?: boolean;
disableValidation?: boolean;
Expand All @@ -62,8 +64,8 @@ export interface InternalBitskiProviderConfig {

type Optional<T, K extends keyof T> = Omit<T, K> & Partial<T>;

export type BitskiProviderConfig = Optional<
InternalBitskiProviderConfig,
export type BitskiProviderConfig<Extra = unknown> = Optional<
InternalBitskiProviderConfig<Extra>,
'apiBaseUrl' | 'signerBaseUrl' | 'fetch' | 'additionalHeaders' | 'sign' | 'getUser' | 'store'
>;

Expand All @@ -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<T = unknown> {
export interface RequestContext<Extra = unknown> {
// The current chain the request is being made on
chain: EthChainDefinitionWithRpcUrl;

// The configuration of the provider
config: InternalBitskiProviderConfig;
config: InternalBitskiProviderConfig<Extra>;

extra?: T;
extra?: Extra;

// Allows middleware to emit an event on the provider
emit: <T extends EthEvent>(eventName: T, ...params: EthEventParams[T]) => void;
Expand Down
4 changes: 4 additions & 0 deletions packages/bitski-provider/src/utils/transaction.ts
Expand Up @@ -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',
}
Expand Down Expand Up @@ -129,6 +130,7 @@ const createPayload = <T extends EthSignMethod>(
}

case EthMethod.eth_signTypedData:
case EthMethod.eth_signTypedData_v1:
case EthMethod.eth_signTypedData_v3:
case EthMethod.eth_signTypedData_v4:
if (params && params.length > 1) {
Expand Down Expand Up @@ -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:
Expand Down
26 changes: 15 additions & 11 deletions packages/bitski/src/-private/auth/token-store.ts
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion packages/bitski/src/index.ts
Expand Up @@ -112,7 +112,7 @@ export class Bitski {
* This method should be called always, and immediately after the page loads.
*/
async initialize(): Promise<void> {
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;
Expand Down
19 changes: 19 additions & 0 deletions packages/eth-provider-types/index.d.ts
Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 = {
Expand Down Expand Up @@ -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 *****/
Expand Down

0 comments on commit eb505af

Please sign in to comment.