Skip to content

Commit

Permalink
Add some typescript
Browse files Browse the repository at this point in the history
  • Loading branch information
garywang committed Dec 19, 2020
1 parent 37efefa commit 1de3d8e
Show file tree
Hide file tree
Showing 7 changed files with 200 additions and 31 deletions.
6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"@types/bn.js": "^4.11.6",
"@types/jest": "^26.0.14",
"@types/node": "^14.11.4",
"@types/react": "^16.9.51",
"@types/react-dom": "^16.9.8",
"bip32": "^2.0.5",
"bip39": "^3.0.2",
"bn.js": "^5.1.2",
Expand All @@ -24,6 +29,7 @@
"react-dom": "^16.13.1",
"react-scripts": "3.4.1",
"tweetnacl": "^1.0.3",
"typescript": "3.9.7",
"web3": "^1.2.11"
},
"scripts": {
Expand Down
1 change: 1 addition & 0 deletions src/react-app-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/// <reference types="react-scripts" />
40 changes: 32 additions & 8 deletions src/utils/connection.js → src/utils/connection.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
import React, { useContext, useEffect, useMemo } from 'react';
import { clusterApiUrl, Connection } from '@solana/web3.js';
import {
AccountInfo,
clusterApiUrl,
Connection,
PublicKey,
} from '@solana/web3.js';
import { useLocalStorageState } from './utils';
import { refreshCache, setCache, useAsyncData } from './fetch-loop';
import tuple from 'immutable-tuple';

const ConnectionContext = React.createContext(null);
const ConnectionContext = React.createContext<{
endpoint: string;
setEndpoint: (string) => void;
connection: Connection;
} | null>(null);

export const MAINNET_URL = 'https://solana-api.projectserum.com';
export function ConnectionProvider({ children }) {
Expand All @@ -25,21 +34,34 @@ export function ConnectionProvider({ children }) {
}

export function useConnection() {
return useContext(ConnectionContext).connection;
let context = useContext(ConnectionContext);
if (!context) {
throw new Error('Missing connection context');
}
return context.connection;
}

export function useConnectionConfig() {
let context = useContext(ConnectionContext);
if (!context) {
throw new Error('Missing connection context');
}
return { endpoint: context.endpoint, setEndpoint: context.setEndpoint };
}

export function useIsProdNetwork() {
const endpoint = useContext(ConnectionContext).endpoint;
return endpoint === MAINNET_URL;
let context = useContext(ConnectionContext);
if (!context) {
throw new Error('Missing connection context');
}
return context.endpoint === MAINNET_URL;
}

export function useSolanaExplorerUrlSuffix() {
const context = useContext(ConnectionContext);
if (!context) {
throw new Error('Missing connection context');
}
const endpoint = context.endpoint;
if (endpoint === clusterApiUrl('devnet')) {
return '?cluster=devnet';
Expand All @@ -49,7 +71,7 @@ export function useSolanaExplorerUrlSuffix() {
return '';
}

export function useAccountInfo(publicKey) {
export function useAccountInfo(publicKey?: PublicKey) {
const connection = useConnection();
const cacheKey = tuple(connection, publicKey?.toBase58());
const [accountInfo, loaded] = useAsyncData(
Expand All @@ -60,7 +82,7 @@ export function useAccountInfo(publicKey) {
if (!publicKey) {
return;
}
let previousInfo = null;
let previousInfo: AccountInfo<Buffer> | null = null;
const id = connection.onAccountChange(publicKey, (info) => {
if (
!previousInfo ||
Expand All @@ -71,7 +93,9 @@ export function useAccountInfo(publicKey) {
setCache(cacheKey, info);
}
});
return () => connection.removeAccountChangeListener(id);
return () => {
connection.removeAccountChangeListener(id);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [connection, publicKey?.toBase58(), cacheKey]);
return [accountInfo, loaded];
Expand Down
44 changes: 30 additions & 14 deletions src/utils/fetch-loop.js → src/utils/fetch-loop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import tuple from 'immutable-tuple';

const pageLoadTime = new Date();

const globalCache = new Map();
const globalCache: Map<any, any> = new Map();

class FetchLoops {
loops = new Map();
Expand Down Expand Up @@ -39,43 +39,59 @@ class FetchLoops {
}
const globalLoops = new FetchLoops();

class FetchLoopListener {
constructor(cacheKey, fn, refreshInterval, callback) {
class FetchLoopListener<T = any> {
cacheKey: any;
fn: () => Promise<T>;
refreshInterval: number;
callback: () => void;

constructor(
cacheKey: any,
fn: () => Promise<T>,
refreshInterval: number,
callback: () => void,
) {
this.cacheKey = cacheKey;
this.fn = fn;
this.refreshInterval = refreshInterval;
this.callback = callback;
}
}

class FetchLoopInternal {
constructor(cacheKey, fn) {
class FetchLoopInternal<T = any> {
cacheKey: any;
fn: () => Promise<T>;
timeoutId: null | any;
listeners: Set<FetchLoopListener<T>>;
errors: number;

constructor(cacheKey: any, fn: () => Promise<T>) {
this.cacheKey = cacheKey;
this.fn = fn;
this.timeoutId = null;
this.listeners = new Set();
this.errors = 0;
}

get refreshInterval() {
get refreshInterval(): number {
return Math.min(
...[...this.listeners].map((listener) => listener.refreshInterval),
);
}

get stopped() {
get stopped(): boolean {
return this.listeners.size === 0;
}

addListener(listener) {
let previousRefreshInterval = this.refreshInterval;
addListener(listener: FetchLoopListener<T>): void {
const previousRefreshInterval = this.refreshInterval;
this.listeners.add(listener);
if (this.refreshInterval < previousRefreshInterval) {
this.refresh();
}
}

removeListener(listener) {
removeListener(listener: FetchLoopListener<T>): void {
assert(this.listeners.delete(listener));
if (this.stopped) {
if (this.timeoutId) {
Expand All @@ -85,7 +101,7 @@ class FetchLoopInternal {
}
}

notifyListeners() {
notifyListeners(): void {
this.listeners.forEach((listener) => listener.callback());
}

Expand All @@ -99,7 +115,7 @@ class FetchLoopInternal {
}

try {
let data = await this.fn();
const data = await this.fn();
globalCache.set(this.cacheKey, data);
this.errors = 0;
this.notifyListeners();
Expand All @@ -113,11 +129,11 @@ class FetchLoopInternal {

// Back off on errors.
if (this.errors > 0) {
waitTime = Math.min(1000 * Math.pow(2, this.errors - 1), 60000);
waitTime = Math.min(1000 * 2 ** (this.errors - 1), 60000);
}

// Don't do any refreshing for the first five seconds, to make way for other things to load.
let timeSincePageLoad = new Date() - pageLoadTime;
const timeSincePageLoad = +new Date() - +pageLoadTime;
if (timeSincePageLoad < 5000) {
waitTime += 5000 - timeSincePageLoad / 2;
}
Expand Down
26 changes: 18 additions & 8 deletions src/utils/utils.js → src/utils/utils.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { useCallback, useEffect, useState } from 'react';
import { Connection, PublicKey } from '@solana/web3.js';

export async function sleep(ms) {
export async function sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}

export function useLocalStorageState(key, defaultState = null) {
export function useLocalStorageState<T>(
key: string,
defaultState: T,
): [T, (T) => void] {
const [state, setState] = useState(() => {
let storedState = localStorage.getItem(key);
if (storedState) {
Expand Down Expand Up @@ -32,14 +36,14 @@ export function useLocalStorageState(key, defaultState = null) {
return [state, setLocalStorageState];
}

export function useEffectAfterTimeout(effect, timeout) {
export function useEffectAfterTimeout(effect: () => void, timeout: number) {
useEffect(() => {
let handle = setTimeout(effect, timeout);
return () => clearTimeout(handle);
});
}

export function useListener(emitter, eventName) {
export function useListener(emitter, eventName: string) {
let [, forceUpdate] = useState(0);
useEffect(() => {
let listener = () => forceUpdate((i) => i + 1);
Expand All @@ -48,19 +52,25 @@ export function useListener(emitter, eventName) {
}, [emitter, eventName]);
}

export function abbreviateAddress(address) {
export function abbreviateAddress(address: PublicKey) {
let base58 = address.toBase58();
return base58.slice(0, 4) + '…' + base58.slice(base58.length - 4);
}

export async function confirmTransaction(connection, signature) {
export async function confirmTransaction(
connection: Connection,
signature: string,
) {
let startTime = new Date();
let result = await connection.confirmTransaction(signature, 'recent');
if (result.value.err) {
throw new Error(
'Error confirming transaction: ' + JSON.stringify(result.err),
'Error confirming transaction: ' + JSON.stringify(result.value.err),
);
}
console.log('Transaction confirmed after %sms', new Date() - startTime);
console.log(
'Transaction confirmed after %sms',
new Date().getTime() - startTime.getTime(),
);
return result.value;
}
26 changes: 26 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"compilerOptions": {
"target": "esnext",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"noImplicitAny": false,
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react"
},
"include": [
"src"
]
}
Loading

0 comments on commit 1de3d8e

Please sign in to comment.