Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: fix issues with the wallet locking #1095

Merged
merged 2 commits into from
Dec 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions src/apps/popup/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ export enum TimeoutDurationSetting {
}

export const MapTimeoutDurationSettingToValue = {
[TimeoutDurationSetting['1 min']]: 1,
[TimeoutDurationSetting['5 min']]: 5,
[TimeoutDurationSetting['15 min']]: 15,
[TimeoutDurationSetting['30 min']]: 30,
[TimeoutDurationSetting['1 hour']]: 60,
[TimeoutDurationSetting['24 hours']]: 60 * 24
[TimeoutDurationSetting['1 min']]: 1000 * 60 * 1,
[TimeoutDurationSetting['5 min']]: 1000 * 60 * 5,
[TimeoutDurationSetting['15 min']]: 1000 * 60 * 15,
[TimeoutDurationSetting['30 min']]: 1000 * 60 * 30,
[TimeoutDurationSetting['1 hour']]: 1000 * 60 * 60,
[TimeoutDurationSetting['24 hours']]: 1000 * 60 * 60 * 24
};

export const LOCK_VAULT_TIMEOUT = 1000 * 60 * 5;
32 changes: 18 additions & 14 deletions src/background/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { RootAction, getType } from 'typesafe-actions';
import {
Tabs,
action,
alarms,
browserAction,
management,
runtime,
Expand All @@ -14,6 +13,7 @@ import {
import {
getUrlOrigin,
hasHttpPrefix,
isChromeBuild,
isEqualCaseInsensitive
} from '@src/utils';

Expand All @@ -28,6 +28,7 @@ import {
bringWeb3Events
} from '@background/bring-web3-events';
import { WindowApp } from '@background/create-open-window';
import { initKeepAlive } from '@background/keep-alive';
import {
disableOnboardingFlow,
enableOnboardingFlow,
Expand Down Expand Up @@ -275,16 +276,6 @@ tabs.onUpdated.addListener(async (tabId, changeInfo, tab) => {
}
});

// Dispatch the lockVault action when the 'vaultLock' alarm is triggered
alarms.onAlarm.addListener(async function (alarm) {
const store = await getExistingMainStoreSingletonOrInit();

if (alarm.name === 'vaultLock') {
// Dispatch the lockVault action to the main store
store.dispatch(lockVault());
}
});

// NOTE: if two events are send at the same time (same function) it must reuse the same store instance
runtime.onMessage.addListener(
async (
Expand Down Expand Up @@ -731,6 +722,13 @@ runtime.onMessage.addListener(
);
}
} else {
if (action === 'keepAlive') {
// Send an asynchronous response
sendResponse({ status: 'alive' });

// returning true to indicate an asynchronous response
return true;
}
// this is added for not spamming with errors from bringweb3
if ('from' in action) {
// @ts-ignore
Expand All @@ -744,7 +742,13 @@ runtime.onMessage.addListener(
}
);

bringInitBackground({
identifier: process.env.PLATFORM_IDENTIFIER || '', // The identifier key you obtained from Bringweb3
apiEndpoint: process.env.NODE_ENV === 'production' ? 'prod' : 'sandbox'
initKeepAlive().catch(error => {
console.error('Initialization of keep alive error:', error);
});

if (isChromeBuild) {
bringInitBackground({
identifier: process.env.PLATFORM_IDENTIFIER || '', // The identifier key you obtained from Bringweb3
apiEndpoint: process.env.NODE_ENV === 'production' ? 'prod' : 'sandbox'
});
}
64 changes: 64 additions & 0 deletions src/background/keep-alive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { runtime } from 'webextension-polyfill';

import { isChromeBuild } from '@src/utils';

import { getExistingMainStoreSingletonOrInit } from '@background/redux/get-main-store';
import { selectKeysDoesExist } from '@background/redux/keys/selectors';
import { selectVaultIsLocked } from '@background/redux/session/selectors';
import { selectVaultCipherDoesExist } from '@background/redux/vault-cipher/selectors';

let keepAliveInterval: ReturnType<typeof setInterval> | null = null;

// Function to start the keep-alive interval
export function startKeepAlive() {
if (!keepAliveInterval) {
keepAliveInterval = setInterval(keepAlive, 15000); // 15 seconds
console.log('KeepAlive interval started.');
}
}

// Function to stop the keep-alive interval
export function stopKeepAlive() {
if (keepAliveInterval) {
clearInterval(keepAliveInterval);
keepAliveInterval = null;
console.log('KeepAlive interval stopped.');
}
}

// Function to check and manage the keep-alive mechanism based on vault state
async function manageKeepAlive() {
const store = await getExistingMainStoreSingletonOrInit();
const state = store.getState();

const vaultIsLocked = selectVaultIsLocked(state);
const keysDoesExist = selectKeysDoesExist(state);
const vaultCipherDoesExist = selectVaultCipherDoesExist(state);

if (vaultIsLocked && keysDoesExist && vaultCipherDoesExist) {
stopKeepAlive();
} else {
startKeepAlive();
}
}

// ping mechanism to keep background script from destroing wallet session when it's unlocked
function keepAlive() {
runtime.sendMessage('keepAlive').catch(error => {
console.error('KeepAlive error:', error);
});
}

export async function initKeepAlive() {
if (isChromeBuild) {
const store = await getExistingMainStoreSingletonOrInit();

// Initial call to manageKeepAlive to set the initial state
await manageKeepAlive();

// Subscribe to store updates
store.subscribe(async () => {
await manageKeepAlive();
});
}
}
4 changes: 4 additions & 0 deletions src/background/redux/last-activity-time/selectors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { RootState } from 'typesafe-actions';

export const selectVaultLastActivityTime = (state: RootState): number | null =>
state.lastActivityTime;
1 change: 1 addition & 0 deletions src/background/redux/root-selector.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './active-origin/selectors';
export * from './keys/selectors';
export * from './last-activity-time/selectors';
export * from './login-retry-count/selectors';
export * from './login-retry-lockout-time/selectors';
export * from './session/selectors';
Expand Down
46 changes: 29 additions & 17 deletions src/background/redux/sagas/vault-sagas.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { put, select, takeLatest } from 'redux-saga/effects';
import { getType } from 'typesafe-actions';
import { alarms } from 'webextension-polyfill';

import { getUrlOrigin } from '@src/utils';

Expand All @@ -25,6 +24,7 @@ import { Account } from '@libs/types/account';
import { accountInfoReset } from '../account-info/actions';
import { keysUpdated } from '../keys/actions';
import { lastActivityTimeRefreshed } from '../last-activity-time/actions';
import { selectVaultLastActivityTime } from '../last-activity-time/selectors';
import { loginRetryCountReseted } from '../login-retry-count/actions';
import {
encryptionKeyHashCreated,
Expand Down Expand Up @@ -234,35 +234,47 @@ function* unlockVaultSaga(action: ReturnType<typeof unlockVault>) {
}

/**
* This saga function is responsible for managing the vault timeout and locking mechanism.
* It checks if the vault exists and is not locked, retrieves the vault timeout duration setting,
* calculates the timeout duration value based on the setting, and creates an alarm to lock the vault.
* If an error occurs during the execution, it logs the error.
* Saga to handle the timeout and locking mechanism of a vault based on its last activity time and a specified timeout duration setting.
*
* The generator function calculates the time elapsed since the last activity of the vault.
* If the vault exists, it is not locked, and the last
* activity time is available, it checks if the elapsed time surpasses the timeout duration.
* If true, it triggers a vault lock action
* immediately.
* If false, it sets up a delay for the remaining time until the timeout duration is met before locking the vault.
*
*/
function* timeoutCounterSaga() {
const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));

try {
// Check if the vault exists and is not locked
const vaultDoesExist = yield* sagaSelect(selectVaultCipherDoesExist);
const vaultIsLocked = yield* sagaSelect(selectVaultIsLocked);

// Get the vault timeout duration setting
const vaultLastActivityTime = yield* sagaSelect(
selectVaultLastActivityTime
);
const vaultTimeoutDurationSetting = yield* sagaSelect(
selectTimeoutDurationSetting
);

// Calculate the timeout duration value based on the setting
const timeoutDurationValue =
MapTimeoutDurationSettingToValue[vaultTimeoutDurationSetting];

// If the vault exists and is not locked, create an alarm to lock the vault
if (vaultDoesExist && !vaultIsLocked) {
alarms.create('vaultLock', {
delayInMinutes: timeoutDurationValue
});
if (vaultDoesExist && !vaultIsLocked && vaultLastActivityTime) {
const currentTime = Date.now();
const timeoutExpired =
currentTime - vaultLastActivityTime >= timeoutDurationValue;

if (timeoutExpired) {
yield put(lockVault());
} else {
yield* sagaCall(delay, timeoutDurationValue);
yield put(lockVault());
}
}
} catch (err) {
// Log any errors that occur during the execution of the saga
console.error(err, 'err');
console.error(err);
} finally {
//
}
}

Expand Down
Loading
Loading