mirror of
https://github.com/balena-io/etcher.git
synced 2025-07-17 00:06:32 +00:00
Simplify settings
Change-type: patch
This commit is contained in:
parent
ba39ff433d
commit
ffe281f25d
@ -165,17 +165,16 @@ const COMPUTE_MODULE_DESCRIPTIONS: _.Dictionary<string> = {
|
|||||||
[USB_PRODUCT_ID_BCM2710_BOOT]: 'Compute Module 3',
|
[USB_PRODUCT_ID_BCM2710_BOOT]: 'Compute Module 3',
|
||||||
};
|
};
|
||||||
|
|
||||||
let BLACKLISTED_DRIVES: string[] = [];
|
async function driveIsAllowed(drive: {
|
||||||
|
|
||||||
function driveIsAllowed(drive: {
|
|
||||||
devicePath: string;
|
devicePath: string;
|
||||||
device: string;
|
device: string;
|
||||||
raw: string;
|
raw: string;
|
||||||
}) {
|
}) {
|
||||||
|
const driveBlacklist = (await settings.get('driveBlacklist')) || [];
|
||||||
return !(
|
return !(
|
||||||
BLACKLISTED_DRIVES.includes(drive.devicePath) ||
|
driveBlacklist.includes(drive.devicePath) ||
|
||||||
BLACKLISTED_DRIVES.includes(drive.device) ||
|
driveBlacklist.includes(drive.device) ||
|
||||||
BLACKLISTED_DRIVES.includes(drive.raw)
|
driveBlacklist.includes(drive.raw)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -240,9 +239,9 @@ function getDrives() {
|
|||||||
return _.keyBy(availableDrives.getDrives() || [], 'device');
|
return _.keyBy(availableDrives.getDrives() || [], 'device');
|
||||||
}
|
}
|
||||||
|
|
||||||
function addDrive(drive: Drive) {
|
async function addDrive(drive: Drive) {
|
||||||
const preparedDrive = prepareDrive(drive);
|
const preparedDrive = prepareDrive(drive);
|
||||||
if (!driveIsAllowed(preparedDrive)) {
|
if (!(await driveIsAllowed(preparedDrive))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const drives = getDrives();
|
const drives = getDrives();
|
||||||
@ -330,14 +329,8 @@ window.addEventListener('beforeunload', async (event) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
async function main(): Promise<void> {
|
async function main() {
|
||||||
try {
|
await ledsInit();
|
||||||
await settings.load();
|
|
||||||
} catch (error) {
|
|
||||||
exceptionReporter.report(error);
|
|
||||||
}
|
|
||||||
BLACKLISTED_DRIVES = settings.get('driveBlacklist') || [];
|
|
||||||
ledsInit();
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
React.createElement(MainPage),
|
React.createElement(MainPage),
|
||||||
document.getElementById('main'),
|
document.getElementById('main'),
|
||||||
|
@ -37,10 +37,10 @@ export class FeaturedProject extends React.Component<
|
|||||||
this.state = { endpoint: null };
|
this.state = { endpoint: null };
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentDidMount() {
|
public async componentDidMount() {
|
||||||
try {
|
try {
|
||||||
const endpoint =
|
const endpoint =
|
||||||
settings.get('featuredProjectEndpoint') ||
|
(await settings.get('featuredProjectEndpoint')) ||
|
||||||
'https://assets.balena.io/etcher-featured/index.html';
|
'https://assets.balena.io/etcher-featured/index.html';
|
||||||
this.setState({ endpoint });
|
this.setState({ endpoint });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -91,7 +91,7 @@ export class SafeWebview extends React.PureComponent<
|
|||||||
url.searchParams.set(API_VERSION_PARAM, API_VERSION);
|
url.searchParams.set(API_VERSION_PARAM, API_VERSION);
|
||||||
url.searchParams.set(
|
url.searchParams.set(
|
||||||
OPT_OUT_ANALYTICS_PARAM,
|
OPT_OUT_ANALYTICS_PARAM,
|
||||||
(!settings.get('errorReporting')).toString(),
|
(!settings.getSync('errorReporting')).toString(),
|
||||||
);
|
);
|
||||||
this.entryHref = url.href;
|
this.entryHref = url.href;
|
||||||
// Events steal 'this'
|
// Events steal 'this'
|
||||||
@ -192,15 +192,13 @@ export class SafeWebview extends React.PureComponent<
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Open link in browser if it's opened as a 'foreground-tab'
|
// Open link in browser if it's opened as a 'foreground-tab'
|
||||||
public static newWindow(event: electron.NewWindowEvent) {
|
public static async newWindow(event: electron.NewWindowEvent) {
|
||||||
const url = new window.URL(event.url);
|
const url = new window.URL(event.url);
|
||||||
if (
|
if (
|
||||||
_.every([
|
(url.protocol === 'http:' || url.protocol === 'https:') &&
|
||||||
url.protocol === 'http:' || url.protocol === 'https:',
|
event.disposition === 'foreground-tab' &&
|
||||||
event.disposition === 'foreground-tab',
|
|
||||||
// Don't open links if they're disabled by the env var
|
// Don't open links if they're disabled by the env var
|
||||||
!settings.get('disableExternalLinks'),
|
!(await settings.get('disableExternalLinks'))
|
||||||
])
|
|
||||||
) {
|
) {
|
||||||
electron.shell.openExternal(url.href);
|
electron.shell.openExternal(url.href);
|
||||||
}
|
}
|
||||||
|
@ -20,14 +20,12 @@ import * as _ from 'lodash';
|
|||||||
import * as os from 'os';
|
import * as os from 'os';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { Badge, Checkbox, Modal } from 'rendition';
|
import { Badge, Checkbox, Modal } from 'rendition';
|
||||||
import styled from 'styled-components';
|
|
||||||
|
|
||||||
import { version } from '../../../../../package.json';
|
import { version } from '../../../../../package.json';
|
||||||
import * as settings from '../../models/settings';
|
import * as settings from '../../models/settings';
|
||||||
import * as analytics from '../../modules/analytics';
|
import * as analytics from '../../modules/analytics';
|
||||||
import { open as openExternal } from '../../os/open-external/services/open-external';
|
import { open as openExternal } from '../../os/open-external/services/open-external';
|
||||||
|
|
||||||
const { useState } = React;
|
|
||||||
const platform = os.platform();
|
const platform = os.platform();
|
||||||
|
|
||||||
interface WarningModalProps {
|
interface WarningModalProps {
|
||||||
@ -67,7 +65,8 @@ interface Setting {
|
|||||||
hide?: boolean;
|
hide?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const settingsList: Setting[] = [
|
async function getSettingsList(): Promise<Setting[]> {
|
||||||
|
return [
|
||||||
{
|
{
|
||||||
name: 'errorReporting',
|
name: 'errorReporting',
|
||||||
label: 'Anonymously report errors and usage statistics to balena.io',
|
label: 'Anonymously report errors and usage statistics to balena.io',
|
||||||
@ -105,24 +104,42 @@ const settingsList: Setting[] = [
|
|||||||
You will be able to overwrite your system drives if you're not careful.`,
|
You will be able to overwrite your system drives if you're not careful.`,
|
||||||
confirmLabel: 'Enable unsafe mode',
|
confirmLabel: 'Enable unsafe mode',
|
||||||
},
|
},
|
||||||
hide: settings.get('disableUnsafeMode'),
|
hide: await settings.get('disableUnsafeMode'),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
}
|
||||||
|
|
||||||
interface SettingsModalProps {
|
interface SettingsModalProps {
|
||||||
toggleModal: (value: boolean) => void;
|
toggleModal: (value: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SettingsModal: any = styled(
|
export function SettingsModal({ toggleModal }: SettingsModalProps) {
|
||||||
({ toggleModal }: SettingsModalProps) => {
|
const [settingsList, setCurrentSettingsList]: [
|
||||||
|
Setting[],
|
||||||
|
React.Dispatch<React.SetStateAction<Setting[]>>,
|
||||||
|
] = React.useState([]);
|
||||||
|
React.useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
if (settingsList.length === 0) {
|
||||||
|
setCurrentSettingsList(await getSettingsList());
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
});
|
||||||
const [currentSettings, setCurrentSettings]: [
|
const [currentSettings, setCurrentSettings]: [
|
||||||
_.Dictionary<any>,
|
_.Dictionary<boolean>,
|
||||||
React.Dispatch<React.SetStateAction<_.Dictionary<any>>>,
|
React.Dispatch<React.SetStateAction<_.Dictionary<boolean>>>,
|
||||||
] = useState(settings.getAll());
|
] = React.useState({});
|
||||||
|
React.useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
if (_.isEmpty(currentSettings)) {
|
||||||
|
setCurrentSettings(await settings.getAll());
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
});
|
||||||
const [warning, setWarning]: [
|
const [warning, setWarning]: [
|
||||||
any,
|
any,
|
||||||
React.Dispatch<React.SetStateAction<any>>,
|
React.Dispatch<React.SetStateAction<any>>,
|
||||||
] = useState({});
|
] = React.useState({});
|
||||||
|
|
||||||
const toggleSetting = async (setting: string, options?: any) => {
|
const toggleSetting = async (setting: string, options?: any) => {
|
||||||
const value = currentSettings[setting];
|
const value = currentSettings[setting];
|
||||||
@ -193,8 +210,8 @@ export const SettingsModal: any = styled(
|
|||||||
<WarningModal
|
<WarningModal
|
||||||
message={warning.description}
|
message={warning.description}
|
||||||
confirmLabel={warning.confirmLabel}
|
confirmLabel={warning.confirmLabel}
|
||||||
done={() => {
|
done={async () => {
|
||||||
settings.set(warning.setting, !warning.settingValue);
|
await settings.set(warning.setting, !warning.settingValue);
|
||||||
setCurrentSettings({
|
setCurrentSettings({
|
||||||
...currentSettings,
|
...currentSettings,
|
||||||
[warning.setting]: true,
|
[warning.setting]: true,
|
||||||
@ -208,9 +225,4 @@ export const SettingsModal: any = styled(
|
|||||||
)}
|
)}
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
)`
|
|
||||||
> div:nth-child(3) {
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
@ -66,7 +66,7 @@ interface DeviceFromState {
|
|||||||
device: string;
|
device: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function init() {
|
export async function init(): Promise<void> {
|
||||||
// ledsMapping is something like:
|
// ledsMapping is something like:
|
||||||
// {
|
// {
|
||||||
// 'platform-xhci-hcd.0.auto-usb-0:1.1.1:1.0-scsi-0:0:0:0': [
|
// 'platform-xhci-hcd.0.auto-usb-0:1.1.1:1.0-scsi-0:0:0:0': [
|
||||||
@ -77,7 +77,7 @@ export function init() {
|
|||||||
// ...
|
// ...
|
||||||
// }
|
// }
|
||||||
const ledsMapping: _.Dictionary<[string, string, string]> =
|
const ledsMapping: _.Dictionary<[string, string, string]> =
|
||||||
settings.get('ledsMapping') || {};
|
(await settings.get('ledsMapping')) || {};
|
||||||
for (const [drivePath, ledsNames] of Object.entries(ledsMapping)) {
|
for (const [drivePath, ledsNames] of Object.entries(ledsMapping)) {
|
||||||
leds.set('/dev/disk/by-path/' + drivePath, new RGBLed(ledsNames));
|
leds.set('/dev/disk/by-path/' + drivePath, new RGBLed(ledsNames));
|
||||||
}
|
}
|
||||||
|
@ -1,73 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2017 balena.io
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import * as electron from 'electron';
|
|
||||||
import { promises as fs } from 'fs';
|
|
||||||
import * as path from 'path';
|
|
||||||
|
|
||||||
const JSON_INDENT = 2;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @summary Userdata directory path
|
|
||||||
* @description
|
|
||||||
* Defaults to the following:
|
|
||||||
* - `%APPDATA%/etcher` on Windows
|
|
||||||
* - `$XDG_CONFIG_HOME/etcher` or `~/.config/etcher` on Linux
|
|
||||||
* - `~/Library/Application Support/etcher` on macOS
|
|
||||||
* See https://electronjs.org/docs/api/app#appgetpathname
|
|
||||||
*
|
|
||||||
* NOTE: The ternary is due to this module being loaded both,
|
|
||||||
* Electron's main process and renderer process
|
|
||||||
*/
|
|
||||||
const USER_DATA_DIR = electron.app
|
|
||||||
? electron.app.getPath('userData')
|
|
||||||
: electron.remote.app.getPath('userData');
|
|
||||||
|
|
||||||
const CONFIG_PATH = path.join(USER_DATA_DIR, 'config.json');
|
|
||||||
|
|
||||||
async function readConfigFile(filename: string): Promise<any> {
|
|
||||||
let contents = '{}';
|
|
||||||
try {
|
|
||||||
contents = await fs.readFile(filename, { encoding: 'utf8' });
|
|
||||||
} catch (error) {
|
|
||||||
if (error.code !== 'ENOENT') {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return JSON.parse(contents);
|
|
||||||
} catch (parseError) {
|
|
||||||
console.error(parseError);
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function writeConfigFile(filename: string, data: any): Promise<any> {
|
|
||||||
await fs.writeFile(filename, JSON.stringify(data, null, JSON_INDENT));
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function readAll(): Promise<any> {
|
|
||||||
return await readConfigFile(CONFIG_PATH);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function writeAll(settings: any): Promise<any> {
|
|
||||||
return await writeConfigFile(CONFIG_PATH, settings);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function clear(): Promise<void> {
|
|
||||||
await fs.unlink(CONFIG_PATH);
|
|
||||||
}
|
|
@ -15,56 +15,93 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import * as _debug from 'debug';
|
import * as _debug from 'debug';
|
||||||
|
import * as electron from 'electron';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
|
import { promises as fs } from 'fs';
|
||||||
|
import { join } from 'path';
|
||||||
|
|
||||||
import * as packageJSON from '../../../../package.json';
|
import * as packageJSON from '../../../../package.json';
|
||||||
import * as localSettings from './local-settings';
|
|
||||||
|
|
||||||
const debug = _debug('etcher:models:settings');
|
const debug = _debug('etcher:models:settings');
|
||||||
|
|
||||||
|
const JSON_INDENT = 2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Userdata directory path
|
||||||
|
* @description
|
||||||
|
* Defaults to the following:
|
||||||
|
* - `%APPDATA%/etcher` on Windows
|
||||||
|
* - `$XDG_CONFIG_HOME/etcher` or `~/.config/etcher` on Linux
|
||||||
|
* - `~/Library/Application Support/etcher` on macOS
|
||||||
|
* See https://electronjs.org/docs/api/app#appgetpathname
|
||||||
|
*
|
||||||
|
* NOTE: The ternary is due to this module being loaded both,
|
||||||
|
* Electron's main process and renderer process
|
||||||
|
*/
|
||||||
|
const USER_DATA_DIR = electron.app
|
||||||
|
? electron.app.getPath('userData')
|
||||||
|
: electron.remote.app.getPath('userData');
|
||||||
|
|
||||||
|
const CONFIG_PATH = join(USER_DATA_DIR, 'config.json');
|
||||||
|
|
||||||
|
async function readConfigFile(filename: string): Promise<_.Dictionary<any>> {
|
||||||
|
let contents = '{}';
|
||||||
|
try {
|
||||||
|
contents = await fs.readFile(filename, { encoding: 'utf8' });
|
||||||
|
} catch (error) {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return JSON.parse(contents);
|
||||||
|
} catch (parseError) {
|
||||||
|
console.error(parseError);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// exported for tests
|
// exported for tests
|
||||||
export const DEFAULT_SETTINGS: _.Dictionary<any> = {
|
export async function readAll() {
|
||||||
|
return await readConfigFile(CONFIG_PATH);
|
||||||
|
}
|
||||||
|
|
||||||
|
// exported for tests
|
||||||
|
export async function writeConfigFile(
|
||||||
|
filename: string,
|
||||||
|
data: _.Dictionary<any>,
|
||||||
|
): Promise<void> {
|
||||||
|
await fs.writeFile(filename, JSON.stringify(data, null, JSON_INDENT));
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_SETTINGS: _.Dictionary<any> = {
|
||||||
unsafeMode: false,
|
unsafeMode: false,
|
||||||
errorReporting: true,
|
errorReporting: true,
|
||||||
unmountOnSuccess: true,
|
unmountOnSuccess: true,
|
||||||
validateWriteOnSuccess: true,
|
validateWriteOnSuccess: true,
|
||||||
updatesEnabled:
|
updatesEnabled: !_.includes(['rpm', 'deb'], packageJSON.packageType),
|
||||||
packageJSON.updates.enabled &&
|
|
||||||
!_.includes(['rpm', 'deb'], packageJSON.packageType),
|
|
||||||
desktopNotifications: true,
|
desktopNotifications: true,
|
||||||
autoBlockmapping: true,
|
autoBlockmapping: true,
|
||||||
decompressFirst: true,
|
decompressFirst: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
let settings = _.cloneDeep(DEFAULT_SETTINGS);
|
const settings = _.cloneDeep(DEFAULT_SETTINGS);
|
||||||
|
|
||||||
/**
|
async function load(): Promise<void> {
|
||||||
* @summary Reset settings to their default values
|
|
||||||
*/
|
|
||||||
export async function reset(): Promise<void> {
|
|
||||||
debug('reset');
|
|
||||||
settings = _.cloneDeep(DEFAULT_SETTINGS);
|
|
||||||
return await localSettings.writeAll(settings);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @summary Extend the application state with the local settings
|
|
||||||
*/
|
|
||||||
export async function load(): Promise<void> {
|
|
||||||
debug('load');
|
debug('load');
|
||||||
const loadedSettings = await localSettings.readAll();
|
// Use exports.readAll() so it can be mocked in tests
|
||||||
|
const loadedSettings = await exports.readAll();
|
||||||
_.assign(settings, loadedSettings);
|
_.assign(settings, loadedSettings);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
const loaded = load();
|
||||||
* @summary Set a setting value
|
|
||||||
*/
|
|
||||||
export async function set(key: string, value: any): Promise<void> {
|
export async function set(key: string, value: any): Promise<void> {
|
||||||
debug('set', key, value);
|
debug('set', key, value);
|
||||||
|
await loaded;
|
||||||
const previousValue = settings[key];
|
const previousValue = settings[key];
|
||||||
settings[key] = value;
|
settings[key] = value;
|
||||||
try {
|
try {
|
||||||
await localSettings.writeAll(settings);
|
// Use exports.writeConfigFile() so it can be mocked in tests
|
||||||
|
await exports.writeConfigFile(CONFIG_PATH, settings);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Revert to previous value if persisting settings failed
|
// Revert to previous value if persisting settings failed
|
||||||
settings[key] = previousValue;
|
settings[key] = previousValue;
|
||||||
@ -72,24 +109,17 @@ export async function set(key: string, value: any): Promise<void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export async function get(key: string): Promise<any> {
|
||||||
* @summary Get a setting value
|
await loaded;
|
||||||
*/
|
return getSync(key);
|
||||||
export function get(key: string): any {
|
}
|
||||||
|
|
||||||
|
export function getSync(key: string): any {
|
||||||
return _.cloneDeep(settings[key]);
|
return _.cloneDeep(settings[key]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export async function getAll() {
|
||||||
* @summary Check if setting value exists
|
|
||||||
*/
|
|
||||||
export function has(key: string): boolean {
|
|
||||||
return settings[key] != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @summary Get all setting values
|
|
||||||
*/
|
|
||||||
export function getAll() {
|
|
||||||
debug('getAll');
|
debug('getAll');
|
||||||
|
await loaded;
|
||||||
return _.cloneDeep(settings);
|
return _.cloneDeep(settings);
|
||||||
}
|
}
|
||||||
|
@ -165,7 +165,7 @@ function storeReducer(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const shouldAutoselectAll = Boolean(
|
const shouldAutoselectAll = Boolean(
|
||||||
settings.get('disableExplicitDriveSelection'),
|
settings.getSync('disableExplicitDriveSelection'),
|
||||||
);
|
);
|
||||||
const AUTOSELECT_DRIVE_COUNT = 1;
|
const AUTOSELECT_DRIVE_COUNT = 1;
|
||||||
const nonStaleSelectedDevices = nonStaleNewState
|
const nonStaleSelectedDevices = nonStaleNewState
|
||||||
|
@ -22,33 +22,29 @@ import { getConfig, hasProps } from '../../../shared/utils';
|
|||||||
import * as settings from '../models/settings';
|
import * as settings from '../models/settings';
|
||||||
import { store } from '../models/store';
|
import { store } from '../models/store';
|
||||||
|
|
||||||
const sentryToken =
|
|
||||||
settings.get('analyticsSentryToken') ||
|
|
||||||
_.get(packageJSON, ['analytics', 'sentry', 'token']);
|
|
||||||
const mixpanelToken =
|
|
||||||
settings.get('analyticsMixpanelToken') ||
|
|
||||||
_.get(packageJSON, ['analytics', 'mixpanel', 'token']);
|
|
||||||
|
|
||||||
const configUrl =
|
|
||||||
settings.get('configUrl') || 'https://balena.io/etcher/static/config.json';
|
|
||||||
|
|
||||||
const DEFAULT_PROBABILITY = 0.1;
|
const DEFAULT_PROBABILITY = 0.1;
|
||||||
|
|
||||||
const services = {
|
async function installCorvus(): Promise<void> {
|
||||||
|
const sentryToken =
|
||||||
|
(await settings.get('analyticsSentryToken')) ||
|
||||||
|
_.get(packageJSON, ['analytics', 'sentry', 'token']);
|
||||||
|
const mixpanelToken =
|
||||||
|
(await settings.get('analyticsMixpanelToken')) ||
|
||||||
|
_.get(packageJSON, ['analytics', 'mixpanel', 'token']);
|
||||||
|
resinCorvus.install({
|
||||||
|
services: {
|
||||||
sentry: sentryToken,
|
sentry: sentryToken,
|
||||||
mixpanel: mixpanelToken,
|
mixpanel: mixpanelToken,
|
||||||
};
|
},
|
||||||
|
|
||||||
resinCorvus.install({
|
|
||||||
services,
|
|
||||||
options: {
|
options: {
|
||||||
release: packageJSON.version,
|
release: packageJSON.version,
|
||||||
shouldReport: () => {
|
shouldReport: () => {
|
||||||
return settings.get('errorReporting');
|
return settings.getSync('errorReporting');
|
||||||
},
|
},
|
||||||
mixpanelDeferred: true,
|
mixpanelDeferred: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let mixpanelSample = DEFAULT_PROBABILITY;
|
let mixpanelSample = DEFAULT_PROBABILITY;
|
||||||
|
|
||||||
@ -56,8 +52,12 @@ let mixpanelSample = DEFAULT_PROBABILITY;
|
|||||||
* @summary Init analytics configurations
|
* @summary Init analytics configurations
|
||||||
*/
|
*/
|
||||||
async function initConfig() {
|
async function initConfig() {
|
||||||
|
await installCorvus();
|
||||||
let validatedConfig = null;
|
let validatedConfig = null;
|
||||||
try {
|
try {
|
||||||
|
const configUrl =
|
||||||
|
(await settings.get('configUrl')) ||
|
||||||
|
'https://balena.io/etcher/static/config.json';
|
||||||
const config = await getConfig(configUrl);
|
const config = await getConfig(configUrl);
|
||||||
const mixpanel = _.get(config, ['analytics', 'mixpanel'], {});
|
const mixpanel = _.get(config, ['analytics', 'mixpanel'], {});
|
||||||
mixpanelSample = mixpanel.probability || DEFAULT_PROBABILITY;
|
mixpanelSample = mixpanel.probability || DEFAULT_PROBABILITY;
|
||||||
|
@ -23,7 +23,9 @@ import * as settings from '../models/settings';
|
|||||||
* @summary returns true if system drives should be shown
|
* @summary returns true if system drives should be shown
|
||||||
*/
|
*/
|
||||||
function includeSystemDrives() {
|
function includeSystemDrives() {
|
||||||
return settings.get('unsafeMode') && !settings.get('disableUnsafeMode');
|
return (
|
||||||
|
settings.getSync('unsafeMode') && !settings.getSync('disableUnsafeMode')
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const adapters: sdk.scanner.adapters.Adapter[] = [
|
const adapters: sdk.scanner.adapters.Adapter[] = [
|
||||||
|
@ -136,7 +136,7 @@ interface FlashResults {
|
|||||||
* @description
|
* @description
|
||||||
* This function is extracted for testing purposes.
|
* This function is extracted for testing purposes.
|
||||||
*/
|
*/
|
||||||
export function performWrite(
|
export async function performWrite(
|
||||||
image: string,
|
image: string,
|
||||||
drives: DrivelistDrive[],
|
drives: DrivelistDrive[],
|
||||||
onProgress: sdk.multiWrite.OnProgressFunction,
|
onProgress: sdk.multiWrite.OnProgressFunction,
|
||||||
@ -144,7 +144,13 @@ export function performWrite(
|
|||||||
): Promise<{ cancelled?: boolean }> {
|
): Promise<{ cancelled?: boolean }> {
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
ipc.serve();
|
ipc.serve();
|
||||||
return new Promise((resolve, reject) => {
|
const {
|
||||||
|
unmountOnSuccess,
|
||||||
|
validateWriteOnSuccess,
|
||||||
|
autoBlockmapping,
|
||||||
|
decompressFirst,
|
||||||
|
} = await settings.getAll();
|
||||||
|
return await new Promise((resolve, reject) => {
|
||||||
ipc.server.on('error', (error) => {
|
ipc.server.on('error', (error) => {
|
||||||
terminateServer();
|
terminateServer();
|
||||||
const errorObject = errors.fromJSON(error);
|
const errorObject = errors.fromJSON(error);
|
||||||
@ -162,8 +168,8 @@ export function performWrite(
|
|||||||
driveCount: drives.length,
|
driveCount: drives.length,
|
||||||
uuid: flashState.getFlashUuid(),
|
uuid: flashState.getFlashUuid(),
|
||||||
flashInstanceUuid: flashState.getFlashUuid(),
|
flashInstanceUuid: flashState.getFlashUuid(),
|
||||||
unmountOnSuccess: settings.get('unmountOnSuccess'),
|
unmountOnSuccess,
|
||||||
validateWriteOnSuccess: settings.get('validateWriteOnSuccess'),
|
validateWriteOnSuccess,
|
||||||
};
|
};
|
||||||
|
|
||||||
ipc.server.on('fail', ({ error }: { error: Error & { code: string } }) => {
|
ipc.server.on('fail', ({ error }: { error: Error & { code: string } }) => {
|
||||||
@ -190,10 +196,10 @@ export function performWrite(
|
|||||||
destinations: drives,
|
destinations: drives,
|
||||||
source,
|
source,
|
||||||
SourceType: source.SourceType.name,
|
SourceType: source.SourceType.name,
|
||||||
validateWriteOnSuccess: settings.get('validateWriteOnSuccess'),
|
validateWriteOnSuccess,
|
||||||
autoBlockmapping: settings.get('autoBlockmapping'),
|
autoBlockmapping,
|
||||||
unmountOnSuccess: settings.get('unmountOnSuccess'),
|
unmountOnSuccess,
|
||||||
decompressFirst: settings.get('decompressFirst'),
|
decompressFirst,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -266,8 +272,8 @@ export async function flash(
|
|||||||
uuid: flashState.getFlashUuid(),
|
uuid: flashState.getFlashUuid(),
|
||||||
status: 'started',
|
status: 'started',
|
||||||
flashInstanceUuid: flashState.getFlashUuid(),
|
flashInstanceUuid: flashState.getFlashUuid(),
|
||||||
unmountOnSuccess: settings.get('unmountOnSuccess'),
|
unmountOnSuccess: await settings.get('unmountOnSuccess'),
|
||||||
validateWriteOnSuccess: settings.get('validateWriteOnSuccess'),
|
validateWriteOnSuccess: await settings.get('validateWriteOnSuccess'),
|
||||||
};
|
};
|
||||||
|
|
||||||
analytics.logEvent('Flash', analyticsData);
|
analytics.logEvent('Flash', analyticsData);
|
||||||
@ -320,7 +326,7 @@ export async function flash(
|
|||||||
/**
|
/**
|
||||||
* @summary Cancel write operation
|
* @summary Cancel write operation
|
||||||
*/
|
*/
|
||||||
export function cancel() {
|
export async function cancel() {
|
||||||
const drives = selectionState.getSelectedDevices();
|
const drives = selectionState.getSelectedDevices();
|
||||||
const analyticsData = {
|
const analyticsData = {
|
||||||
image: selectionState.getImagePath(),
|
image: selectionState.getImagePath(),
|
||||||
@ -328,8 +334,8 @@ export function cancel() {
|
|||||||
driveCount: drives.length,
|
driveCount: drives.length,
|
||||||
uuid: flashState.getFlashUuid(),
|
uuid: flashState.getFlashUuid(),
|
||||||
flashInstanceUuid: flashState.getFlashUuid(),
|
flashInstanceUuid: flashState.getFlashUuid(),
|
||||||
unmountOnSuccess: settings.get('unmountOnSuccess'),
|
unmountOnSuccess: await settings.get('unmountOnSuccess'),
|
||||||
validateWriteOnSuccess: settings.get('validateWriteOnSuccess'),
|
validateWriteOnSuccess: await settings.get('validateWriteOnSuccess'),
|
||||||
status: 'cancel',
|
status: 'cancel',
|
||||||
};
|
};
|
||||||
analytics.logEvent('Cancel', analyticsData);
|
analytics.logEvent('Cancel', analyticsData);
|
||||||
|
@ -21,9 +21,9 @@ import * as settings from '../models/settings';
|
|||||||
/**
|
/**
|
||||||
* @summary Send a notification
|
* @summary Send a notification
|
||||||
*/
|
*/
|
||||||
export function send(title: string, body: string, icon: string) {
|
export async function send(title: string, body: string, icon: string) {
|
||||||
// Bail out if desktop notifications are disabled
|
// Bail out if desktop notifications are disabled
|
||||||
if (!settings.get('desktopNotifications')) {
|
if (!(await settings.get('desktopNotifications'))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,9 +21,9 @@ import { logEvent } from '../../../modules/analytics';
|
|||||||
/**
|
/**
|
||||||
* @summary Open an external resource
|
* @summary Open an external resource
|
||||||
*/
|
*/
|
||||||
export function open(url: string) {
|
export async function open(url: string) {
|
||||||
// Don't open links if they're disabled by the env var
|
// Don't open links if they're disabled by the env var
|
||||||
if (settings.get('disableExternalLinks')) {
|
if (await settings.get('disableExternalLinks')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ const getDriveListLabel = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const shouldShowDrivesButton = () => {
|
const shouldShowDrivesButton = () => {
|
||||||
return !settings.get('disableExplicitDriveSelection');
|
return !settings.getSync('disableExplicitDriveSelection');
|
||||||
};
|
};
|
||||||
|
|
||||||
const getDriveSelectionStateSlice = () => ({
|
const getDriveSelectionStateSlice = () => ({
|
||||||
|
@ -175,7 +175,7 @@ export class MainPage extends React.Component<
|
|||||||
tabIndex={5}
|
tabIndex={5}
|
||||||
onClick={() => this.setState({ hideSettings: false })}
|
onClick={() => this.setState({ hideSettings: false })}
|
||||||
/>
|
/>
|
||||||
{!settings.get('disableExternalLinks') && (
|
{!settings.getSync('disableExternalLinks') && (
|
||||||
<Icon
|
<Icon
|
||||||
icon={<FontAwesomeIcon icon={faQuestionCircle} />}
|
icon={<FontAwesomeIcon icon={faQuestionCircle} />}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
|
@ -28,8 +28,6 @@ import * as settings from './app/models/settings';
|
|||||||
import * as analytics from './app/modules/analytics';
|
import * as analytics from './app/modules/analytics';
|
||||||
import { buildWindowMenu } from './menu';
|
import { buildWindowMenu } from './menu';
|
||||||
|
|
||||||
const configUrl =
|
|
||||||
settings.get('configUrl') || 'https://balena.io/etcher/static/config.json';
|
|
||||||
const updatablePackageTypes = ['appimage', 'nsis', 'dmg'];
|
const updatablePackageTypes = ['appimage', 'nsis', 'dmg'];
|
||||||
const packageUpdatable = _.includes(updatablePackageTypes, packageType);
|
const packageUpdatable = _.includes(updatablePackageTypes, packageType);
|
||||||
let packageUpdated = false;
|
let packageUpdated = false;
|
||||||
@ -38,7 +36,7 @@ async function checkForUpdates(interval: number) {
|
|||||||
// We use a while loop instead of a setInterval to preserve
|
// We use a while loop instead of a setInterval to preserve
|
||||||
// async execution time between each function call
|
// async execution time between each function call
|
||||||
while (!packageUpdated) {
|
while (!packageUpdated) {
|
||||||
if (settings.get('updatesEnabled')) {
|
if (await settings.get('updatesEnabled')) {
|
||||||
try {
|
try {
|
||||||
const release = await autoUpdater.checkForUpdates();
|
const release = await autoUpdater.checkForUpdates();
|
||||||
const isOutdated =
|
const isOutdated =
|
||||||
@ -56,8 +54,8 @@ async function checkForUpdates(interval: number) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createMainWindow() {
|
async function createMainWindow() {
|
||||||
const fullscreen = Boolean(settings.get('fullscreen'));
|
const fullscreen = Boolean(await settings.get('fullscreen'));
|
||||||
const defaultWidth = 800;
|
const defaultWidth = 800;
|
||||||
const defaultHeight = 480;
|
const defaultHeight = 480;
|
||||||
let width = defaultWidth;
|
let width = defaultWidth;
|
||||||
@ -116,6 +114,9 @@ function createMainWindow() {
|
|||||||
});
|
});
|
||||||
if (packageUpdatable) {
|
if (packageUpdatable) {
|
||||||
try {
|
try {
|
||||||
|
const configUrl =
|
||||||
|
(await settings.get('configUrl')) ||
|
||||||
|
'https://balena.io/etcher/static/config.json';
|
||||||
const onlineConfig = await getConfig(configUrl);
|
const onlineConfig = await getConfig(configUrl);
|
||||||
const autoUpdaterConfig = _.get(
|
const autoUpdaterConfig = _.get(
|
||||||
onlineConfig,
|
onlineConfig,
|
||||||
@ -151,19 +152,11 @@ electron.app.on('before-quit', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
async function main(): Promise<void> {
|
async function main(): Promise<void> {
|
||||||
try {
|
|
||||||
await settings.load();
|
|
||||||
} catch (error) {
|
|
||||||
// TODO: What do if loading the config fails?
|
|
||||||
console.error('Error loading settings:');
|
|
||||||
console.error(error);
|
|
||||||
} finally {
|
|
||||||
if (electron.app.isReady()) {
|
if (electron.app.isReady()) {
|
||||||
createMainWindow();
|
await createMainWindow();
|
||||||
} else {
|
} else {
|
||||||
electron.app.on('ready', createMainWindow);
|
electron.app.on('ready', createMainWindow);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
main();
|
main();
|
||||||
|
@ -4,11 +4,6 @@
|
|||||||
"displayName": "balenaEtcher",
|
"displayName": "balenaEtcher",
|
||||||
"version": "1.5.82",
|
"version": "1.5.82",
|
||||||
"packageType": "local",
|
"packageType": "local",
|
||||||
"updates": {
|
|
||||||
"enabled": true,
|
|
||||||
"sleepDays": 7,
|
|
||||||
"semverRange": "<2.0.0"
|
|
||||||
},
|
|
||||||
"main": "generated/etcher.js",
|
"main": "generated/etcher.js",
|
||||||
"description": "Flash OS images to SD cards and USB drives, safely and easily.",
|
"description": "Flash OS images to SD cards and USB drives, safely and easily.",
|
||||||
"productDescription": "Etcher is a powerful OS image flasher built with web technologies to ensure flashing an SDCard or USB drive is a pleasant and safe experience. It protects you from accidentally writing to your hard-drives, ensures every byte of data was written correctly and much more.",
|
"productDescription": "Etcher is a powerful OS image flasher built with web technologies to ensure flashing an SDCard or USB drive is a pleasant and safe experience. It protects you from accidentally writing to your hard-drives, ensures every byte of data was written correctly and much more.",
|
||||||
|
@ -18,206 +18,80 @@ import { expect } from 'chai';
|
|||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
import { stub } from 'sinon';
|
import { stub } from 'sinon';
|
||||||
|
|
||||||
import * as localSettings from '../../../lib/gui/app/models/local-settings';
|
|
||||||
import * as settings from '../../../lib/gui/app/models/settings';
|
import * as settings from '../../../lib/gui/app/models/settings';
|
||||||
|
|
||||||
async function checkError(promise: Promise<any>, fn: (err: Error) => void) {
|
async function checkError(promise: Promise<any>, fn: (err: Error) => any) {
|
||||||
try {
|
try {
|
||||||
await promise;
|
await promise;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
fn(error);
|
await fn(error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
throw new Error('Expected error was not thrown');
|
throw new Error('Expected error was not thrown');
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('Browser: settings', function () {
|
describe('Browser: settings', () => {
|
||||||
beforeEach(function () {
|
it('should be able to set and read values', async () => {
|
||||||
return settings.reset();
|
expect(await settings.get('foo')).to.be.undefined;
|
||||||
|
await settings.set('foo', true);
|
||||||
|
expect(await settings.get('foo')).to.be.true;
|
||||||
|
await settings.set('foo', false);
|
||||||
|
expect(await settings.get('foo')).to.be.false;
|
||||||
});
|
});
|
||||||
|
|
||||||
const DEFAULT_SETTINGS = _.cloneDeep(settings.DEFAULT_SETTINGS);
|
describe('.set()', () => {
|
||||||
|
it('should not change the application state if storing to the local machine results in an error', async () => {
|
||||||
it('should be able to set and read values', function () {
|
|
||||||
expect(settings.get('foo')).to.be.undefined;
|
|
||||||
return settings
|
|
||||||
.set('foo', true)
|
|
||||||
.then(() => {
|
|
||||||
expect(settings.get('foo')).to.be.true;
|
|
||||||
return settings.set('foo', false);
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
expect(settings.get('foo')).to.be.false;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('.reset()', function () {
|
|
||||||
it('should reset the settings to their default values', function () {
|
|
||||||
expect(settings.getAll()).to.deep.equal(DEFAULT_SETTINGS);
|
|
||||||
return settings
|
|
||||||
.set('foo', 1234)
|
|
||||||
.then(() => {
|
|
||||||
expect(settings.getAll()).to.not.deep.equal(DEFAULT_SETTINGS);
|
|
||||||
return settings.reset();
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
expect(settings.getAll()).to.deep.equal(DEFAULT_SETTINGS);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should reset the local settings to their default values', function () {
|
|
||||||
return settings
|
|
||||||
.set('foo', 1234)
|
|
||||||
.then(localSettings.readAll)
|
|
||||||
.then((data) => {
|
|
||||||
expect(data).to.not.deep.equal(DEFAULT_SETTINGS);
|
|
||||||
return settings.reset();
|
|
||||||
})
|
|
||||||
.then(localSettings.readAll)
|
|
||||||
.then((data) => {
|
|
||||||
expect(data).to.deep.equal(DEFAULT_SETTINGS);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('given the local settings are cleared', function () {
|
|
||||||
beforeEach(function () {
|
|
||||||
return localSettings.clear();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should set the local settings to their default values', function () {
|
|
||||||
return settings
|
|
||||||
.reset()
|
|
||||||
.then(localSettings.readAll)
|
|
||||||
.then((data) => {
|
|
||||||
expect(data).to.deep.equal(DEFAULT_SETTINGS);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('.set()', function () {
|
|
||||||
it('should store the settings to the local machine', function () {
|
|
||||||
return localSettings
|
|
||||||
.readAll()
|
|
||||||
.then((data) => {
|
|
||||||
expect(data.foo).to.be.undefined;
|
|
||||||
expect(data.bar).to.be.undefined;
|
|
||||||
return settings.set('foo', 'bar');
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
return settings.set('bar', 'baz');
|
|
||||||
})
|
|
||||||
.then(localSettings.readAll)
|
|
||||||
.then((data) => {
|
|
||||||
expect(data.foo).to.equal('bar');
|
|
||||||
expect(data.bar).to.equal('baz');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not change the application state if storing to the local machine results in an error', async function () {
|
|
||||||
await settings.set('foo', 'bar');
|
await settings.set('foo', 'bar');
|
||||||
expect(settings.get('foo')).to.equal('bar');
|
expect(await settings.get('foo')).to.equal('bar');
|
||||||
|
|
||||||
const localSettingsWriteAllStub = stub(localSettings, 'writeAll');
|
const writeConfigFileStub = stub(settings, 'writeConfigFile');
|
||||||
localSettingsWriteAllStub.returns(
|
writeConfigFileStub.returns(Promise.reject(new Error('settings error')));
|
||||||
Promise.reject(new Error('localSettings error')),
|
|
||||||
);
|
|
||||||
|
|
||||||
await checkError(settings.set('foo', 'baz'), (error) => {
|
const p = settings.set('foo', 'baz');
|
||||||
|
await checkError(p, async (error) => {
|
||||||
expect(error).to.be.an.instanceof(Error);
|
expect(error).to.be.an.instanceof(Error);
|
||||||
expect(error.message).to.equal('localSettings error');
|
expect(error.message).to.equal('settings error');
|
||||||
localSettingsWriteAllStub.restore();
|
expect(await settings.get('foo')).to.equal('bar');
|
||||||
expect(settings.get('foo')).to.equal('bar');
|
|
||||||
});
|
});
|
||||||
|
writeConfigFileStub.restore();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('.load()', function () {
|
describe('.set()', () => {
|
||||||
it('should extend the application state with the local settings content', function () {
|
it('should set an unknown key', async () => {
|
||||||
const object = {
|
expect(await settings.get('foobar')).to.be.undefined;
|
||||||
foo: 'bar',
|
await settings.set('foobar', true);
|
||||||
};
|
expect(await settings.get('foobar')).to.be.true;
|
||||||
|
|
||||||
expect(settings.getAll()).to.deep.equal(DEFAULT_SETTINGS);
|
|
||||||
|
|
||||||
return localSettings
|
|
||||||
.writeAll(object)
|
|
||||||
.then(() => {
|
|
||||||
expect(settings.getAll()).to.deep.equal(DEFAULT_SETTINGS);
|
|
||||||
return settings.load();
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
expect(settings.getAll()).to.deep.equal(
|
|
||||||
_.assign({}, DEFAULT_SETTINGS, object),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should keep the application state intact if there are no local settings', function () {
|
|
||||||
expect(settings.getAll()).to.deep.equal(DEFAULT_SETTINGS);
|
|
||||||
return localSettings
|
|
||||||
.clear()
|
|
||||||
.then(settings.load)
|
|
||||||
.then(() => {
|
|
||||||
expect(settings.getAll()).to.deep.equal(DEFAULT_SETTINGS);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('.set()', function () {
|
it('should set the key to undefined if no value', async () => {
|
||||||
it('should set an unknown key', function () {
|
|
||||||
expect(settings.get('foobar')).to.be.undefined;
|
|
||||||
return settings.set('foobar', true).then(() => {
|
|
||||||
expect(settings.get('foobar')).to.be.true;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should set the key to undefined if no value', function () {
|
|
||||||
return settings
|
|
||||||
.set('foo', 'bar')
|
|
||||||
.then(() => {
|
|
||||||
expect(settings.get('foo')).to.equal('bar');
|
|
||||||
return settings.set('foo', undefined);
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
expect(settings.get('foo')).to.be.undefined;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should store the setting to the local machine', function () {
|
|
||||||
return localSettings
|
|
||||||
.readAll()
|
|
||||||
.then((data) => {
|
|
||||||
expect(data.foo).to.be.undefined;
|
|
||||||
return settings.set('foo', 'bar');
|
|
||||||
})
|
|
||||||
.then(localSettings.readAll)
|
|
||||||
.then((data) => {
|
|
||||||
expect(data.foo).to.equal('bar');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not change the application state if storing to the local machine results in an error', async function () {
|
|
||||||
await settings.set('foo', 'bar');
|
await settings.set('foo', 'bar');
|
||||||
expect(settings.get('foo')).to.equal('bar');
|
expect(await settings.get('foo')).to.equal('bar');
|
||||||
|
await settings.set('foo', undefined);
|
||||||
|
expect(await settings.get('foo')).to.be.undefined;
|
||||||
|
});
|
||||||
|
|
||||||
const localSettingsWriteAllStub = stub(localSettings, 'writeAll');
|
it('should store the setting to the local machine', async () => {
|
||||||
localSettingsWriteAllStub.returns(
|
const data = await settings.readAll();
|
||||||
Promise.reject(new Error('localSettings error')),
|
expect(data.foo).to.be.undefined;
|
||||||
);
|
await settings.set('foo', 'bar');
|
||||||
|
const data1 = await settings.readAll();
|
||||||
|
expect(data1.foo).to.equal('bar');
|
||||||
|
});
|
||||||
|
|
||||||
await checkError(settings.set('foo', 'baz'), (error) => {
|
it('should not change the application state if storing to the local machine results in an error', async () => {
|
||||||
|
await settings.set('foo', 'bar');
|
||||||
|
expect(await settings.get('foo')).to.equal('bar');
|
||||||
|
|
||||||
|
const writeConfigFileStub = stub(settings, 'writeConfigFile');
|
||||||
|
writeConfigFileStub.returns(Promise.reject(new Error('settings error')));
|
||||||
|
|
||||||
|
await checkError(settings.set('foo', 'baz'), async (error) => {
|
||||||
expect(error).to.be.an.instanceof(Error);
|
expect(error).to.be.an.instanceof(Error);
|
||||||
expect(error.message).to.equal('localSettings error');
|
expect(error.message).to.equal('settings error');
|
||||||
localSettingsWriteAllStub.restore();
|
expect(await settings.get('foo')).to.equal('bar');
|
||||||
expect(settings.get('foo')).to.equal('bar');
|
|
||||||
});
|
});
|
||||||
});
|
writeConfigFileStub.restore();
|
||||||
});
|
|
||||||
|
|
||||||
describe('.getAll()', function () {
|
|
||||||
it('should initial return all default values', function () {
|
|
||||||
expect(settings.getAll()).to.deep.equal(DEFAULT_SETTINGS);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user