Skip to content

Commit 288dc14

Browse files
authored
adding session timer and track private key existence in UI (#13)
* adding session timer and track private key existence in UI * rm debugger * rm unnecessary const's
1 parent a79d33a commit 288dc14

File tree

10 files changed

+217
-85
lines changed

10 files changed

+217
-85
lines changed

src/api/internal/index.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,14 @@ export const recoverAccount = async (
105105

106106
export const confirmPassword = async (
107107
password: string,
108-
): Promise<{ publicKey: string; applicationState: APPLICATION_STATE }> => {
108+
): Promise<{
109+
publicKey: string;
110+
hasPrivateKey: boolean;
111+
applicationState: APPLICATION_STATE;
112+
}> => {
109113
let response = {
110114
publicKey: "",
115+
hasPrivateKey: false,
111116
applicationState: APPLICATION_STATE.MNEMONIC_PHRASE_CONFIRMED,
112117
};
113118
try {

src/api/types/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
export interface Response {
88
applicationState: APPLICATION_STATE;
99
publicKey: string;
10+
hasPrivateKey: boolean;
1011
mnemonicPhrase: string;
1112
isCorrectPhrase: boolean;
1213
confirmedPassword: boolean;

src/background/ducks/session.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { combineReducers } from "redux";
2+
import { configureStore, createSelector, createSlice } from "@reduxjs/toolkit";
3+
4+
const initialState = {
5+
publicKey: "",
6+
privateKey: "",
7+
mnemonicPhrase: "",
8+
};
9+
10+
interface UiData {
11+
publicKey: string;
12+
mnemonicPhrase?: string;
13+
}
14+
15+
interface AppData {
16+
privateKey: string;
17+
}
18+
19+
const sessionSlice = createSlice({
20+
name: "session",
21+
initialState,
22+
reducers: {
23+
logIn: (state, action: { payload: UiData }) => {
24+
const { publicKey, mnemonicPhrase = "" } = action.payload;
25+
26+
return {
27+
...state,
28+
publicKey,
29+
mnemonicPhrase,
30+
};
31+
},
32+
logOut: () => initialState,
33+
grantAccountAccess: (state, action: { payload: AppData }) => {
34+
const { privateKey } = action.payload;
35+
36+
return {
37+
...state,
38+
privateKey,
39+
};
40+
},
41+
timeoutAccountAccess: (state) => ({ ...state, privateKey: "" }),
42+
},
43+
});
44+
45+
export const store = configureStore({
46+
reducer: combineReducers({
47+
session: sessionSlice.reducer,
48+
}),
49+
});
50+
51+
export const {
52+
actions: { logIn, logOut, grantAccountAccess, timeoutAccountAccess },
53+
} = sessionSlice;
54+
55+
const sessionSelector = (state: { session: UiData & AppData }) => state.session;
56+
export const publicKeySelector = createSelector(
57+
sessionSelector,
58+
(session) => session.publicKey,
59+
);
60+
export const mnemonicPhraseSelector = createSelector(
61+
sessionSelector,
62+
(session) => session.mnemonicPhrase,
63+
);
64+
export const hasPrivateKeySelector = createSelector(
65+
sessionSelector,
66+
(session) => !!session.privateKey.length,
67+
);
68+
export const privateKeySelector = createSelector(
69+
sessionSelector,
70+
(session) => session.privateKey,
71+
);

src/background/helpers/session.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import {
2+
grantAccountAccess,
3+
store,
4+
timeoutAccountAccess,
5+
} from "../ducks/session";
6+
7+
const SESSION_LENGTH = 5;
8+
9+
export class SessionTimer {
10+
DURATION = 1000 * 60 * SESSION_LENGTH;
11+
constructor(duration?: number) {
12+
this.DURATION = duration || this.DURATION;
13+
}
14+
15+
startSession(key: { privateKey: string }) {
16+
store.dispatch(grantAccountAccess(key));
17+
setTimeout(() => {
18+
store.dispatch(timeoutAccountAccess());
19+
}, this.DURATION);
20+
}
21+
}

src/background/messageListener/externalMessageListener.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,8 @@ import { ExternalRequest as Request } from "api/types";
44
import { EXTERNAL_SERVICE_TYPES } from "statics";
55
import { removeQueryParam } from "helpers";
66
import { Sender, SendResponseInterface } from "../types";
7-
import {
8-
responseQueue,
9-
uiData,
10-
transactionQueue,
11-
} from "./internalMessageListener";
7+
import { responseQueue, transactionQueue } from "./internalMessageListener";
8+
import { store, publicKeySelector } from "../ducks/session";
129

1310
const WHITELIST_ID = "whitelist";
1411
const WINDOW_DIMENSIONS = "height=600,width=357";
@@ -22,14 +19,15 @@ const externalMessageListener = (
2219
// TODO: add check to make sure this origin is on whitelist
2320
const whitelistStr = localStorage.getItem(WHITELIST_ID) || "";
2421
const whitelist = whitelistStr.split(",");
22+
const publicKey = publicKeySelector(store.getState());
2523

2624
const { tab } = sender;
2725
const tabUrl = tab?.url ? tab.url : "";
2826

2927
if (whitelist.includes(removeQueryParam(tabUrl))) {
30-
if (uiData.publicKey) {
28+
if (publicKey) {
3129
// okay, the requester checks out and we have public key, send it
32-
sendResponse({ publicKey: uiData.publicKey });
30+
sendResponse({ publicKey });
3331
return;
3432
}
3533
}
@@ -46,7 +44,7 @@ const externalMessageListener = (
4644
// queue it up, we'll let user confirm the url looks okay and then we'll send publicKey
4745
// if we're good, of course
4846
if (url === tabUrl) {
49-
sendResponse({ publicKey: uiData.publicKey });
47+
sendResponse({ publicKey });
5048
} else {
5149
sendResponse({ error: "User declined access" });
5250
}

src/background/messageListener/internalMessageListener.ts

Lines changed: 41 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,25 @@ import { SERVICE_TYPES, APPLICATION_STATE, SERVER_URL } from "statics";
66
import { Response as Request } from "api/types";
77
import { removeQueryParam } from "helpers";
88
import { Sender, SendResponseInterface } from "../types";
9+
import {
10+
hasPrivateKeySelector,
11+
privateKeySelector,
12+
store,
13+
logIn,
14+
logOut,
15+
mnemonicPhraseSelector,
16+
publicKeySelector,
17+
} from "../ducks/session";
18+
import { SessionTimer } from "../helpers/session";
919

1020
const server = new StellarSdk.Server(SERVER_URL);
1121

12-
let KEY_STORE: { privateKey: string } | null = null;
13-
14-
interface UiData {
15-
publicKey: string;
16-
mnemonicPhrase: string;
17-
[key: string]: string;
18-
}
19-
20-
export const uiData: UiData = {
21-
publicKey: "",
22-
mnemonicPhrase: "",
23-
};
24-
2522
const KEY_ID = "keyId";
2623
const WHITELIST_ID = "whitelist";
2724
const APPLICATION_ID = "applicationState";
2825

26+
const sessionTimer = new SessionTimer();
27+
2928
export const responseQueue: Array<(message?: any) => void> = [];
3029
export const transactionQueue: Array<{ sign: (sourceKeys: {}) => void }> = [];
3130

@@ -55,14 +54,17 @@ const internalMessageListener = (
5554
password: string;
5655
wallet: StellarHdWallet;
5756
}) => {
58-
uiData.publicKey = wallet.getPublicKey(0);
57+
const publicKey = wallet.getPublicKey(0);
58+
const privateKey = wallet.getSecret(0);
59+
60+
store.dispatch(logIn({ publicKey, mnemonicPhrase }));
5961

6062
const keyMetadata = {
6163
key: {
6264
extra: { mnemonicPhrase },
6365
type: KeyType.plaintextKey,
64-
publicKey: uiData.publicKey,
65-
privateKey: wallet.getSecret(0),
66+
publicKey,
67+
privateKey,
6668
},
6769

6870
password,
@@ -83,8 +85,8 @@ const internalMessageListener = (
8385
const createAccount = async () => {
8486
const { password } = request;
8587

86-
uiData.mnemonicPhrase = generateMnemonic({ entropyBits: 128 });
87-
const wallet = fromMnemonic(uiData.mnemonicPhrase);
88+
const mnemonicPhrase = generateMnemonic({ entropyBits: 128 });
89+
const wallet = fromMnemonic(mnemonicPhrase);
8890

8991
try {
9092
const response = await fetch(
@@ -99,30 +101,31 @@ const internalMessageListener = (
99101
throw new Error("Error creating account");
100102
}
101103

102-
_storeAccount({
104+
await _storeAccount({
103105
password,
104106
wallet,
105-
mnemonicPhrase: uiData.mnemonicPhrase,
107+
mnemonicPhrase,
106108
});
107109
localStorage.setItem(APPLICATION_ID, APPLICATION_STATE.PASSWORD_CREATED);
108110

109-
sendResponse({ publicKey: uiData.publicKey });
111+
sendResponse({ publicKey: publicKeySelector(store.getState()) });
110112
};
111113

112114
const loadAccount = () => {
113115
sendResponse({
114-
publicKey: uiData.publicKey,
116+
publicKey: publicKeySelector(store.getState()),
115117
applicationState: localStorage.getItem(APPLICATION_ID) || "",
116118
});
117119
};
118120

119121
const getMnemonicPhrase = () => {
120-
sendResponse({ mnemonicPhrase: uiData.mnemonicPhrase });
122+
sendResponse({ mnemonicPhrase: mnemonicPhraseSelector(store.getState()) });
121123
};
122124

123125
const confirmMnemonicPhrase = () => {
124126
const isCorrectPhrase =
125-
uiData.mnemonicPhrase === request.mnemonicPhraseToConfirm;
127+
mnemonicPhraseSelector(store.getState()) ===
128+
request.mnemonicPhraseToConfirm;
126129

127130
const applicationState = isCorrectPhrase
128131
? APPLICATION_STATE.MNEMONIC_PHRASE_CONFIRMED
@@ -158,13 +161,14 @@ const internalMessageListener = (
158161
}
159162

160163
sendResponse({
161-
publicKey: uiData.publicKey,
164+
publicKey: publicKeySelector(store.getState()),
162165
applicationState: localStorage.getItem(APPLICATION_ID) || "",
163166
});
164167
};
165168

166169
const confirmPassword = async () => {
167170
const { password } = request;
171+
const state = store.getState();
168172
let keyStore;
169173
try {
170174
keyStore = await keyManager.loadKey(
@@ -175,14 +179,16 @@ const internalMessageListener = (
175179
console.error(e);
176180
}
177181
let publicKey = "";
182+
let privateKey = "";
178183
if (keyStore) {
179-
({ publicKey } = keyStore);
180-
uiData.publicKey = publicKey;
181-
KEY_STORE = keyStore;
184+
({ privateKey, publicKey } = keyStore);
185+
store.dispatch(logIn({ publicKey }));
186+
sessionTimer.startSession({ privateKey });
182187
}
183188

184189
sendResponse({
185-
publicKey: uiData.publicKey,
190+
publicKey: publicKeySelector(state),
191+
hasPrivateKey: hasPrivateKeySelector(state),
186192
applicationState: localStorage.getItem(APPLICATION_ID) || "",
187193
});
188194
};
@@ -216,8 +222,8 @@ const internalMessageListener = (
216222
};
217223

218224
const signTransaction = async () => {
219-
if (KEY_STORE) {
220-
const { privateKey } = KEY_STORE;
225+
const privateKey = privateKeySelector(store.getState());
226+
if (privateKey.length) {
221227
const sourceKeys = StellarSdk.Keypair.fromSecret(privateKey);
222228

223229
let response;
@@ -239,6 +245,8 @@ const internalMessageListener = (
239245
transactionResponse(response);
240246
sendResponse({});
241247
}
248+
} else {
249+
sendResponse({ error: "Session timed out" });
242250
}
243251
};
244252

@@ -251,12 +259,10 @@ const internalMessageListener = (
251259
};
252260

253261
const signOut = () => {
254-
Object.keys(uiData).forEach((key) => {
255-
uiData[key] = "";
256-
});
262+
store.dispatch(logOut());
257263

258264
sendResponse({
259-
publicKey: uiData.publicKey,
265+
publicKey: publicKeySelector(store.getState()),
260266
applicationState: localStorage.getItem(APPLICATION_ID) || "",
261267
});
262268
};

src/background/types/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export interface Sender {
44

55
export interface SendResponseInterface {
66
publicKey?: string;
7+
hasPrivateKey?: boolean;
78
applicationState?: string;
89
mnemonicPhrase?: string;
910
isCorrectPhrase?: boolean;

0 commit comments

Comments
 (0)