Add some typescript

This commit is contained in:
Gary Wang 2020-12-16 18:15:38 +08:00
parent 37efefa4a6
commit 1de3d8e01f
7 changed files with 200 additions and 31 deletions

View File

@ -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",
@ -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": {

1
src/react-app-env.d.ts vendored Normal file
View File

@ -0,0 +1 @@
/// <reference types="react-scripts" />

View File

@ -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 }) {
@ -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';
@ -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(
@ -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 ||
@ -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];

View File

@ -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();
@ -39,8 +39,18 @@ 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;
@ -48,8 +58,14 @@ class FetchLoopListener {
}
}
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;
@ -57,25 +73,25 @@ class FetchLoopInternal {
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) {
@ -85,7 +101,7 @@ class FetchLoopInternal {
}
}
notifyListeners() {
notifyListeners(): void {
this.listeners.forEach((listener) => listener.callback());
}
@ -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();
@ -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;
}

View File

@ -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) {
@ -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);
@ -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
tsconfig.json Normal file
View File

@ -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"
]
}

View File

@ -1494,6 +1494,17 @@
"@types/yargs" "^15.0.0"
chalk "^3.0.0"
"@jest/types@^26.6.2":
version "26.6.2"
resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.6.2.tgz#bef5a532030e1d88a2f5a6d933f84e97226ed48e"
integrity sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==
dependencies:
"@types/istanbul-lib-coverage" "^2.0.0"
"@types/istanbul-reports" "^3.0.0"
"@types/node" "*"
"@types/yargs" "^15.0.0"
chalk "^4.0.0"
"@ledgerhq/devices@^5.34.0":
version "5.34.0"
resolved "https://registry.yarnpkg.com/@ledgerhq/devices/-/devices-5.34.0.tgz#4cb073a7bc9528f42f539241fedf44c0c6b451dc"
@ -1880,7 +1891,7 @@
dependencies:
"@babel/types" "^7.3.0"
"@types/bn.js@^4.11.3", "@types/bn.js@^4.11.5":
"@types/bn.js@^4.11.3", "@types/bn.js@^4.11.5", "@types/bn.js@^4.11.6":
version "4.11.6"
resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-4.11.6.tgz#c306c70d9358aaea33cd4eda092a742b9505967c"
integrity sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==
@ -1941,6 +1952,21 @@
"@types/istanbul-lib-coverage" "*"
"@types/istanbul-lib-report" "*"
"@types/istanbul-reports@^3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz#508b13aa344fa4976234e75dddcc34925737d821"
integrity sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==
dependencies:
"@types/istanbul-lib-report" "*"
"@types/jest@^26.0.14":
version "26.0.19"
resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.19.tgz#e6fa1e3def5842ec85045bd5210e9bb8289de790"
integrity sha512-jqHoirTG61fee6v6rwbnEuKhpSKih0tuhqeFbCmMmErhtu3BYlOZaXWjffgOstMM4S/3iQD31lI5bGLTrs97yQ==
dependencies:
jest-diff "^26.0.0"
pretty-format "^26.0.0"
"@types/json-schema@^7.0.3", "@types/json-schema@^7.0.4":
version "7.0.5"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.5.tgz#dcce4430e64b443ba8945f0290fb564ad5bac6dd"
@ -1976,6 +2002,11 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.62.tgz#733923d73669188d35950253dd18a21570085d2b"
integrity sha512-qAfo81CsD7yQIM9mVyh6B/U47li5g7cfpVQEDMfQeF8pSZVwzbhwU3crc0qG4DmpsebpJPR49AKOExQyJ05Cpg==
"@types/node@^14.11.4":
version "14.14.14"
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.14.tgz#f7fd5f3cc8521301119f63910f0fb965c7d761ae"
integrity sha512-UHnOPWVWV1z+VV8k6L1HhG7UbGBgIdghqF3l9Ny9ApPghbjICXkUJSd/b9gOgQfjM1r+37cipdw/HJ3F6ICEnQ==
"@types/parse-json@^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
@ -2015,6 +2046,13 @@
dependencies:
"@types/react" "*"
"@types/react-dom@^16.9.8":
version "16.9.10"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.10.tgz#4485b0bec3d41f856181b717f45fd7831101156f"
integrity sha512-ItatOrnXDMAYpv6G8UCk2VhbYVTjZT9aorLtA/OzDN9XJ2GKcfam68jutoAcILdRjsRUO8qb7AmyObF77Q8QFw==
dependencies:
"@types/react" "^16"
"@types/react-transition-group@^4.2.0":
version "4.4.0"
resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.0.tgz#882839db465df1320e4753e6e9f70ca7e9b4d46d"
@ -2030,6 +2068,14 @@
"@types/prop-types" "*"
csstype "^3.0.2"
"@types/react@^16", "@types/react@^16.9.51":
version "16.14.2"
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.14.2.tgz#85dcc0947d0645349923c04ccef6018a1ab7538c"
integrity sha512-BzzcAlyDxXl2nANlabtT4thtvbbnhee8hMmH/CcJrISDBVcJS1iOsP1f0OAgSdGE0MsY9tqcrb9YoZcOFv9dbQ==
dependencies:
"@types/prop-types" "*"
csstype "^3.0.2"
"@types/secp256k1@^4.0.1":
version "4.0.1"
resolved "https://registry.yarnpkg.com/@types/secp256k1/-/secp256k1-4.0.1.tgz#fb3aa61a1848ad97d7425ff9dcba784549fca5a4"
@ -4422,6 +4468,11 @@ diff-sequences@^24.9.0:
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.9.0.tgz#5715d6244e2aa65f48bba0bc972db0b0b11e95b5"
integrity sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew==
diff-sequences@^26.6.2:
version "26.6.2"
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.6.2.tgz#48ba99157de1923412eed41db6b6d4aa9ca7c0b1"
integrity sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==
diffie-hellman@^5.0.0:
version "5.0.3"
resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875"
@ -7038,6 +7089,16 @@ jest-diff@^24.0.0, jest-diff@^24.9.0:
jest-get-type "^24.9.0"
pretty-format "^24.9.0"
jest-diff@^26.0.0:
version "26.6.2"
resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-26.6.2.tgz#1aa7468b52c3a68d7d5c5fdcdfcd5e49bd164394"
integrity sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA==
dependencies:
chalk "^4.0.0"
diff-sequences "^26.6.2"
jest-get-type "^26.3.0"
pretty-format "^26.6.2"
jest-docblock@^24.3.0:
version "24.9.0"
resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-24.9.0.tgz#7970201802ba560e1c4092cc25cbedf5af5a8ce2"
@ -7096,6 +7157,11 @@ jest-get-type@^24.9.0:
resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-24.9.0.tgz#1684a0c8a50f2e4901b6644ae861f579eed2ef0e"
integrity sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q==
jest-get-type@^26.3.0:
version "26.3.0"
resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-26.3.0.tgz#e97dc3c3f53c2b406ca7afaed4493b1d099199e0"
integrity sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==
jest-haste-map@^24.9.0:
version "24.9.0"
resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-24.9.0.tgz#b38a5d64274934e21fa417ae9a9fbeb77ceaac7d"
@ -9997,6 +10063,16 @@ pretty-format@^25.1.0, pretty-format@^25.5.0:
ansi-styles "^4.0.0"
react-is "^16.12.0"
pretty-format@^26.0.0, pretty-format@^26.6.2:
version "26.6.2"
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.2.tgz#e35c2705f14cb7fe2fe94fa078345b444120fc93"
integrity sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==
dependencies:
"@jest/types" "^26.6.2"
ansi-regex "^5.0.0"
ansi-styles "^4.0.0"
react-is "^17.0.1"
process-nextick-args@~2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
@ -10281,6 +10357,11 @@ react-is@^16.12.0, react-is@^16.7.0, react-is@^16.8.0, react-is@^16.8.1, react-i
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
react-is@^17.0.1:
version "17.0.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz#5b3531bd76a645a4c9fb6e693ed36419e3301339"
integrity sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==
react-scripts@3.4.1:
version "3.4.1"
resolved "https://registry.yarnpkg.com/react-scripts/-/react-scripts-3.4.1.tgz#f551298b5c71985cc491b9acf3c8e8c0ae3ada0a"
@ -12055,6 +12136,11 @@ typeforce@^1.11.5:
resolved "https://registry.yarnpkg.com/typeforce/-/typeforce-1.18.0.tgz#d7416a2c5845e085034d70fcc5b6cc4a90edbfdc"
integrity sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g==
typescript@3.9.7:
version "3.9.7"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.7.tgz#98d600a5ebdc38f40cb277522f12dc800e9e25fa"
integrity sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==
ultron@~1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.1.tgz#9fe1536a10a664a65266a1e3ccf85fd36302bc9c"