mirror of
https://github.com/balena-io/etcher.git
synced 2025-04-19 21:07:18 +00:00

- upgrade pretty_bytes to 6.1.1 - upgrade electron-remote to 2.1.0 - upgrade semver to 7.5.4 + @types/semver to 7.5.6 - upgrade chai to 4.3.11 + @types/chai to 4.3.10 - upgrade mocha to 10.2.0 + @types/mocha to 10.0.6 - upgrade sinon to 17.0.1 + @types/sinon to 17.0.2 - remove useless @types - upgrade @svgr/webpack to 8.1.0 - upgrade @sentry/electron to 4.15.1 - upgrade tslib to 2.6.2 - upgrade immutable to 4.3.4 - upgrade redux to 4.2.1 - upgrade ts-node to 10.9.2 & ts-loader to 9.5.1 - remove mini-css-extract-plugin - upgrade husky to 8.0.3 - upgrade uuid to 9.0.1 - upgrade lint-staged to 15.2.1 - upgrade @types/node to 18.11.9 - upgrade @fortawesome/fontawesome-free to 6.5.1 - upgrade i18next to 23.7.8 & react-i18next to 11.18.6 - bump react, react-dom + related @types to 17.0.2 and rendition to 35.1.0 - fix getuid for ts - fix @types/react being in wrong deps - upgrade @types/tmp to 0.2.6 - upgrade typescript to 5.3.3 - upgrade @types/mime-types to 2.1.4 - remove d3 from deps - upgrade electron-updater to 6.1.7 - upgrade rendition to 35.1.2 - upgrade node-ipc to 9.2.3 - upgrade @types/node-ipc to 9.2.3 - upgrade electron to 27.1.3 - upgrade @electron-forge/* to 7.2.0 - upgrade @reforged/marker-appimage to 3.3.2 - upgrade style-loader to 3.3.3 - upgrade balena-lint to 7.2.4 - run CI with node 18.19 - add xxhash-addon to sidecar assets Change-type: patch
204 lines
5.2 KiB
TypeScript
204 lines
5.2 KiB
TypeScript
/*
|
|
* 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 ipc from 'node-ipc';
|
|
import { Dictionary, values } from 'lodash';
|
|
|
|
import type { MultiDestinationProgress } from 'etcher-sdk/build/multi-write';
|
|
|
|
import { toJSON } from '../shared/errors';
|
|
import { GENERAL_ERROR, SUCCESS } from '../shared/exit-codes';
|
|
import { delay } from '../shared/utils';
|
|
import { WriteOptions } from './types/types';
|
|
import { write, cleanup } from './child-writer';
|
|
import { startScanning } from './scanner';
|
|
import { getSourceMetadata } from './source-metadata';
|
|
import { DrivelistDrive } from '../shared/drive-constraints';
|
|
|
|
ipc.config.id = process.env.IPC_CLIENT_ID as string;
|
|
ipc.config.socketRoot = process.env.IPC_SOCKET_ROOT as string;
|
|
|
|
// NOTE: Ensure this isn't disabled, as it will cause
|
|
// the stdout maxBuffer size to be exceeded when flashing
|
|
ipc.config.silent = true;
|
|
|
|
// > If set to 0, the client will NOT try to reconnect.
|
|
// See https://github.com/RIAEvangelist/node-ipc/
|
|
//
|
|
// The purpose behind this change is for this process
|
|
// to emit a "disconnect" event as soon as the GUI
|
|
// process is closed, so we can kill this process as well.
|
|
|
|
// @ts-ignore (0 is a valid value for stopRetrying and is not the same as false)
|
|
ipc.config.stopRetrying = 0;
|
|
|
|
const DISCONNECT_DELAY = 100;
|
|
const IPC_SERVER_ID = process.env.IPC_SERVER_ID as string;
|
|
|
|
/**
|
|
* @summary Send a message to the IPC server
|
|
*/
|
|
function emit(channel: string, message?: any) {
|
|
ipc.of[IPC_SERVER_ID].emit(channel, message);
|
|
}
|
|
|
|
/**
|
|
* @summary Send a log debug message to the IPC server
|
|
*/
|
|
function log(message: string) {
|
|
if (console?.log) {
|
|
console.log(message);
|
|
}
|
|
emit('log', message);
|
|
}
|
|
|
|
/**
|
|
* @summary Terminate the child process
|
|
*/
|
|
async function terminate(exitCode: number) {
|
|
ipc.disconnect(IPC_SERVER_ID);
|
|
await cleanup(Date.now());
|
|
process.nextTick(() => {
|
|
process.exit(exitCode || SUCCESS);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @summary Handle errors
|
|
*/
|
|
async function handleError(error: Error) {
|
|
emit('error', toJSON(error));
|
|
await delay(DISCONNECT_DELAY);
|
|
await terminate(GENERAL_ERROR);
|
|
}
|
|
|
|
/**
|
|
* @summary Abort handler
|
|
* @example
|
|
*/
|
|
const onAbort = async (exitCode: number) => {
|
|
log('Abort');
|
|
emit('abort');
|
|
await delay(DISCONNECT_DELAY);
|
|
await terminate(exitCode);
|
|
};
|
|
|
|
const onSkip = async (exitCode: number) => {
|
|
log('Skip validation');
|
|
emit('skip');
|
|
await delay(DISCONNECT_DELAY);
|
|
await terminate(exitCode);
|
|
};
|
|
|
|
ipc.connectTo(IPC_SERVER_ID, () => {
|
|
// Gracefully exit on the following cases. If the parent
|
|
// process detects that child exit successfully but
|
|
// no flashing information is available, then it will
|
|
// assume that the child died halfway through.
|
|
|
|
process.once('uncaughtException', handleError);
|
|
|
|
process.once('SIGINT', async () => {
|
|
await terminate(SUCCESS);
|
|
});
|
|
|
|
process.once('SIGTERM', async () => {
|
|
await terminate(SUCCESS);
|
|
});
|
|
|
|
// The IPC server failed. Abort.
|
|
ipc.of[IPC_SERVER_ID].on('error', async () => {
|
|
await terminate(SUCCESS);
|
|
});
|
|
|
|
// The IPC server was disconnected. Abort.
|
|
ipc.of[IPC_SERVER_ID].on('disconnect', async () => {
|
|
await terminate(SUCCESS);
|
|
});
|
|
|
|
ipc.of[IPC_SERVER_ID].on('sourceMetadata', async (params) => {
|
|
const { selected, SourceType, auth } = JSON.parse(params);
|
|
try {
|
|
const sourceMatadata = await getSourceMetadata(
|
|
selected,
|
|
SourceType,
|
|
auth,
|
|
);
|
|
emitSourceMetadata(sourceMatadata);
|
|
} catch (error: any) {
|
|
emitFail(error);
|
|
}
|
|
});
|
|
|
|
ipc.of[IPC_SERVER_ID].on('scan', async () => {
|
|
startScanning();
|
|
});
|
|
|
|
// write handler
|
|
ipc.of[IPC_SERVER_ID].on('write', async (options: WriteOptions) => {
|
|
// Remove leftover tmp files older than 1 hour
|
|
cleanup(Date.now() - 60 * 60 * 1000);
|
|
|
|
let exitCode = SUCCESS;
|
|
|
|
ipc.of[IPC_SERVER_ID].on('cancel', () => onAbort(exitCode));
|
|
|
|
ipc.of[IPC_SERVER_ID].on('skip', () => onSkip(exitCode));
|
|
|
|
const results = await write(options);
|
|
|
|
if (results.errors.length > 0) {
|
|
results.errors = results.errors.map((error: any) => {
|
|
return toJSON(error);
|
|
});
|
|
exitCode = GENERAL_ERROR;
|
|
}
|
|
|
|
emit('done', { results });
|
|
await delay(DISCONNECT_DELAY);
|
|
await terminate(exitCode);
|
|
});
|
|
|
|
ipc.of[IPC_SERVER_ID].on('connect', () => {
|
|
log(
|
|
`Successfully connected to IPC server: ${IPC_SERVER_ID}, socket root ${ipc.config.socketRoot}`,
|
|
);
|
|
emit('ready', {});
|
|
});
|
|
});
|
|
|
|
function emitLog(message: string) {
|
|
log(message);
|
|
}
|
|
|
|
function emitState(state: MultiDestinationProgress) {
|
|
emit('state', state);
|
|
}
|
|
|
|
function emitFail(data: any) {
|
|
emit('fail', data);
|
|
}
|
|
|
|
function emitDrives(drives: Dictionary<DrivelistDrive>) {
|
|
emit('drives', JSON.stringify(values(drives)));
|
|
}
|
|
|
|
function emitSourceMetadata(sourceMetadata: any) {
|
|
emit('sourceMetadata', JSON.stringify(sourceMetadata));
|
|
}
|
|
|
|
export { emitLog, emitState, emitFail, emitDrives, emitSourceMetadata };
|