diff --git a/packages/extension/cypress/e2e/NFT.cy.ts b/packages/extension/cypress/e2e/NFT.cy.ts index e4d812ea1..a469277a6 100644 --- a/packages/extension/cypress/e2e/NFT.cy.ts +++ b/packages/extension/cypress/e2e/NFT.cy.ts @@ -331,6 +331,15 @@ describe('Mint', () => { sendMessage(message, cb) {} }; + (win as any).chrome.storage = { + local: { + get(key, cb) {}, + set(obj, cb) { + if (cb) cb(); + } + } + }; + cy.stub((win as any).chrome.runtime, 'sendMessage').resolves({}); } }); diff --git a/packages/extension/cypress/e2e/networks.cy.ts b/packages/extension/cypress/e2e/networks.cy.ts index 9737bd89e..de5adeb47 100644 --- a/packages/extension/cypress/e2e/networks.cy.ts +++ b/packages/extension/cypress/e2e/networks.cy.ts @@ -29,6 +29,15 @@ describe('Switch networks', () => { sendMessage(message, cb) {} }; + (win as any).chrome.storage = { + local: { + get(key, cb) {}, + set(obj, cb) { + if (cb) cb(); + } + } + }; + cy.stub((win as any).chrome.runtime, 'sendMessage').resolves({}); } }); diff --git a/packages/extension/cypress/e2e/offers.cy.ts b/packages/extension/cypress/e2e/offers.cy.ts index f30da0387..6e28d3c65 100644 --- a/packages/extension/cypress/e2e/offers.cy.ts +++ b/packages/extension/cypress/e2e/offers.cy.ts @@ -235,6 +235,15 @@ describe('Offers', () => { sendMessage(message, cb) {} }; + (win as any).chrome.storage = { + local: { + get(key, cb) {}, + set(obj, cb) { + if (cb) cb(); + } + } + }; + cy.stub((win as any).chrome.runtime, 'sendMessage').resolves({}); } }); diff --git a/packages/extension/cypress/e2e/payments.cy.ts b/packages/extension/cypress/e2e/payments.cy.ts index 14253bd3e..481ecd97d 100644 --- a/packages/extension/cypress/e2e/payments.cy.ts +++ b/packages/extension/cypress/e2e/payments.cy.ts @@ -34,6 +34,15 @@ describe('Make payment - XRP', () => { sendMessage(message, cb) {} }; + (win as any).chrome.storage = { + local: { + get(key, cb) {}, + set(obj, cb) { + if (cb) cb(); + } + } + }; + cy.stub((win as any).chrome.runtime, 'sendMessage').resolves({}); } } @@ -127,6 +136,15 @@ describe('Make payment - ETH', () => { sendMessage(message, cb) {} }; + (win as any).chrome.storage = { + local: { + get(key, cb) {}, + set(obj, cb) { + if (cb) cb(); + } + } + }; + cy.stub((win as any).chrome.runtime, 'sendMessage').resolves({}); } } @@ -218,6 +236,15 @@ describe('Make payment - SOLO', () => { (win as any).chrome.runtime = { sendMessage(message, cb) {} }; + + (win as any).chrome.storage = { + local: { + get(key, cb) {}, + set(obj, cb) { + if (cb) cb(); + } + } + }; } } ); @@ -256,6 +283,15 @@ describe('Make payment - SOLO', () => { (win as any).chrome.runtime = { sendMessage(message, cb) {} }; + + (win as any).chrome.storage = { + local: { + get(key, cb) {}, + set(obj, cb) { + if (cb) cb(); + } + } + }; } } ); diff --git a/packages/extension/cypress/e2e/send_token.cy.ts b/packages/extension/cypress/e2e/send_token.cy.ts index 7232b87ac..1bce226a2 100644 --- a/packages/extension/cypress/e2e/send_token.cy.ts +++ b/packages/extension/cypress/e2e/send_token.cy.ts @@ -30,6 +30,15 @@ describe('Send Token', () => { sendMessage(message, cb) {} }; + (win as any).chrome.storage = { + local: { + get(key, cb) {}, + set(obj, cb) { + if (cb) cb(); + } + } + }; + cy.stub((win as any).chrome.runtime, 'sendMessage').resolves({}); } }); diff --git a/packages/extension/cypress/e2e/set_account.cy.ts b/packages/extension/cypress/e2e/set_account.cy.ts index 3420351d3..a00cd7f88 100644 --- a/packages/extension/cypress/e2e/set_account.cy.ts +++ b/packages/extension/cypress/e2e/set_account.cy.ts @@ -30,6 +30,15 @@ describe('Set Account', () => { sendMessage(message, cb) {} }; + (win as any).chrome.storage = { + local: { + get(key, cb) {}, + set(obj, cb) { + if (cb) cb(); + } + } + }; + cy.stub((win as any).chrome.runtime, 'sendMessage').resolves({}); } } diff --git a/packages/extension/cypress/e2e/submit_transaction.cy.ts b/packages/extension/cypress/e2e/submit_transaction.cy.ts index fa5a20197..3e37ab6b4 100644 --- a/packages/extension/cypress/e2e/submit_transaction.cy.ts +++ b/packages/extension/cypress/e2e/submit_transaction.cy.ts @@ -107,6 +107,15 @@ describe('Submit Transaction', () => { sendMessage(message, cb) {} }; + (win as any).chrome.storage = { + local: { + get(key, cb) {}, + set(obj, cb) { + if (cb) cb(); + } + } + }; + cy.stub((win as any).chrome.runtime, 'sendMessage').resolves({}); } }); diff --git a/packages/extension/cypress/e2e/trustlines.cy.ts b/packages/extension/cypress/e2e/trustlines.cy.ts index c914bcfff..6517503a7 100644 --- a/packages/extension/cypress/e2e/trustlines.cy.ts +++ b/packages/extension/cypress/e2e/trustlines.cy.ts @@ -238,6 +238,15 @@ const navigate = (url: string, password: string) => { sendMessage(message, cb) {} }; + (win as any).chrome.storage = { + local: { + get(key, cb) {}, + set(obj, cb) { + if (cb) cb(); + } + } + }; + cy.stub((win as any).chrome.runtime, 'sendMessage').resolves({}); } }); diff --git a/packages/extension/cypress/e2e/wallet_management.cy.ts b/packages/extension/cypress/e2e/wallet_management.cy.ts index e7ef99062..6f0904c37 100644 --- a/packages/extension/cypress/e2e/wallet_management.cy.ts +++ b/packages/extension/cypress/e2e/wallet_management.cy.ts @@ -16,6 +16,15 @@ describe('Setup the initial wallet (no previous wallet)', () => { sendMessage(message, cb) {} }; + (win as any).chrome.storage = { + local: { + get(key, cb) {}, + set(obj, cb) { + if (cb) cb(); + } + } + }; + cy.stub((win as any).chrome.runtime, 'sendMessage').resolves({}); } }); @@ -239,6 +248,15 @@ describe('Add an additional wallet (with previous wallet)', () => { sendMessage(message, cb) {} }; + (win as any).chrome.storage = { + local: { + get(key, cb) {}, + set(obj, cb) { + if (cb) cb(); + } + } + }; + cy.stub((win as any).chrome.runtime, 'sendMessage').resolves({}); } }); @@ -400,6 +418,15 @@ describe('Edit wallet', () => { sendMessage(message, cb) {} }; + (win as any).chrome.storage = { + local: { + get(key, cb) {}, + set(obj, cb) { + if (cb) cb(); + } + } + }; + cy.stub((win as any).chrome.runtime, 'sendMessage').resolves({}); } }); @@ -539,6 +566,15 @@ describe('Switch wallet', () => { sendMessage(message, cb) {} }; + (win as any).chrome.storage = { + local: { + get(key, cb) {}, + set(obj, cb) { + if (cb) cb(); + } + } + }; + cy.stub((win as any).chrome.runtime, 'sendMessage').resolves({}); } }); @@ -595,6 +631,15 @@ describe('Reset password', () => { sendMessage(message, cb) {} }; + (win as any).chrome.storage = { + local: { + get(key, cb) {}, + set(obj, cb) { + if (cb) cb(); + } + } + }; + cy.stub((win as any).chrome.runtime, 'sendMessage').resolves({}); } }); diff --git a/packages/extension/src/components/pages/Login/Login.tsx b/packages/extension/src/components/pages/Login/Login.tsx index c8d5053d8..5f2c7d9e8 100644 --- a/packages/extension/src/components/pages/Login/Login.tsx +++ b/packages/extension/src/components/pages/Login/Login.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect, FC, useCallback, useRef } from 'react'; +import { useState, useEffect, FC, useCallback, useRef, ChangeEvent } from 'react'; import { Button, @@ -52,6 +52,7 @@ import { import { useWallet } from '../../../contexts'; import { useKeyUp } from '../../../hooks/useKeyUp'; import { loadData } from '../../../utils'; +import { loadRememberSessionState, saveRememberSessionState } from '../../../utils/login'; import { Logo } from '../../atoms/Logo'; export const Login: FC = () => { @@ -108,9 +109,18 @@ export const Login: FC = () => { } }, [navigate, navigateToPath, search, selectedWallet, wallets]); + useEffect(() => { + const loadRememberSession = async () => { + const loadedState = await loadRememberSessionState(); + setRememberSession(loadedState); + }; + loadRememberSession(); + }, []); + const handleUnlock = useCallback(() => { const passwordValue = passwordRef.current?.value; if (passwordValue && signIn(passwordValue, rememberSession)) { + saveRememberSessionState(rememberSession); navigateToPath(); if (process.env.NODE_ENV === 'production') { @@ -146,6 +156,11 @@ export const Login: FC = () => { navigate(RESET_PASSWORD_PATH); }, [navigate]); + const handleRememberSessionChange = (e: ChangeEvent) => { + const checked = e.target.checked; + setRememberSession(checked); + }; + return ( { control={ setRememberSession(e.target.checked)} + onChange={handleRememberSessionChange} name="rememberSession" color="primary" style={{ transform: 'scale(0.9)' }} diff --git a/packages/extension/src/constants/index.ts b/packages/extension/src/constants/index.ts index 858ba71e7..1cec7be4c 100644 --- a/packages/extension/src/constants/index.ts +++ b/packages/extension/src/constants/index.ts @@ -1,7 +1,7 @@ export * from './apiErrors'; export * from './colors'; export * from './links'; -export * from './localStorage'; +export * from './storage'; export * from './navigation'; export * from './parameters'; export * from './routes'; diff --git a/packages/extension/src/constants/localStorage.ts b/packages/extension/src/constants/storage.ts similarity index 82% rename from packages/extension/src/constants/localStorage.ts rename to packages/extension/src/constants/storage.ts index e0345df6e..c5e8cc65b 100644 --- a/packages/extension/src/constants/localStorage.ts +++ b/packages/extension/src/constants/storage.ts @@ -4,3 +4,4 @@ export const STORAGE_SEED = 'seed'; export const STORAGE_TRUSTED_APPS = 'trustedApps'; export const STORAGE_WALLETS = 'wallets'; export const STORAGE_SELECTED_WALLET = 'selectedWallet'; +export const STORAGE_REMEMBER_SESSION = 'rememberSession'; diff --git a/packages/extension/src/utils/index.ts b/packages/extension/src/utils/index.ts index efc90ce70..7e9b55501 100644 --- a/packages/extension/src/utils/index.ts +++ b/packages/extension/src/utils/index.ts @@ -8,7 +8,8 @@ export * from './link'; export * from './network'; export * from './numbersToSeed'; export * from './parseFromString'; -export * from './storage'; +export * from './storageChrome'; +export * from './storageLocal'; export * from './transaction'; export * from './truncateAddress'; export * from './truncateWalletName'; diff --git a/packages/extension/src/utils/login.test.ts b/packages/extension/src/utils/login.test.ts new file mode 100644 index 000000000..205312e0c --- /dev/null +++ b/packages/extension/src/utils/login.test.ts @@ -0,0 +1,55 @@ +import { STORAGE_REMEMBER_SESSION } from '../constants'; +import { loadRememberSessionState, saveRememberSessionState } from './login'; +import { saveInChromeStorage, loadFromChromeStorage } from './storageChrome'; + +jest.mock('./storageChrome', () => ({ + saveInChromeStorage: jest.fn(), + loadFromChromeStorage: jest.fn() +})); + +describe('saveRememberSessionState', () => { + beforeEach(() => { + saveInChromeStorage.mockClear(); + }); + + test('should save the remember session state to chrome storage', async () => { + const state = true; + saveRememberSessionState(state); + expect(saveInChromeStorage).toHaveBeenCalledWith( + STORAGE_REMEMBER_SESSION, + JSON.stringify(state) + ); + }); +}); + +describe('loadRememberSessionState', () => { + beforeEach(() => { + loadFromChromeStorage.mockClear(); + }); + + test('should load the remember session state from chrome storage', async () => { + const state = true; + (loadFromChromeStorage as jest.Mock).mockResolvedValue({ + [STORAGE_REMEMBER_SESSION]: JSON.stringify(state) + }); + + const loadedState = await loadRememberSessionState(); + expect(loadedState).toEqual(state); + }); + + test('should return false if no state is found in chrome storage', async () => { + (loadFromChromeStorage as jest.Mock).mockResolvedValue({}); + + const loadedState = await loadRememberSessionState(); + expect(loadedState).toEqual(false); + }); + + test('should return false if an error occurs while loading the state', async () => { + (loadFromChromeStorage as jest.Mock).mockRejectedValue( + new Error('Error loading state from chrome storage') + ); + + const loadedState = await loadRememberSessionState(); + expect(loadedState).toEqual(false); + }); +}); diff --git a/packages/extension/src/utils/login.ts b/packages/extension/src/utils/login.ts new file mode 100644 index 000000000..f9f1fccce --- /dev/null +++ b/packages/extension/src/utils/login.ts @@ -0,0 +1,18 @@ +import * as Sentry from '@sentry/react'; + +import { STORAGE_REMEMBER_SESSION } from '../constants'; +import { loadFromChromeStorage, saveInChromeStorage } from './storageChrome'; + +export const saveRememberSessionState = (checked: boolean) => { + saveInChromeStorage(STORAGE_REMEMBER_SESSION, JSON.stringify(checked)); +}; + +export const loadRememberSessionState = async (): Promise => { + try { + const storedState = await loadFromChromeStorage(STORAGE_REMEMBER_SESSION); + return storedState ? JSON.parse(storedState[STORAGE_REMEMBER_SESSION]) === true : false; + } catch (e) { + Sentry.captureException(e); + return false; + } +}; diff --git a/packages/extension/src/utils/network.ts b/packages/extension/src/utils/network.ts index 66777c46f..6885dbeac 100644 --- a/packages/extension/src/utils/network.ts +++ b/packages/extension/src/utils/network.ts @@ -1,7 +1,7 @@ import { NETWORK, Network } from '@gemwallet/constants'; import { NetworkData } from '@gemwallet/constants/src/network/network.types'; -import { STORAGE_CUSTOM_NETWORKS, STORAGE_NETWORK } from '../constants/localStorage'; +import { STORAGE_CUSTOM_NETWORKS, STORAGE_NETWORK } from '../constants/storage'; import { loadData, removeData, saveData } from '.'; diff --git a/packages/extension/src/utils/storageChrome.ts b/packages/extension/src/utils/storageChrome.ts new file mode 100644 index 000000000..00d651bc0 --- /dev/null +++ b/packages/extension/src/utils/storageChrome.ts @@ -0,0 +1,15 @@ +/* + * Manages the storage in Chrome storage + */ + +export const saveInChromeStorage = (key: string, value: string) => { + if (process.env.NODE_ENV === 'production') { + chrome.storage.local.set({ [key]: value }); + } +}; + +export const loadFromChromeStorage = (key: string) => { + if (process.env.NODE_ENV === 'production') { + return chrome.storage.local.get(key); + } +}; diff --git a/packages/extension/src/utils/storage.ts b/packages/extension/src/utils/storageLocal.ts similarity index 97% rename from packages/extension/src/utils/storage.ts rename to packages/extension/src/utils/storageLocal.ts index 7bce2e927..9e618c5dc 100644 --- a/packages/extension/src/utils/storage.ts +++ b/packages/extension/src/utils/storageLocal.ts @@ -1,3 +1,7 @@ +/* + * Manages localStorage data + */ + export const saveData = (key: string, value: string) => { try { localStorage.setItem(key, value); diff --git a/packages/extension/src/utils/trustedApps.test.ts b/packages/extension/src/utils/trustedApps.test.ts index f3953bfb5..81863c688 100644 --- a/packages/extension/src/utils/trustedApps.test.ts +++ b/packages/extension/src/utils/trustedApps.test.ts @@ -1,5 +1,5 @@ import { STORAGE_TRUSTED_APPS } from '../constants'; -import { loadData } from './storage'; +import { loadData } from './storageLocal'; import { checkPermissions, loadTrustedApps, diff --git a/packages/extension/src/utils/trustedApps.ts b/packages/extension/src/utils/trustedApps.ts index 79d7097b4..18d741a3b 100644 --- a/packages/extension/src/utils/trustedApps.ts +++ b/packages/extension/src/utils/trustedApps.ts @@ -1,5 +1,5 @@ -import { STORAGE_TRUSTED_APPS } from '../constants/localStorage'; -import { loadData, removeData, saveData } from './storage'; +import { STORAGE_TRUSTED_APPS } from '../constants/storage'; +import { loadData, removeData, saveData } from './storageLocal'; export enum Permission { Address = 'address', diff --git a/packages/extension/src/utils/wallet.ts b/packages/extension/src/utils/wallet.ts index e3ebc01bb..f7570bc70 100644 --- a/packages/extension/src/utils/wallet.ts +++ b/packages/extension/src/utils/wallet.ts @@ -1,7 +1,7 @@ -import { STORAGE_SELECTED_WALLET, STORAGE_WALLETS } from '../constants/localStorage'; +import { STORAGE_SELECTED_WALLET, STORAGE_WALLETS } from '../constants/storage'; import { Wallet } from '../types'; import { decrypt, encrypt } from './crypto'; -import { loadData, removeData, saveData } from './storage'; +import { loadData, removeData, saveData } from './storageLocal'; export interface WalletToSave extends Omit { name?: string;