add homepage with authentication
This commit is contained in:
parent
8df34f8040
commit
e67f436320
|
@ -23,6 +23,7 @@
|
|||
"@types/react-fontawesome": "^1.6.4",
|
||||
"@types/react-redux": "^7.1.7",
|
||||
"@types/react-router-dom": "^5.1.5",
|
||||
"@types/react-router-redux": "^5.0.18",
|
||||
"bn.js": "^5.1.1",
|
||||
"classnames": "^2.2.6",
|
||||
"connected-react-router": "^6.7.0",
|
||||
|
@ -34,6 +35,7 @@
|
|||
"react-redux": "^7.2.0",
|
||||
"react-router": "^5.1.2",
|
||||
"react-router-dom": "^5.1.2",
|
||||
"react-router-redux": "^4.0.8",
|
||||
"react-scripts": "3.4.1",
|
||||
"redux": "^4.0.5",
|
||||
"redux-thunk": "^2.3.0",
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
import { Dispatch } from 'redux';
|
||||
import { RootState } from '../reducers';
|
||||
import { Web3Type } from "../actions/web3";
|
||||
import {
|
||||
SignRedeemResponse,
|
||||
signTypedDataWithKeycard,
|
||||
signTypedDataWithWeb3,
|
||||
signTypedLogin,
|
||||
} from "../utils";
|
||||
import { buildRecipientBucketsPath } from "../config";
|
||||
import { push } from "react-router-redux";
|
||||
|
||||
export const start = () => {
|
||||
return (dispatch: Dispatch, getState: () => RootState) => {
|
||||
const state = getState();
|
||||
const account = state.web3.account!;
|
||||
const web3Type = state.web3.type;
|
||||
const chainID = state.web3.chainID!;
|
||||
|
||||
signTypedLogin(chainID, account, web3Type).then((resp: SignRedeemResponse) => {
|
||||
const path = buildRecipientBucketsPath(resp.signer);
|
||||
dispatch(push(path));
|
||||
}).catch(err => {
|
||||
//FIXME: handle error
|
||||
console.error(err)
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,9 +1,3 @@
|
|||
export const LAYOUT_TOGGLE_SIDEBAR = "LAYOUT_TOGGLE_SIDEBAR";
|
||||
export interface LayoutToggleSidebarAction {
|
||||
type: typeof LAYOUT_TOGGLE_SIDEBAR
|
||||
open: boolean
|
||||
}
|
||||
|
||||
export const LAYOUT_FLIP_CARD = "LAYOUT_FLIP_CARD";
|
||||
export interface LayoutFlipCardAction {
|
||||
type: typeof LAYOUT_FLIP_CARD
|
||||
|
@ -17,15 +11,9 @@ export interface LayoutToggleDebugAction {
|
|||
}
|
||||
|
||||
export type LayoutActions =
|
||||
LayoutToggleSidebarAction |
|
||||
LayoutFlipCardAction |
|
||||
LayoutToggleDebugAction;
|
||||
|
||||
export const toggleSidebar = (open: boolean): LayoutToggleSidebarAction => ({
|
||||
type: LAYOUT_TOGGLE_SIDEBAR,
|
||||
open,
|
||||
});
|
||||
|
||||
export const toggleDebug = (open: boolean): LayoutToggleDebugAction => ({
|
||||
type: LAYOUT_TOGGLE_DEBUG,
|
||||
open,
|
||||
|
|
|
@ -2,9 +2,14 @@ import { RootState } from '../reducers';
|
|||
import { config } from "../config";
|
||||
import { Dispatch } from 'redux';
|
||||
import { sha3 } from "web3-utils";
|
||||
import { recoverTypedSignature } from 'eth-sig-util';
|
||||
import { Web3Type } from "../actions/web3";
|
||||
import { KECCAK_EMPTY_STRING, newBucketContract} from '../utils';
|
||||
import {
|
||||
KECCAK_EMPTY_STRING,
|
||||
newBucketContract,
|
||||
SignRedeemResponse,
|
||||
signTypedDataWithKeycard,
|
||||
signTypedDataWithWeb3,
|
||||
} from '../utils';
|
||||
import { debug } from "./debug";
|
||||
|
||||
interface RedeemMessage {
|
||||
|
@ -14,11 +19,6 @@ interface RedeemMessage {
|
|||
blockHash: string
|
||||
}
|
||||
|
||||
interface SignRedeemResponse {
|
||||
sig: string
|
||||
signer: string
|
||||
}
|
||||
|
||||
export const ERROR_REDEEMING = "ERROR_REDEEMING";
|
||||
export interface ErrRedeeming {
|
||||
type: typeof ERROR_REDEEMING
|
||||
|
@ -96,6 +96,7 @@ export const redeem = (bucketAddress: string, recipientAddress: string, cleanCod
|
|||
dispatch(redeeming());
|
||||
|
||||
const state = getState();
|
||||
const chainID = state.web3.chainID!;
|
||||
const web3Type = state.web3.type;
|
||||
|
||||
const bucket = newBucketContract(bucketAddress);
|
||||
|
@ -118,7 +119,7 @@ export const redeem = (bucketAddress: string, recipientAddress: string, cleanCod
|
|||
const domainName = isERC20 ? "KeycardERC20Bucket" : "KeycardNFTBucket";
|
||||
//FIXME: is signer needed?
|
||||
dispatch(debug("signing redeem"));
|
||||
signRedeem(web3Type, bucketAddress, state.web3.account!, message, domainName).then(async ({ sig, signer }: SignRedeemResponse) => {
|
||||
signRedeem(chainID, web3Type, bucketAddress, state.web3.account!, message, domainName).then(async ({ sig, signer }: SignRedeemResponse) => {
|
||||
dispatch(debug(`signature: ${sig}, signer: ${signer}`));
|
||||
const recipient = state.redeemable.recipient!;
|
||||
if (signer.toLowerCase() !== recipient.toLowerCase()) {
|
||||
|
@ -136,9 +137,7 @@ export const redeem = (bucketAddress: string, recipientAddress: string, cleanCod
|
|||
}
|
||||
}
|
||||
|
||||
async function signRedeem(web3Type: Web3Type, contractAddress: string, signer: string, message: RedeemMessage, domainName: string): Promise<SignRedeemResponse> {
|
||||
const chainId = await config.web3!.eth.net.getId();
|
||||
|
||||
async function signRedeem(chainID: number, web3Type: Web3Type, contractAddress: string, signer: string, message: RedeemMessage, domainName: string): Promise<SignRedeemResponse> {
|
||||
const domain = [
|
||||
{ name: "name", type: "string" },
|
||||
{ name: "version", type: "string" },
|
||||
|
@ -156,7 +155,7 @@ async function signRedeem(web3Type: Web3Type, contractAddress: string, signer: s
|
|||
const domainData = {
|
||||
name: domainName,
|
||||
version: "1",
|
||||
chainId: chainId,
|
||||
chainId: chainID,
|
||||
verifyingContract: contractAddress
|
||||
};
|
||||
|
||||
|
@ -171,48 +170,12 @@ async function signRedeem(web3Type: Web3Type, contractAddress: string, signer: s
|
|||
};
|
||||
|
||||
if (web3Type === Web3Type.Status) {
|
||||
return signWithKeycard(data);
|
||||
return signTypedDataWithKeycard(data);
|
||||
} else {
|
||||
return signWithWeb3(signer, data);
|
||||
return signTypedDataWithWeb3(signer, data);
|
||||
}
|
||||
}
|
||||
|
||||
const signWithWeb3 = (signer: string, data: any): Promise<SignRedeemResponse> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
(window as any).ethereum.sendAsync({
|
||||
method: "eth_signTypedData_v3",
|
||||
params: [signer, JSON.stringify(data)],
|
||||
from: signer,
|
||||
}, (err: string, resp: any) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
const sig = resp.result;
|
||||
const signer = recoverTypedSignature({
|
||||
data,
|
||||
sig
|
||||
});
|
||||
|
||||
resolve({ sig, signer });
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
const signWithKeycard = (data: any): Promise<SignRedeemResponse> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
(window as any).ethereum.send("keycard_signTypedData", JSON.stringify(data)).then((sig: any) => {
|
||||
const signer = recoverTypedSignature({
|
||||
data,
|
||||
sig
|
||||
});
|
||||
resolve({ sig, signer });
|
||||
}).catch((err: string) => {
|
||||
reject(err);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
//FIXME: fix bucket contract type
|
||||
const sendTransaction = (account: string, bucket: any, bucketAddress: string, message: RedeemMessage, sig: string) => {
|
||||
return (dispatch: Dispatch, getState: () => RootState) => {
|
||||
|
|
|
@ -182,8 +182,9 @@ export const tokenMetadataLoaded = (tokenAddress: string, recipient: string, met
|
|||
export const loadRedeemable = (bucketAddress: string, recipientAddress: string) => {
|
||||
return async (dispatch: Dispatch, getState: () => RootState) => {
|
||||
const networkID = getState().web3.networkID!;
|
||||
dispatch(debug(`erc20 factory address: ${contractAddress(ERC20BucketFactory, networkID)}`));
|
||||
dispatch(debug(`nft factory address: ${contractAddress(NFTBucketFactory, networkID)}`));
|
||||
// FIXME: how can we set the address if deployed via make
|
||||
// dispatch(debug(`erc20 factory address: ${contractAddress(ERC20BucketFactory, networkID)}`));
|
||||
// dispatch(debug(`nft factory address: ${contractAddress(NFTBucketFactory, networkID)}`));
|
||||
dispatch(debug(`bucket address: ${bucketAddress}`));
|
||||
dispatch(debug(`recipient address: ${recipientAddress}`));
|
||||
dispatch(loadingRedeemable(bucketAddress, recipientAddress));
|
||||
|
|
|
@ -6,15 +6,17 @@ import {
|
|||
import { RootState } from '../reducers';
|
||||
import { debug } from "./debug";
|
||||
|
||||
export const VALID_NETWORK_NAME = "Ganache";
|
||||
export const VALID_NETWORK_ID = 5777;
|
||||
// export const VALID_NETWORK_NAME = "Ganache";
|
||||
// export const VALID_NETWORK_ID = 5777;
|
||||
|
||||
// export const VALID_NETWORK_NAME = "Ropsten";
|
||||
// export const VALID_NETWORK_ID = 3;
|
||||
export const VALID_NETWORK_NAME = "Ropsten";
|
||||
export const VALID_NETWORK_ID = 3;
|
||||
|
||||
// export const VALID_NETWORK_NAME = "Goerli";
|
||||
// export const VALID_NETWORK_ID = 5;
|
||||
export const LOCAL_NETWORK_ID = 1337;
|
||||
|
||||
export const LOCAL_NETWORK_IDS = [1337, 5777];
|
||||
export const VALID_NETWORK_IDS = [VALID_NETWORK_ID, ...LOCAL_NETWORK_IDS];
|
||||
|
||||
export enum Web3Type {
|
||||
None,
|
||||
|
@ -39,6 +41,7 @@ export const WEB3_NETWORK_ID_LOADED = "WEB3_NETWORK_ID_LOADED";
|
|||
export interface Web3NetworkIDLoadedAction {
|
||||
type: typeof WEB3_NETWORK_ID_LOADED
|
||||
networkID: number
|
||||
chainID: number
|
||||
}
|
||||
|
||||
export const WEB3_ACCOUNT_LOADED = "WEB3_ACCOUNT_LOADED";
|
||||
|
@ -59,9 +62,10 @@ export const web3Initialized = (t: Web3Type): Web3Actions => ({
|
|||
web3Type: t,
|
||||
})
|
||||
|
||||
export const web3NetworkIDLoaded = (id: number): Web3Actions => ({
|
||||
export const web3NetworkIDLoaded = (networkID: number, chainID: number): Web3Actions => ({
|
||||
type: WEB3_NETWORK_ID_LOADED,
|
||||
networkID: id,
|
||||
networkID,
|
||||
chainID,
|
||||
});
|
||||
|
||||
export const web3Error = (error: string): Web3Actions => ({
|
||||
|
@ -76,42 +80,25 @@ export const accountLoaded = (account: string): Web3Actions => ({
|
|||
|
||||
export const initializeWeb3 = () => {
|
||||
const w = window as any;
|
||||
return (dispatch: Dispatch, getState: () => RootState) => {
|
||||
return async (dispatch: Dispatch, getState: () => RootState) => {
|
||||
if (w.ethereum) {
|
||||
config.web3 = new Web3(w.ethereum);
|
||||
(config.web3! as any).eth.handleRevert = true;
|
||||
|
||||
w.ethereum.enable()
|
||||
.then(() => {
|
||||
const t: Web3Type = w.ethereum.isStatus ? Web3Type.Status : Web3Type.Generic;
|
||||
dispatch(web3Initialized(t));
|
||||
config.web3!.eth.net.getId().then((id: number) => {
|
||||
if (id !== VALID_NETWORK_ID && id !== LOCAL_NETWORK_ID) {
|
||||
dispatch(web3Error(`wrong network, please connect to ${VALID_NETWORK_NAME}`));
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(debug(`network id: ${id}`))
|
||||
dispatch(web3NetworkIDLoaded(id))
|
||||
checkNetworkAndChainId().then((resp: any) => {
|
||||
dispatch(debug(`network id: ${resp.networkID}`))
|
||||
dispatch(debug(`chain id: ${resp.chainID}`))
|
||||
dispatch(web3NetworkIDLoaded(resp.networkID, resp.chainID))
|
||||
dispatch(web3Initialized(resp.type));
|
||||
dispatch<any>(loadAddress());
|
||||
});
|
||||
})
|
||||
})
|
||||
.catch((err: string) => {
|
||||
//FIXME: handle error
|
||||
console.log("error", err)
|
||||
console.error("web3 error", err)
|
||||
dispatch(web3Error(err));
|
||||
});
|
||||
} else if (config.web3) {
|
||||
const t: Web3Type = w.ethereum.isStatus ? Web3Type.Status : Web3Type.Generic;
|
||||
dispatch(web3Initialized(t));
|
||||
config.web3!.eth.net.getId().then((id: number) => {
|
||||
dispatch(debug(`network id: ${id}`))
|
||||
dispatch(web3NetworkIDLoaded(id))
|
||||
dispatch<any>(loadAddress());
|
||||
})
|
||||
.catch((err: string) => {
|
||||
//FIXME: handle error
|
||||
console.log("error", err)
|
||||
});
|
||||
} else {
|
||||
dispatch(web3Error("web3 not supported"));
|
||||
}
|
||||
|
@ -126,3 +113,36 @@ const loadAddress = () => {
|
|||
});
|
||||
};
|
||||
}
|
||||
|
||||
const checkNetworkAndChainId = async () => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const networkID = await config.web3!.eth.net.getId();
|
||||
const type = (window as any).ethereum.isStatus ? Web3Type.Status : Web3Type.Generic;
|
||||
|
||||
if (!VALID_NETWORK_IDS.includes(networkID)) {
|
||||
reject(`wrong network, please connect to ${VALID_NETWORK_NAME}`);
|
||||
return;
|
||||
}
|
||||
|
||||
let chainID;
|
||||
|
||||
//FIXME: status should fix the getChainId error
|
||||
if (type === Web3Type.Status) {
|
||||
chainID = networkID;
|
||||
} else if (LOCAL_NETWORK_IDS.includes(networkID)) {
|
||||
chainID = 1;
|
||||
} else {
|
||||
chainID = await config.web3!.eth.getChainId();
|
||||
}
|
||||
|
||||
resolve({
|
||||
type,
|
||||
networkID,
|
||||
chainID,
|
||||
});
|
||||
} catch(e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -2,8 +2,12 @@ import React from 'react';
|
|||
import {
|
||||
useDispatch,
|
||||
} from 'react-redux';
|
||||
import { start } from "../actions/home";
|
||||
|
||||
export default function() {
|
||||
const dispatch = useDispatch()
|
||||
|
||||
return <>
|
||||
<button onClick={ () => dispatch<any>(start()) }>START</button>
|
||||
</>;
|
||||
}
|
||||
|
|
|
@ -10,12 +10,11 @@ import "../styles/Layout.scss";
|
|||
export default function(ownProps: any) {
|
||||
const props = useSelector((state: RootState) => {
|
||||
return {
|
||||
initialized: state.web3.networkID,
|
||||
initialized: state.web3.networkID !== undefined,
|
||||
networkID: state.web3.networkID,
|
||||
error: state.web3.error,
|
||||
account: state.web3.account,
|
||||
type: state.web3.type,
|
||||
sidebarOpen: state.layout.sidebarOpen,
|
||||
}
|
||||
}, shallowEqual);
|
||||
|
||||
|
|
|
@ -11,6 +11,10 @@ export const config: Config = {
|
|||
export const recipientBucketsPath = "/recipients/:recipientAddress/buckets";
|
||||
export const redeemablePath = "/buckets/:bucketAddress/redeemables/:recipientAddress";
|
||||
|
||||
export const buildRecipientBucketsPath = (recipientAddress: string) => {
|
||||
return `/recipients/${recipientAddress}/buckets`;
|
||||
}
|
||||
|
||||
export const buildRedeemablePath = (bucketAddress: string, recipientAddress: string) => {
|
||||
return `/buckets/${bucketAddress}/redeemables/${recipientAddress}`;
|
||||
}
|
||||
|
|
|
@ -1,30 +1,21 @@
|
|||
import {
|
||||
LayoutActions,
|
||||
LAYOUT_TOGGLE_SIDEBAR,
|
||||
LAYOUT_TOGGLE_DEBUG,
|
||||
LAYOUT_FLIP_CARD,
|
||||
} from "../actions/layout";
|
||||
|
||||
export interface LayoutState {
|
||||
sidebarOpen: boolean,
|
||||
cardFlipped: boolean,
|
||||
debugOpen: boolean,
|
||||
}
|
||||
|
||||
const initialState: LayoutState = {
|
||||
sidebarOpen: false,
|
||||
cardFlipped: false,
|
||||
debugOpen: false,
|
||||
}
|
||||
|
||||
export const layoutReducer = (state: LayoutState = initialState, action: LayoutActions): LayoutState => {
|
||||
switch(action.type) {
|
||||
case LAYOUT_TOGGLE_SIDEBAR:
|
||||
return {
|
||||
...state,
|
||||
sidebarOpen: action.open,
|
||||
};
|
||||
|
||||
case LAYOUT_TOGGLE_DEBUG:
|
||||
return {
|
||||
...state,
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
export interface Web3State {
|
||||
initialized: boolean
|
||||
networkID: number | undefined
|
||||
chainID: number | undefined
|
||||
error: string | undefined
|
||||
account: string | undefined
|
||||
type: Web3Type
|
||||
|
@ -18,6 +19,7 @@ export interface Web3State {
|
|||
const initialState: Web3State = {
|
||||
initialized: false,
|
||||
networkID: undefined,
|
||||
chainID: undefined,
|
||||
error: undefined,
|
||||
account: undefined,
|
||||
type: Web3Type.None,
|
||||
|
@ -44,6 +46,7 @@ export const web3Reducer = (state: Web3State = initialState, action: Web3Actions
|
|||
return {
|
||||
...state,
|
||||
networkID: action.networkID,
|
||||
chainID: action.chainID,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,8 @@ import {
|
|||
import { AbiItem } from "web3-utils";
|
||||
import Bucket from './contracts/Bucket.json';
|
||||
import { config } from "./config";
|
||||
import { recoverTypedSignature } from 'eth-sig-util';
|
||||
import { Web3Type } from "./actions/web3";
|
||||
|
||||
// keccak256("")
|
||||
export const KECCAK_EMPTY_STRING = "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470";
|
||||
|
@ -39,3 +41,95 @@ export const newBucketContract = (address: string) => {
|
|||
const bucket = new config.web3!.eth.Contract(bucketAbi, address);
|
||||
return bucket;
|
||||
}
|
||||
|
||||
export interface SignRedeemResponse {
|
||||
sig: string
|
||||
signer: string
|
||||
}
|
||||
|
||||
export const signTypedDataWithKeycard = (data: any): Promise<SignRedeemResponse> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
(window as any).ethereum.request({
|
||||
method: "keycard_signTypedData",
|
||||
params: JSON.stringify(data)
|
||||
}).then((sig: any) => {
|
||||
const signer = recoverTypedSignature({
|
||||
data,
|
||||
sig
|
||||
});
|
||||
resolve({ sig, signer });
|
||||
}).catch((err: string) => {
|
||||
alert("err")
|
||||
reject(err);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
export const signTypedDataWithWeb3 = (signer: string, data: any): Promise<SignRedeemResponse> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
(window as any).ethereum.sendAsync({
|
||||
method: "eth_signTypedData_v3",
|
||||
params: [signer, JSON.stringify(data)],
|
||||
from: signer,
|
||||
}, (err: string, resp: any) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
const sig = resp.result;
|
||||
const signer = recoverTypedSignature({
|
||||
data,
|
||||
sig
|
||||
});
|
||||
|
||||
resolve({ sig, signer });
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
//FIXME: use a proper message for authentication instead of KeycardERC20Bucket
|
||||
export const signTypedLogin = async (chainID: number, signer: string, web3Type: Web3Type): Promise<SignRedeemResponse> => {
|
||||
const message = {
|
||||
blockNumber: 1,
|
||||
blockHash: "0x0000000000000000000000000000000000000000",
|
||||
code: "0x0000000000000000000000000000000000000000",
|
||||
receiver: signer,
|
||||
}
|
||||
|
||||
const domain = [
|
||||
{ name: "name", type: "string" },
|
||||
{ name: "version", type: "string" },
|
||||
{ name: "chainId", type: "uint256" },
|
||||
{ name: "verifyingContract", type: "address" }
|
||||
];
|
||||
|
||||
const redeem = [
|
||||
{ name: "blockNumber", type: "uint256" },
|
||||
{ name: "blockHash", type: "bytes32" },
|
||||
{ name: "receiver", type: "address" },
|
||||
{ name: "code", type: "bytes32" },
|
||||
];
|
||||
|
||||
const domainData = {
|
||||
name: "KeycardERC20Bucket",
|
||||
version: "1",
|
||||
chainId: chainID,
|
||||
verifyingContract: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC",
|
||||
};
|
||||
|
||||
const data = {
|
||||
types: {
|
||||
EIP712Domain: domain,
|
||||
Redeem: redeem,
|
||||
},
|
||||
primaryType: "Redeem",
|
||||
domain: domainData,
|
||||
message: message
|
||||
};
|
||||
|
||||
if (web3Type === Web3Type.Status) {
|
||||
return signTypedDataWithKeycard(data);
|
||||
} else {
|
||||
return signTypedDataWithWeb3(signer, data);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1798,6 +1798,16 @@
|
|||
"@types/react" "*"
|
||||
"@types/react-router" "*"
|
||||
|
||||
"@types/react-router-redux@^5.0.18":
|
||||
version "5.0.18"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-router-redux/-/react-router-redux-5.0.18.tgz#5f28d5f7387fa71e33f602ccf9269e1609d47b8b"
|
||||
integrity sha512-5SI69Virpmo+5HXWXKIzSt5hsnV7TTidL3Ddmbi+PH1CIdi40wthJwjFoqiE+gRQANur5WhjEtfyPorJ4zymHA==
|
||||
dependencies:
|
||||
"@types/history" "*"
|
||||
"@types/react" "*"
|
||||
"@types/react-router" "*"
|
||||
redux ">= 3.7.2"
|
||||
|
||||
"@types/react-router@*":
|
||||
version "5.1.8"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.8.tgz#4614e5ba7559657438e17766bb95ef6ed6acc3fa"
|
||||
|
@ -9860,6 +9870,11 @@ react-router-dom@^5.1.2:
|
|||
tiny-invariant "^1.0.2"
|
||||
tiny-warning "^1.0.0"
|
||||
|
||||
react-router-redux@^4.0.8:
|
||||
version "4.0.8"
|
||||
resolved "https://registry.yarnpkg.com/react-router-redux/-/react-router-redux-4.0.8.tgz#227403596b5151e182377dab835b5d45f0f8054e"
|
||||
integrity sha1-InQDWWtRUeGCN32rg1tdRfD4BU4=
|
||||
|
||||
react-router@5.2.0, react-router@^5.1.2:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.2.0.tgz#424e75641ca8747fbf76e5ecca69781aa37ea293"
|
||||
|
@ -10061,7 +10076,7 @@ redux-thunk@^2.3.0:
|
|||
resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622"
|
||||
integrity sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw==
|
||||
|
||||
redux@^4.0.0, redux@^4.0.5:
|
||||
"redux@>= 3.7.2", redux@^4.0.0, redux@^4.0.5:
|
||||
version "4.0.5"
|
||||
resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.5.tgz#4db5de5816e17891de8a80c424232d06f051d93f"
|
||||
integrity sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w==
|
||||
|
|
Loading…
Reference in New Issue