Skip to content

Commit

Permalink
feat(suite): autostart
Browse files Browse the repository at this point in the history
  • Loading branch information
martykan authored and mroz22 committed Jul 31, 2024
1 parent 6ee0cb5 commit cab0643
Show file tree
Hide file tree
Showing 14 changed files with 151 additions and 11 deletions.
6 changes: 6 additions & 0 deletions packages/suite-desktop-api/src/__tests__/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ describe('DesktopApi', () => {
api.appHide(true);
});

it('DesktopApi.appAutoStart', () => {
const spy = jest.spyOn(ipcRenderer, 'send');
api.appAutoStart(true);
expect(spy).toHaveBeenCalledWith('app/auto-start', true);
});

it('DesktopApi.checkForUpdates', () => {
const spy = jest.spyOn(ipcRenderer, 'send');
api.checkForUpdates();
Expand Down
2 changes: 2 additions & 0 deletions packages/suite-desktop-api/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export interface MainChannels {
'app/restart': void;
'app/focus': void;
'app/hide': void;
'app/auto-start': boolean;
'store/clear': void;
'theme/change': SuiteThemeVariant;
'tor/get-status': void;
Expand Down Expand Up @@ -101,6 +102,7 @@ export interface DesktopApi {
appRestart: DesktopApiSend<'app/restart'>;
appFocus: DesktopApiSend<'app/focus'>;
appHide: DesktopApiSend<'app/hide'>;
appAutoStart: DesktopApiSend<'app/auto-start'>;
// Auto-updater
checkForUpdates: DesktopApiSend<'update/check'>;
downloadUpdate: DesktopApiSend<'update/download'>;
Expand Down
1 change: 1 addition & 0 deletions packages/suite-desktop-api/src/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export const factory = <R extends StrictIpcRenderer<any, IpcRendererEvent>>(
appRestart: () => ipcRenderer.send('app/restart'),
appFocus: () => ipcRenderer.send('app/focus'),
appHide: () => ipcRenderer.send('app/hide'),
appAutoStart: (enabled: boolean) => ipcRenderer.send('app/auto-start', enabled),

// Auto-updater
checkForUpdates: isManual => {
Expand Down
58 changes: 58 additions & 0 deletions packages/suite-desktop-core/src/modules/auto-start.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/**
* Auto start handler
*/
import fs from 'fs';
import os from 'os';
import path from 'path';

import { app, ipcMain } from '../typed-electron';

import type { Module } from './index';

export const SERVICE_NAME = 'auto-start';

// Linux autostart desktop file
const LINUX_AUTOSTART_DIR = '.config/autostart/';
const LINUX_AUTOSTART_FILE = 'Trezor-Suite.desktop';
const LINUX_DESKTOP = `[Desktop Entry]
Type=Application
Version=1.0
Name=Trezor Suite
Comment=Trezor Suite startup script
Exec="${process.env.APPIMAGE || process.execPath}" --bridge-daemon
StartupNotify=false
Terminal=false
`;

const linuxAutoStart = (enabled: boolean) => {
if (enabled) {
fs.mkdirSync(path.join(os.homedir(), LINUX_AUTOSTART_DIR), { recursive: true });
fs.writeFileSync(
path.join(os.homedir(), LINUX_AUTOSTART_DIR, LINUX_AUTOSTART_FILE),
LINUX_DESKTOP,
);
fs.chmodSync(path.join(os.homedir(), LINUX_AUTOSTART_DIR, LINUX_AUTOSTART_FILE), 0o755);
} else {
fs.unlinkSync(path.join(os.homedir(), LINUX_AUTOSTART_DIR, LINUX_AUTOSTART_FILE));
}
};

export const init: Module = () => {
const { logger } = global;

ipcMain.on('app/auto-start', (_, enabled: boolean) => {
logger.debug(SERVICE_NAME, 'Auto start ' + (enabled ? 'enabled' : 'disabled'));

if (process.platform === 'linux') {
// For Linux, we use a custom implementation
linuxAutoStart(enabled);
} else {
// For Windows and macOS, we use native Electron API
app.setLoginItemSettings({
openAtLogin: enabled,
openAsHidden: true,
args: ['--bridge-daemon'],
});
}
});
};
17 changes: 7 additions & 10 deletions packages/suite-desktop-core/src/modules/bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,14 @@ const skipNewBridgeRollout = app.commandLine.hasSwitch('skip-new-bridge-rollout'

export const SERVICE_NAME = 'bridge';

const handleBridgeStatus = async (
bridge: BridgeProcess | TrezordNode,
mainWindow: Dependencies['mainWindow'],
) => {
const handleBridgeStatus = async (bridge: BridgeProcess | TrezordNode) => {
const { logger } = global;

logger.info('bridge', `Getting status`);
const status = await bridge.status();
logger.info('bridge', `Toggling bridge. Status: ${JSON.stringify(status)}`);

mainWindow.webContents.send('bridge/status', status);
ipcMain.emit('bridge/status', status);

return status;
};
Expand Down Expand Up @@ -100,7 +97,7 @@ const getBridgeInstance = (store: Dependencies['store']) => {
});
};

const load = async ({ store, mainWindow }: Dependencies) => {
const load = async ({ store }: Dependencies) => {
const { logger } = global;
const bridge = getBridgeInstance(store);

Expand All @@ -112,7 +109,7 @@ const load = async ({ store, mainWindow }: Dependencies) => {
ipcMain.handle('bridge/toggle', async ipcEvent => {
validateIpcMessage(ipcEvent);

const status = await handleBridgeStatus(bridge, mainWindow);
const status = await handleBridgeStatus(bridge);
try {
if (status.service) {
await bridge.stop();
Expand All @@ -124,7 +121,7 @@ const load = async ({ store, mainWindow }: Dependencies) => {
} catch (error) {
return { success: false, error };
} finally {
handleBridgeStatus(bridge, mainWindow);
handleBridgeStatus(bridge);
}
});

Expand Down Expand Up @@ -152,7 +149,7 @@ const load = async ({ store, mainWindow }: Dependencies) => {
} catch (error) {
return { success: false, error };
} finally {
mainWindow.webContents.send('bridge/settings', store.getBridgeSettings());
ipcMain.emit('bridge/settings', store.getBridgeSettings());
}
},
);
Expand All @@ -177,7 +174,7 @@ const load = async ({ store, mainWindow }: Dependencies) => {
`Starting (Legacy dev: ${b2t(bridgeLegacyDev)}, Legacy test: ${b2t(bridgeLegacyTest)}, Legacy: ${b2t(bridgeLegacy)}, Test: ${b2t(bridgeTest)})`,
);
await start(bridge);
handleBridgeStatus(bridge, mainWindow);
handleBridgeStatus(bridge);
} catch (err) {
logger.error(SERVICE_NAME, `Start failed: ${err.message}`);
}
Expand Down
2 changes: 2 additions & 0 deletions packages/suite-desktop-core/src/modules/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import * as requestInterceptor from './request-interceptor';
import * as coinjoin from './coinjoin';
import * as csp from './csp';
import * as fileProtocol from './file-protocol';
import * as autoStart from './auto-start';

// General modules (both dev & prod)
const MODULES = [
Expand Down Expand Up @@ -61,6 +62,7 @@ const MODULES = [
devTools,
requestInterceptor,
coinjoin,
autoStart,
// Modules used only in dev/prod mode
...(isDevEnv ? [] : [csp, fileProtocol]),
];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,4 @@ export const LOCK_TYPE = {
export const REQUEST_DEVICE_RECONNECT = '@suite/request-device-reconnect';
export const SET_EXPERIMENTAL_FEATURES = '@suite/set-experimental-features';
export const SET_SIDEBAR_WIDTH = '@suite/set-sidebar-width';
export const SET_AUTO_START = '@suite/set-auto-start';
14 changes: 13 additions & 1 deletion packages/suite/src/actions/suite/suiteActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ export type SuiteAction =
}
| { type: typeof deviceActions.requestDeviceReconnect.type }
| { type: typeof SUITE.SET_EXPERIMENTAL_FEATURES; payload?: ExperimentalFeature[] }
| { type: typeof SUITE.SET_SIDEBAR_WIDTH; payload: { width: number } };
| { type: typeof SUITE.SET_SIDEBAR_WIDTH; payload: { width: number } }
| { type: typeof SUITE.SET_AUTO_START; enabled?: boolean };

export const appChanged = createAction(
SUITE.APP_CHANGED,
Expand Down Expand Up @@ -335,3 +336,14 @@ export const lockRouter = (payload: boolean): SuiteAction => ({
type: SUITE.LOCK_ROUTER,
payload,
});

/**
* Set auto start for Suite
* @param enabled {boolean} - true if Suite should start automatically
* @returns {SuiteAction}
*/
export const setAutoStart = (enabled: boolean) => (dispatch: Dispatch) => {
desktopApi.appAutoStart(enabled);

dispatch({ type: SUITE.SET_AUTO_START, enabled });
};
1 change: 1 addition & 0 deletions packages/suite/src/constants/suite/anchors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export const enum SettingsAnchor {
ClearStorage = '@general-settings/clear-storage',
VersionWithUpdate = '@general-settings/version-with-update',
EarlyAccess = '@general-settings/early-access',
AutoStart = '@general-settings/auto-start',

BackupFailed = '@device-settings/backup-failed',
BackupRecoverySeed = '@device-settings/backup-recovery-seed',
Expand Down
1 change: 1 addition & 0 deletions packages/suite/src/middlewares/wallet/storageMiddleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ const storageMiddleware = (api: MiddlewareAPI<Dispatch, AppState>) => {
case SUITE.SET_DEFAULT_WALLET_LOADING:
case SUITE.SET_AUTODETECT:
case SUITE.SET_SIDEBAR_WIDTH:
case SUITE.SET_AUTO_START:
case SUITE.DEVICE_AUTHENTICITY_OPT_OUT:
case SUITE.EVM_CONFIRM_EXPLANATION_MODAL:
case SUITE.EVM_CLOSE_EXPLANATION_BANNER:
Expand Down
5 changes: 5 additions & 0 deletions packages/suite/src/reducers/suite/suiteReducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ export interface SuiteSettings {
defaultWalletLoading: WalletType;
experimental?: ExperimentalFeature[];
sidebarWidth: number;
autoStart?: boolean;
}

export interface SuiteState {
Expand Down Expand Up @@ -268,6 +269,10 @@ const suiteReducer = (state: SuiteState = initialState, action: Action): SuiteSt
draft.settings.sidebarWidth = action.payload.width;
break;

case SUITE.SET_AUTO_START:
draft.settings.autoStart = action.enabled;
break;

case TRANSPORT.START:
draft.transport = action.payload;
break;
Expand Down
8 changes: 8 additions & 0 deletions packages/suite/src/support/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9161,4 +9161,12 @@ export default defineMessages({
id: 'TR_OPEN_TREZOR_SUITE_DESKTOP',
defaultMessage: 'Open Trezor Suite desktop app',
},
TR_AUTO_START: {
id: 'TR_AUTO_START',
defaultMessage: 'Run Trezor Suite automatically',
},
TR_AUTO_START_DESCRIPTION: {
id: 'TR_AUTO_START_DESCRIPTION',
defaultMessage: 'Start Trezor Suite in the background when you log in to your computer.',
},
});
40 changes: 40 additions & 0 deletions packages/suite/src/views/settings/SettingsDebug/AutoStart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import styled from 'styled-components';

import { Switch } from '@trezor/components';

import { SettingsSectionItem } from 'src/components/settings';
import { ActionColumn, TextColumn, Translation } from 'src/components/suite';
import { SettingsAnchor } from 'src/constants/suite/anchors';
import { useDispatch, useSelector } from 'src/hooks/suite';
import { setAutoStart } from 'src/actions/suite/suiteActions';

const PositionedSwitch = styled.div`
align-self: center;
`;

export const AutoStart = () => {
const autoStartEnabled = useSelector(state => state.suite.settings.autoStart);
const dispatch = useDispatch();

const handleChange = () => {
dispatch(setAutoStart(!autoStartEnabled));
};

return (
<SettingsSectionItem anchorId={SettingsAnchor.AutoStart}>
<TextColumn
title={<Translation id="TR_AUTO_START" />}
description={<Translation id="TR_AUTO_START_DESCRIPTION" />}
/>
<ActionColumn>
<PositionedSwitch>
<Switch
dataTest="@autostart/toggle-switch"
isChecked={!!autoStartEnabled}
onChange={handleChange}
/>
</PositionedSwitch>
</ActionColumn>
</SettingsSectionItem>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { Backends } from './Backends';
import { selectSuiteFlags } from 'src/reducers/suite/suiteReducer';
import { useSelector } from 'src/hooks/suite';
import { PreField } from './PreField';
import { AutoStart } from './AutoStart';

export const SettingsDebug = () => {
const flags = useSelector(selectSuiteFlags);
Expand Down Expand Up @@ -53,6 +54,11 @@ export const SettingsDebug = () => {
<SettingsSection title="Testing">
<ThrowTestingError />
</SettingsSection>
{!isWeb() && (
<SettingsSection title="Application">
<AutoStart />
</SettingsSection>
)}
{!isWeb() && (
<SettingsSection title="Transport backends">
<TransportBackends />
Expand Down

0 comments on commit cab0643

Please sign in to comment.