Merge branch 'main' into bundle-ml-demo

This commit is contained in:
Abhinav 2022-12-28 16:51:30 +05:30
commit 67177bbdff
43 changed files with 976 additions and 195 deletions

57
.github/workflows/build-old.yml vendored Normal file
View file

@ -0,0 +1,57 @@
name: Build/release-old
on:
push:
tags:
- v*
jobs:
release:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
steps:
- name: Check out Git repository
uses: actions/checkout@v2
with:
submodules: recursive
- name: Install Node.js, NPM and Yarn
uses: actions/setup-node@v2.1.5
with:
node-version: 16
- name: Prepare for app notarization
if: startsWith(matrix.os, 'macos')
# Import Apple API key for app notarization on macOS
run: |
mkdir -p ~/private_keys/
echo '${{ secrets.api_key_old }}' > ~/private_keys/AuthKey_${{ secrets.api_key_id_old }}.p8
- name: Install libarchive-tools for pacman build # Related https://github.com/electron-userland/electron-builder/issues/4181
if: startsWith(matrix.os, 'ubuntu')
run: sudo apt-get install libarchive-tools
- name: Electron Builder Action
uses: samuelmeuli/action-electron-builder@v1.6.0
with:
# GitHub token, automatically provided to the action
# (No need to define this secret in the repo settings)
github_token: ${{ secrets.github_token }}
# If the commit is tagged with a version (e.g. "v1.0.0"),
# release the app after building
release: ${{ startsWith(github.ref, 'refs/tags/v') }}
mac_certs: ${{ secrets.mac_certs_old }}
mac_certs_password: ${{ secrets.mac_certs_password_old }}
env:
# macOS notarization API key
API_KEY_ID: ${{ secrets.api_key_id_old }}
API_KEY_ISSUER_ID: ${{ secrets.api_key_issuer_id_old }}
# setry crash reporting token
SENTRY_AUTH_TOKEN: ${{secrets.sentry_auth_token}}
USE_HARD_LINKS: false

2
.gitignore vendored
View file

@ -7,3 +7,5 @@ buildingSteps.md
.DS_Store
.idea/
build/.DS_Store
.env
.electron-symbols/

2
.gitmodules vendored
View file

@ -1,7 +1,7 @@
[submodule "bada-frame"]
path = ui
url = https://github.com/ente-io/bada-frame
branch = demo
branch = release
[submodule "thirdparty/next-electron-server"]
path = thirdparty/next-electron-server
url = https://github.com/ente-io/next-electron-server.git

View file

@ -1,4 +1,4 @@
# bhari-frame (heavy-frame)
# ente Photos - Desktop
Desktop app for [ente.io](https://ente.io) build with [electron](https://electronjs.org) and loads of ❤️.
@ -10,7 +10,7 @@ The goal of this app was to
1. provide a stable environment for customers to back up large amounts of data reliably
2. export uploaded data from our servers to their local hard drives.
Electron was the best way to reuse our battle tested code from [bada-frame](https://github.com/ente-io/bada-frame) that powers [web.ente.io](https://web.ente.io).
Electron was the best way to reuse our battle tested code from [photos-web](https://github.com/ente-io/photos-web) that powers [web.ente.io](https://web.ente.io).
As an archival solution built by a small team, we are hopeful that this project will help us keep our stack lean, while ensuring a painfree life for our customers.
@ -18,12 +18,8 @@ If you are running into issues with this app, please drop a mail to [support@ent
## Download
- [Latest Release](https://github.com/ente-io/bhari-frame/releases/latest)
- [Latest Release](https://github.com/ente-io/photos-desktop/releases/latest)
*User contributed ports*
- [AUR](https://aur.archlinux.org/packages/ente-desktop-appimage):
`yay -S ente-desktop-appimage`
## Building from source
@ -33,10 +29,10 @@ fetch and build from source.
```bash
# Clone this repository
git clone https://github.com/ente-io/bhari-frame
git clone https://github.com/ente-io/photos-desktop
# Go into the repository
cd bhari-frame
cd photos-desktop
# Clone submodules (recursively)
git submodule update --init --recursive

BIN
build/image-magick Executable file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 989 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 607 B

View file

@ -1,7 +1,7 @@
{
"name": "ente",
"productName": "ente",
"version": "1.7.0-alpha.7",
"version": "1.6.16",
"private": true,
"description": "Desktop client for ente.io",
"main": "app/main.js",
@ -39,6 +39,11 @@
]
}
],
"asarUnpack": [
"node_modules/ffmpeg-static/bin/${os}/${arch}/ffmpeg",
"node_modules/ffmpeg-static/index.js",
"node_modules/ffmpeg-static/package.json"
],
"files": [
"app/**/*",
{
@ -63,17 +68,22 @@
"start": "concurrently \"yarn start-main\" \"yarn start-renderer\"",
"build-renderer": "cd ui && yarn install && yarn build && cd ..",
"build": "yarn build-renderer && yarn build-main",
"test-release": "yarn build && electron-builder"
"test-release": "yarn build && electron-builder --config.compression=store"
},
"author": "ente <code@ente.io>",
"devDependencies": {
"@sentry/cli": "^1.68.0",
"@types/auto-launch": "^5.0.2",
"@types/ffmpeg-static": "^3.0.1",
"@types/get-folder-size": "^2.0.0",
"@types/node": "^16.18.3",
"@types/node-fetch": "^2.6.2",
"@types/promise-fs": "^2.1.1",
"@types/semver-compare": "^1.0.1",
"@typescript-eslint/eslint-plugin": "^5.28.0",
"@typescript-eslint/parser": "^5.28.0",
"concurrently": "^7.0.0",
"electron": "^15.3.0",
"electron": "^21.2.2",
"electron-builder": "^23.0.3",
"electron-builder-notarize": "^1.2.0",
"electron-download": "^4.1.1",
@ -87,18 +97,20 @@
},
"dependencies": {
"@sentry/electron": "^2.5.1",
"@types/node": "^14.14.37",
"@types/promise-fs": "^2.1.1",
"chokidar": "^3.5.3",
"any-shell-escape": "^0.1.1",
"auto-launch": "^5.0.5",
"chokidar": "^3.5.3",
"electron-log": "^4.3.5",
"electron-reload": "^2.0.0-alpha.1",
"electron-store": "^8.0.1",
"electron-updater": "^4.3.8",
"ffmpeg-static": "^5.1.0",
"get-folder-size": "^2.0.1",
"next-electron-server": "file:./thirdparty/next-electron-server",
"node-fetch": "^2.6.7",
"node-stream-zip": "^1.15.0",
"promise-fs": "^2.1.1"
"promise-fs": "^2.1.1",
"semver-compare": "^1.0.0"
},
"standard": {
"parser": "babel-eslint"
@ -109,4 +121,4 @@
"prettier --write --ignore-unknown"
]
}
}
}

View file

@ -12,15 +12,13 @@ try {
process.exit(1);
}
const VERSION = /\bv?(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-[\da-z-]+(?:\.[\da-z-]+)*)?(?:\+[\da-z-]+(?:\.[\da-z-]+)*)?\b/i;
const SYMBOL_CACHE_FOLDER = '.electron-symbols';
const package = require('./package.json');
const sentryCli = new SentryCli('./sentry.properties');
async function main() {
let version = getElectronVersion();
const version = getElectronVersion();
if (!version) {
console.error('Cannot detect electron version, check package.json');
console.error('Cannot detect electron version, check that electron is installed');
return;
}
@ -68,20 +66,11 @@ async function main() {
}
function getElectronVersion() {
if (!package) {
return false;
try {
return require('electron/package.json').version;
} catch (error) {
return undefined;
}
let electronVersion =
(package.dependencies && package.dependencies.electron) ||
(package.devDependencies && package.devDependencies.electron);
if (!electronVersion) {
return false;
}
const matches = VERSION.exec(electronVersion);
return matches ? matches[0] : false;
}
async function downloadSymbols(options) {

View file

@ -1,4 +1,3 @@
defaults.url=https://sentry.ente.io/
defaults.org=ente
defaults.project=bhari-frame
cli.executable=../../../../usr/local/lib/node_modules/@sentry/wizard/node_modules/@sentry/cli/bin/sentry-cli

View file

@ -1,5 +1,5 @@
import { ipcRenderer } from 'electron/renderer';
import { logError } from '../utils/logging';
import { logError } from '../services/logging';
export const selectRootDirectory = async (): Promise<string> => {
try {
@ -8,3 +8,18 @@ export const selectRootDirectory = async (): Promise<string> => {
logError(e, 'error while selecting root directory');
}
};
export const getAppVersion = async (): Promise<string> => {
try {
return await ipcRenderer.invoke('get-app-version');
} catch (e) {
logError(e, 'failed to get release version');
throw e;
}
};
export {
logToDisk,
openLogDirectory,
getSentryUserID,
} from '../services/logging';

View file

@ -1,7 +1,7 @@
import { keysStore } from '../stores/keys.store';
import { safeStorageStore } from '../stores/safeStorage.store';
import { uploadStatusStore } from '../stores/upload.store';
import { logError } from '../utils/logging';
import { logError } from '../services/logging';
export const clearElectronStore = () => {
try {

View file

@ -1,6 +1,6 @@
import { readTextFile, writeStream } from './../services/fs';
import { ipcRenderer } from 'electron';
import { logError } from '../utils/logging';
import { logError } from '../services/logging';
import * as fs from 'promise-fs';
export const exists = (path: string) => {

41
src/api/ffmpeg.ts Normal file
View file

@ -0,0 +1,41 @@
import { ipcRenderer } from 'electron';
import { existsSync } from 'fs';
import { logError } from '../services/logging';
import { ElectronFile } from '../types';
export async function runFFmpegCmd(
cmd: string[],
inputFile: File | ElectronFile,
outputFileName: string
) {
let inputFilePath = null;
let createdTempInputFile = null;
try {
if (!existsSync(inputFile.path)) {
const inputFileData = new Uint8Array(await inputFile.arrayBuffer());
inputFilePath = await ipcRenderer.invoke(
'write-temp-file',
inputFileData,
inputFile.name
);
createdTempInputFile = true;
} else {
inputFilePath = inputFile.path;
}
const outputFileData = await ipcRenderer.invoke(
'run-ffmpeg-cmd',
cmd,
inputFilePath,
outputFileName
);
return new File([outputFileData], outputFileName);
} finally {
if (createdTempInputFile) {
try {
await ipcRenderer.invoke('remove-temp-file', inputFilePath);
} catch (e) {
logError(e, 'failed to deleteTempFile');
}
}
}
}

9
src/api/heicConvert.ts Normal file
View file

@ -0,0 +1,9 @@
import { ipcRenderer } from 'electron/renderer';
export async function convertHEIC(fileData: Uint8Array): Promise<Uint8Array> {
const convertedFileData = await ipcRenderer.invoke(
'convert-heic',
fileData
);
return convertedFileData;
}

View file

@ -1,6 +1,6 @@
import { ipcRenderer } from 'electron';
import { safeStorageStore } from '../stores/safeStorage.store';
import { logError } from '../utils/logging';
import { logError } from '../services/logging';
export async function setEncryptionKey(encryptionKey: string) {
try {

View file

@ -1,4 +1,5 @@
import { ipcRenderer } from 'electron';
import { AppUpdateInfo } from '../types';
export const sendNotification = (content: string) => {
ipcRenderer.send('send-notification', content);
@ -9,3 +10,20 @@ export const showOnTray = (content: string) => {
export const reloadWindow = () => {
ipcRenderer.send('reload-window');
};
export const registerUpdateEventListener = (
showUpdateDialog: (updateInfo: AppUpdateInfo) => void
) => {
ipcRenderer.removeAllListeners('show-update-dialog');
ipcRenderer.on('show-update-dialog', (_, updateInfo: AppUpdateInfo) => {
showUpdateDialog(updateInfo);
});
};
export const updateAndRestart = () => {
ipcRenderer.send('update-and-restart');
};
export const skipAppVersion = (version: string) => {
ipcRenderer.send('skip-app-version', version);
};

View file

@ -1,7 +1,7 @@
import { getElectronFile } from './../services/fs';
import { uploadStatusStore } from '../stores/upload.store';
import { ElectronFile, FILE_PATH_TYPE } from '../types';
import { logError } from '../utils/logging';
import { logError } from '../services/logging';
import { ipcRenderer } from 'electron';
import {
getElectronFilesFromGoogleZip,
@ -18,7 +18,10 @@ export const getPendingUploads = async () => {
if (zipPaths.length) {
type = FILE_PATH_TYPE.ZIPS;
for (const zipPath of zipPaths) {
files.push(...(await getElectronFilesFromGoogleZip(zipPath)));
files = [
...files,
...(await getElectronFilesFromGoogleZip(zipPath)),
];
}
const pendingFilePaths = new Set(filePaths);
files = files.filter((file) => pendingFilePaths.has(file.path));
@ -62,10 +65,13 @@ export const showUploadZipDialog = async () => {
const filePaths: string[] = await ipcRenderer.invoke(
'show-upload-zip-dialog'
);
const files: ElectronFile[] = [];
let files: ElectronFile[] = [];
for (const filePath of filePaths) {
files.push(...(await getElectronFilesFromGoogleZip(filePath)));
files = [
...files,
...(await getElectronFilesFromGoogleZip(filePath)),
];
}
return {

View file

@ -1,6 +1,20 @@
const PROD_HOST_URL: string = 'ente://app';
const RENDERER_OUTPUT_DIR: string = './ui/out';
const LOG_FILENAME = 'ente.log';
const MAX_LOG_SIZE = 5 * 1024 * 1024; // 5MB
const FILE_STREAM_CHUNK_SIZE: number = 4 * 1024 * 1024;
export { PROD_HOST_URL, RENDERER_OUTPUT_DIR, FILE_STREAM_CHUNK_SIZE };
const SENTRY_DSN = 'https://e9268b784d1042a7a116f53c58ad2165@sentry.ente.io/5';
const RELEASE_VERSION = require('../../package.json').version;
export {
PROD_HOST_URL,
RENDERER_OUTPUT_DIR,
FILE_STREAM_CHUNK_SIZE,
LOG_FILENAME,
MAX_LOG_SIZE,
SENTRY_DSN,
RELEASE_VERSION,
};

View file

@ -13,8 +13,12 @@ import {
setupNextElectronServe,
enableSharedArrayBufferSupport,
handleDockIconHideOnAutoLaunch,
logSystemInfo,
} from './utils/main';
import { initSentry } from './services/sentry';
import { setupLogging } from './utils/logging';
import { isDev } from './utils/common';
import { setupMainProcessStatsLogger } from './utils/processStats';
let mainWindow: BrowserWindow;
@ -41,6 +45,8 @@ setupMainHotReload();
setupNextElectronServe();
setupLogging(isDev);
const gotTheLock = app.requestSingleInstanceLock();
if (!gotTheLock) {
app.quit();
@ -62,14 +68,16 @@ if (!gotTheLock) {
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', async () => {
logSystemInfo();
setupMainProcessStatsLogger();
initSentry();
mainWindow = await createWindow();
const tray = setupTrayItem(mainWindow);
const watcher = initWatcher(mainWindow);
setupMacWindowOnDockIconClick();
initSentry();
setupMainMenu();
setupIpcComs(tray, mainWindow, watcher);
handleUpdates(mainWindow, tray);
handleUpdates(mainWindow);
handleDownloads(mainWindow);
addAllowOriginHeader(mainWindow);
});

View file

@ -1,4 +1,11 @@
import { reloadWindow, sendNotification, showOnTray } from './api/system';
import {
registerUpdateEventListener,
reloadWindow,
sendNotification,
showOnTray,
updateAndRestart,
skipAppVersion,
} from './api/system';
import {
showUploadDirsDialog,
showUploadFilesDialog,
@ -32,11 +39,23 @@ import {
setExportRecord,
exists,
} from './api/export';
import { selectRootDirectory } from './api/common';
import {
selectRootDirectory,
logToDisk,
openLogDirectory,
getSentryUserID,
getAppVersion,
} from './api/common';
import { fixHotReloadNext12 } from './utils/preload';
import { isFolder, getDirFiles } from './api/fs';
import { convertHEIC } from './api/heicConvert';
import { setupLogging } from './utils/logging';
import { setupRendererProcessStatsLogger } from './utils/processStats';
import { runFFmpegCmd } from './api/ffmpeg';
fixHotReloadNext12();
setupLogging();
setupRendererProcessStatsLogger();
const windowObject: any = window;
@ -76,4 +95,13 @@ windowObject['ElectronAPIs'] = {
isFolder,
updateWatchMappingSyncedFiles,
updateWatchMappingIgnoredFiles,
logToDisk,
convertHEIC,
openLogDirectory,
registerUpdateEventListener,
updateAndRestart,
skipAppVersion,
getSentryUserID,
getAppVersion,
runFFmpegCmd,
};

View file

@ -1,39 +1,117 @@
import { BrowserWindow, dialog, Tray } from 'electron';
import { app, BrowserWindow } from 'electron';
import { autoUpdater } from 'electron-updater';
import log from 'electron-log';
import { setIsAppQuitting, setIsUpdateAvailable } from '../main';
import { buildContextMenu } from '../utils/menu';
import semVerCmp from 'semver-compare';
import { AppUpdateInfo, GetFeatureFlagResponse } from '../types';
import { getSkipAppVersion, setSkipAppVersion } from './userPreference';
import fetch from 'node-fetch';
import { logErrorSentry } from './sentry';
import ElectronLog from 'electron-log';
import { isPlatform } from '../utils/main';
class AppUpdater {
constructor() {
log.transports.file.level = 'debug';
autoUpdater.logger = log;
}
const FIVE_MIN_IN_MICROSECOND = 5 * 60 * 1000;
async checkForUpdate(tray: Tray, mainWindow: BrowserWindow) {
await autoUpdater.checkForUpdatesAndNotify();
autoUpdater.on('update-downloaded', () => {
showUpdateDialog();
setIsUpdateAvailable(true);
tray.setContextMenu(buildContextMenu(mainWindow));
});
export function setupAutoUpdater() {
autoUpdater.logger = log;
autoUpdater.autoDownload = false;
}
export async function checkForUpdateAndNotify(mainWindow: BrowserWindow) {
try {
log.debug('checkForUpdateAndNotify called');
const updateCheckResult = await autoUpdater.checkForUpdates();
log.debug('update version', updateCheckResult.updateInfo.version);
if (
semVerCmp(updateCheckResult.updateInfo.version, app.getVersion()) <=
0
) {
log.debug('already at latest version');
return;
}
const skipAppVersion = getSkipAppVersion();
if (
skipAppVersion &&
updateCheckResult.updateInfo.version === skipAppVersion
) {
log.info(
'user chose to skip version ',
updateCheckResult.updateInfo.version
);
return;
}
const desktopCutoffVersion = await getDesktopCutoffVersion();
if (
desktopCutoffVersion &&
isPlatform('mac') &&
semVerCmp(
updateCheckResult.updateInfo.version,
desktopCutoffVersion
) > 0
) {
log.debug('auto update not possible due to key change');
showUpdateDialog(mainWindow, {
autoUpdatable: false,
version: updateCheckResult.updateInfo.version,
});
} else {
let timeout: NodeJS.Timeout;
log.debug('attempting auto update');
autoUpdater.downloadUpdate();
autoUpdater.on('update-downloaded', () => {
timeout = setTimeout(
() =>
showUpdateDialog(mainWindow, {
autoUpdatable: true,
version: updateCheckResult.updateInfo.version,
}),
FIVE_MIN_IN_MICROSECOND
);
});
autoUpdater.on('error', (error) => {
clearTimeout(timeout);
logErrorSentry(error, 'auto update failed');
showUpdateDialog(mainWindow, {
autoUpdatable: false,
version: updateCheckResult.updateInfo.version,
});
});
}
setIsUpdateAvailable(true);
} catch (e) {
logErrorSentry(e, 'checkForUpdateAndNotify failed');
}
}
export default new AppUpdater();
export function updateAndRestart() {
ElectronLog.log('user quit the app');
setIsAppQuitting(true);
autoUpdater.quitAndInstall();
}
export const showUpdateDialog = (): void => {
dialog
.showMessageBox({
type: 'info',
title: 'Install update',
message: 'Restart to update to the latest version of ente',
buttons: ['Later', 'Restart now'],
})
.then((buttonIndex) => {
if (buttonIndex.response === 1) {
setIsAppQuitting(true);
autoUpdater.quitAndInstall();
}
});
};
export function getAppVersion() {
return `v${app.getVersion()}`;
}
export function skipAppVersion(version: string) {
setSkipAppVersion(version);
}
async function getDesktopCutoffVersion() {
try {
const featureFlags = (
await fetch('https://static.ente.io/feature_flags.json')
).json() as GetFeatureFlagResponse;
return featureFlags.desktopCutoffVersion;
} catch (e) {
logErrorSentry(e, 'failed to get feature flags');
return undefined;
}
}
function showUpdateDialog(
mainWindow: BrowserWindow,
updateInfo: AppUpdateInfo
) {
mainWindow.webContents.send('show-update-dialog', updateInfo);
}

View file

@ -1,12 +1,12 @@
import { isPlatform } from '../utils/main';
import { AutoLauncherClient } from '../types/autoLauncher';
import { isPlatformWindows, isPlatformMac } from '../utils/main';
import linuxAutoLauncher from './autoLauncherClients/linuxAutoLauncher';
import macAndWindowsAutoLauncher from './autoLauncherClients/macAndWindowsAutoLauncher';
class AutoLauncher {
private client: AutoLauncherClient;
init() {
if (isPlatformMac() || isPlatformWindows()) {
if (isPlatform('mac') || isPlatform('windows')) {
this.client = macAndWindowsAutoLauncher;
} else {
this.client = linuxAutoLauncher;

View file

@ -1,6 +1,6 @@
import chokidar from 'chokidar';
import { BrowserWindow } from 'electron';
import { logError } from '../utils/logging';
import { logError } from '../services/logging';
import { getWatchMappings } from '../api/watch';
export function initWatcher(mainWindow: BrowserWindow) {

View file

@ -2,7 +2,7 @@ import path from 'path';
import { readdir, stat, unlink } from 'promise-fs';
import getFolderSize from 'get-folder-size';
import { utimes, close, open } from 'promise-fs';
import { logError } from '../utils/logging';
import { logError } from '../services/logging';
export interface LeastRecentlyUsedResult {
atime: Date;

75
src/services/ffmpeg.ts Normal file
View file

@ -0,0 +1,75 @@
import pathToFfmpeg from 'ffmpeg-static';
const shellescape = require('any-shell-escape');
import util from 'util';
import log from 'electron-log';
import { readFile, rmSync, writeFile } from 'promise-fs';
import { logErrorSentry } from './sentry';
import { generateTempFilePath, getTempDirPath } from '../utils/temp';
import { existsSync } from 'fs';
const execAsync = util.promisify(require('child_process').exec);
export const INPUT_PATH_PLACEHOLDER = 'INPUT';
export const FFMPEG_PLACEHOLDER = 'FFMPEG';
export const OUTPUT_PATH_PLACEHOLDER = 'OUTPUT';
function getFFmpegStaticPath() {
return pathToFfmpeg.replace('app.asar', 'app.asar.unpacked');
}
export async function runFFmpegCmd(
cmd: string[],
inputFilePath: string,
outputFileName: string
) {
let tempOutputFilePath: string;
try {
tempOutputFilePath = await generateTempFilePath(outputFileName);
cmd = cmd.map((cmdPart) => {
if (cmdPart === FFMPEG_PLACEHOLDER) {
return getFFmpegStaticPath();
} else if (cmdPart === INPUT_PATH_PLACEHOLDER) {
return inputFilePath;
} else if (cmdPart === OUTPUT_PATH_PLACEHOLDER) {
return tempOutputFilePath;
} else {
return cmdPart;
}
});
cmd = shellescape(cmd);
log.info('cmd', cmd);
await execAsync(cmd);
if (!existsSync(tempOutputFilePath)) {
throw new Error('ffmpeg output file not found');
}
const outputFile = await readFile(tempOutputFilePath);
return new Uint8Array(outputFile);
} catch (e) {
logErrorSentry(e, 'ffmpeg run command error');
throw e;
} finally {
try {
rmSync(tempOutputFilePath, { force: true });
} catch (e) {
logErrorSentry(e, 'failed to remove tempOutputFile');
}
}
}
export async function writeTempFile(fileStream: Uint8Array, fileName: string) {
const tempFilePath = await generateTempFilePath(fileName);
await writeFile(tempFilePath, fileStream);
return tempFilePath;
}
export async function deleteTempFile(tempFilePath: string) {
const tempDirPath = await getTempDirPath();
if (!tempFilePath.startsWith(tempDirPath)) {
logErrorSentry(
Error('not a temp file'),
'tried to delete a non temp file'
);
}
rmSync(tempFilePath, { force: true });
}

View file

@ -4,6 +4,8 @@ import * as fs from 'promise-fs';
import { ElectronFile } from '../types';
import StreamZip from 'node-stream-zip';
import { Readable } from 'stream';
import { logError } from './logging';
import { existsSync } from 'fs';
// https://stackoverflow.com/a/63111390
export const getDirFilePaths = async (dirPath: string) => {
@ -16,7 +18,7 @@ export const getDirFilePaths = async (dirPath: string) => {
for (const filePath of filePaths) {
const absolute = path.join(dirPath, filePath);
files = files.concat(await getDirFilePaths(absolute));
files = [...files, ...(await getDirFilePaths(absolute))];
}
return files;
@ -48,6 +50,9 @@ export const getFileStream = async (filePath: string) => {
await fs.close(file);
}
},
async cancel() {
await fs.close(file);
},
});
return readableStream;
};
@ -60,13 +65,22 @@ export async function getElectronFile(filePath: string): Promise<ElectronFile> {
size: fileStats.size,
lastModified: fileStats.mtime.valueOf(),
stream: async () => {
if (!existsSync(filePath)) {
throw new Error('electronFile does not exist');
}
return await getFileStream(filePath);
},
blob: async () => {
if (!existsSync(filePath)) {
throw new Error('electronFile does not exist');
}
const blob = await fs.readFile(filePath);
return new Blob([new Uint8Array(blob)]);
},
arrayBuffer: async () => {
if (!existsSync(filePath)) {
throw new Error('electronFile does not exist');
}
const blob = await fs.readFile(filePath);
return new Uint8Array(blob);
},
@ -94,31 +108,50 @@ export const getZipFileStream = async (
const done = {
current: false,
};
const inProgress = {
current: false,
};
let resolveObj: (value?: any) => void = null;
let rejectObj: (reason?: any) => void = null;
stream.on('readable', () => {
if (resolveObj) {
const chunk = stream.read(FILE_STREAM_CHUNK_SIZE) as Buffer;
if (chunk) {
resolveObj(new Uint8Array(chunk));
resolveObj = null;
try {
if (resolveObj) {
inProgress.current = true;
const chunk = stream.read(FILE_STREAM_CHUNK_SIZE) as Buffer;
if (chunk) {
resolveObj(new Uint8Array(chunk));
resolveObj = null;
}
inProgress.current = false;
}
} catch (e) {
rejectObj(e);
}
});
stream.on('end', () => {
done.current = true;
try {
done.current = true;
if (resolveObj && !inProgress.current) {
resolveObj(null);
resolveObj = null;
}
} catch (e) {
rejectObj(e);
}
});
stream.on('error', (e) => {
done.current = true;
if (rejectObj) {
try {
done.current = true;
if (rejectObj) {
rejectObj(e);
rejectObj = null;
}
} catch (e) {
rejectObj(e);
rejectObj = null;
}
});
const readStreamData = () => {
const readStreamData = async () => {
return new Promise<Uint8Array>((resolve, reject) => {
const chunk = stream.read(FILE_STREAM_CHUNK_SIZE) as Buffer;
@ -142,6 +175,7 @@ export const getZipFileStream = async (
controller.close();
}
} catch (e) {
logError(e, 'readableStream pull failed');
controller.close();
}
},
@ -183,5 +217,8 @@ export function writeStream(filePath: string, fileStream: any) {
}
export async function readTextFile(filePath: string) {
if (!existsSync(filePath)) {
throw new Error('File does not exist');
}
return await fs.readFile(filePath, 'utf-8');
}

View file

@ -0,0 +1,72 @@
import util from 'util';
import { exec } from 'child_process';
import { existsSync, rmSync } from 'fs';
import { readFile, writeFile } from 'promise-fs';
import { generateTempFilePath } from '../utils/temp';
import { logErrorSentry } from './sentry';
import { isPlatform } from '../utils/main';
import { isDev } from '../utils/common';
import path from 'path';
const asyncExec = util.promisify(exec);
function getImageMagickStaticPath() {
return isDev
? 'build/image-magick'
: path.join(process.resourcesPath, 'image-magick');
}
export async function convertHEIC(
heicFileData: Uint8Array
): Promise<Uint8Array> {
let tempInputFilePath: string;
let tempOutputFilePath: string;
try {
tempInputFilePath = await generateTempFilePath('.heic');
tempOutputFilePath = await generateTempFilePath('.jpeg');
await writeFile(tempInputFilePath, heicFileData);
await runConvertCommand(tempInputFilePath, tempOutputFilePath);
if (!existsSync(tempOutputFilePath)) {
throw new Error('heic convert output file not found');
}
const convertedFileData = new Uint8Array(
await readFile(tempOutputFilePath)
);
return convertedFileData;
} catch (e) {
logErrorSentry(e, 'failed to convert heic');
throw e;
} finally {
try {
rmSync(tempInputFilePath, { force: true });
} catch (e) {
logErrorSentry(e, 'failed to remove tempInputFile');
}
try {
rmSync(tempOutputFilePath, { force: true });
} catch (e) {
logErrorSentry(e, 'failed to remove tempOutputFile');
}
}
}
async function runConvertCommand(
tempInputFilePath: string,
tempOutputFilePath: string
) {
if (isPlatform('mac')) {
await asyncExec(
`sips -s format jpeg ${tempInputFilePath} --out ${tempOutputFilePath}`
);
} else if (isPlatform('linux')) {
await asyncExec(
`${getImageMagickStaticPath()} ${tempInputFilePath} -quality 100% ${tempOutputFilePath}`
);
} else {
Error(`${process.platform} native heic convert not supported yet`);
}
}

18
src/services/logging.ts Normal file
View file

@ -0,0 +1,18 @@
import log from 'electron-log';
import { ipcRenderer } from 'electron';
export function logToDisk(logLine: string) {
log.info(logLine);
}
export function openLogDirectory() {
ipcRenderer.invoke('open-log-dir');
}
export function logError(error: Error, message: string, info?: string): void {
ipcRenderer.invoke('log-error', error, message, info);
}
export function getSentryUserID(): Promise<string> {
return ipcRenderer.invoke('get-sentry-id');
}

View file

@ -1,19 +1,22 @@
import * as Sentry from '@sentry/electron/dist/main';
import { makeID } from '../utils/logging';
import { keysStore } from '../stores/keys.store';
import { SENTRY_DSN, RELEASE_VERSION } from '../config';
import { isDev } from '../utils/common';
import { logToDisk } from './logging';
const SENTRY_DSN = 'https://e9268b784d1042a7a116f53c58ad2165@sentry.ente.io/5';
const ENV_DEVELOPMENT = 'development';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const version = require('../../package.json').version;
const isDEVSentryENV = () =>
process.env.NEXT_PUBLIC_SENTRY_ENV === ENV_DEVELOPMENT;
export function initSentry(): void {
Sentry.init({
dsn: SENTRY_DSN,
release: version,
release: RELEASE_VERSION,
environment: isDev ? 'development' : 'production',
});
Sentry.setUser({ id: getSentryUserID() });
}
export function logErrorSentry(
@ -22,12 +25,17 @@ export function logErrorSentry(
info?: Record<string, unknown>
) {
const err = errorWithContext(error, msg);
if (!process.env.NEXT_PUBLIC_SENTRY_ENV) {
logToDisk(
`error: ${error?.name} ${error?.message} ${
error?.stack
} msg: ${msg} info: ${JSON.stringify(info)}`
);
if (isDEVSentryENV()) {
console.log(error, { msg, info });
}
Sentry.captureException(err, {
level: Sentry.Severity.Info,
user: { id: getUserAnonymizedID() },
user: { id: getSentryUserID() },
contexts: {
...(info && {
info: info,
@ -46,7 +54,7 @@ function errorWithContext(originalError: Error, context: string) {
return errorWithContext;
}
function getUserAnonymizedID() {
export function getSentryUserID() {
let anonymizeUserID = keysStore.get('AnonymizeUserID')?.id;
if (!anonymizeUserID) {
anonymizeUserID = makeID(6);
@ -54,16 +62,3 @@ function getUserAnonymizedID() {
}
return anonymizeUserID;
}
function makeID(length: number) {
let result = '';
const characters =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
const charactersLength = characters.length;
for (let i = 0; i < length; i++) {
result += characters.charAt(
Math.floor(Math.random() * charactersLength)
);
}
return result;
}

View file

@ -15,11 +15,15 @@ export const getSavedFilePaths = (type: FILE_PATH_TYPE) => {
};
export async function getZipEntryAsElectronFile(
zipName: string,
zip: StreamZip.StreamZipAsync,
entry: StreamZip.ZipEntry
): Promise<ElectronFile> {
return {
path: entry.name,
path: path
.join(zipName, entry.name)
.split(path.sep)
.join(path.posix.sep),
name: path.basename(entry.name),
size: entry.size,
lastModified: entry.time,
@ -58,6 +62,7 @@ export const getElectronFilesFromGoogleZip = async (filePath: string) => {
const zip = new StreamZip.async({
file: filePath,
});
const zipName = path.basename(filePath, '.zip');
const entries = await zip.entries();
const files: ElectronFile[] = [];
@ -65,7 +70,7 @@ export const getElectronFilesFromGoogleZip = async (filePath: string) => {
for (const entry of Object.values(entries)) {
const basename = path.basename(entry.name);
if (entry.isFile && basename.length > 0 && basename[0] !== '.') {
files.push(await getZipEntryAsElectronFile(zip, entry));
files.push(await getZipEntryAsElectronFile(zipName, zip, entry));
}
}

View file

@ -1,10 +1,16 @@
import { userPreferencesStore } from '../stores/userPreferences.store';
export function getHideDockIconPreference() {
const shouldHideDockIcon = userPreferencesStore.get('hideDockIcon');
return shouldHideDockIcon;
return userPreferencesStore.get('hideDockIcon');
}
export function setHideDockIconPreference(shouldHideDockIcon: boolean) {
userPreferencesStore.set('hideDockIcon', shouldHideDockIcon);
}
export function getSkipAppVersion() {
return userPreferencesStore.get('skipAppVersion');
}
export function setSkipAppVersion(version: string) {
userPreferencesStore.set('skipAppVersion', version);
}

View file

@ -5,6 +5,9 @@ const userPreferencesSchema: Schema<UserPreferencesType> = {
hideDockIcon: {
type: 'boolean',
},
skipAppVersion: {
type: 'string',
},
};
export const userPreferencesStore = new Store({

View file

@ -56,4 +56,14 @@ export interface SafeStorageStoreType {
export interface UserPreferencesType {
hideDockIcon: boolean;
skipAppVersion: string;
}
export interface AppUpdateInfo {
autoUpdatable: boolean;
version: string;
}
export interface GetFeatureFlagResponse {
desktopCutoffVersion?: string;
}

View file

@ -3,7 +3,7 @@ import * as path from 'path';
import { isDev } from './common';
import { isAppQuitting } from '../main';
import { PROD_HOST_URL } from '../config';
import { isPlatformMac } from './main';
import { isPlatform } from './main';
import { getHideDockIconPreference } from '../services/userPreference';
import autoLauncher from '../services/autoLauncher';
@ -16,13 +16,13 @@ export async function createWindow(): Promise<BrowserWindow> {
const mainWindow = new BrowserWindow({
height: 600,
width: 800,
backgroundColor: '#111111',
webPreferences: {
sandbox: false,
preload: path.join(__dirname, '../preload.js'),
contextIsolation: false,
},
icon: appIcon,
show: false, // don't show the main window on load
show: false, // don't show the main window on load,
});
mainWindow.maximize();
const wasAutoLaunched = await autoLauncher.wasAutoLaunched();
@ -55,11 +55,25 @@ export async function createWindow(): Promise<BrowserWindow> {
);
});
mainWindow.once('ready-to-show', async () => {
splash.destroy();
if (!wasAutoLaunched) {
mainWindow.show();
try {
splash.destroy();
if (!wasAutoLaunched) {
mainWindow.show();
}
} catch (e) {
// ignore
}
});
setTimeout(() => {
try {
splash.destroy();
if (!wasAutoLaunched) {
mainWindow.show();
}
} catch (e) {
// ignore
}
}, 2000);
mainWindow.on('close', function (event) {
if (!isAppQuitting()) {
event.preventDefault();
@ -69,12 +83,12 @@ export async function createWindow(): Promise<BrowserWindow> {
});
mainWindow.on('hide', () => {
const shouldHideDockIcon = getHideDockIconPreference();
if (isPlatformMac() && shouldHideDockIcon) {
if (isPlatform('mac') && shouldHideDockIcon) {
app.dock.hide();
}
});
mainWindow.on('show', () => {
if (isPlatformMac()) {
if (isPlatform('mac')) {
app.dock.show();
}
});

View file

@ -6,13 +6,25 @@ import {
Notification,
safeStorage,
app,
shell,
} from 'electron';
import { createWindow } from './createWindow';
import { buildContextMenu } from './menu';
import { logErrorSentry } from '../services/sentry';
import { getSentryUserID, logErrorSentry } from '../services/sentry';
import chokidar from 'chokidar';
import path from 'path';
import { getDirFilePaths } from '../services/fs';
import { convertHEIC } from '../services/heicConverter';
import {
getAppVersion,
skipAppVersion,
updateAndRestart,
} from '../services/appUpdater';
import {
deleteTempFile,
runFFmpegCmd,
writeTempFile,
} from '../services/ffmpeg';
export default function setupIpcComs(
tray: Tray,
@ -67,7 +79,7 @@ export default function setupIpcComs(
let files: string[] = [];
for (const dirPath of dir.filePaths) {
files = files.concat(await getDirFilePaths(dirPath));
files = [...files, ...(await getDirFilePaths(dirPath))];
}
return files;
@ -96,4 +108,42 @@ export default function setupIpcComs(
ipcMain.handle('get-path', (_, message) => {
return app.getPath(message);
});
ipcMain.handle('convert-heic', (_, fileData) => {
return convertHEIC(fileData);
});
ipcMain.handle('open-log-dir', () => {
shell.openPath(app.getPath('logs'));
});
ipcMain.on('update-and-restart', () => {
updateAndRestart();
});
ipcMain.on('skip-app-version', (_, version) => {
skipAppVersion(version);
});
ipcMain.handle('get-sentry-id', () => {
return getSentryUserID();
});
ipcMain.handle('get-app-version', () => {
return getAppVersion();
});
ipcMain.handle(
'run-ffmpeg-cmd',
(_, cmd, inputFilePath, outputFileName) => {
return runFFmpegCmd(cmd, inputFilePath, outputFileName);
}
);
ipcMain.handle(
'write-temp-file',
(_, fileStream: Uint8Array, fileName: string) => {
return writeTempFile(fileStream, fileName);
}
);
ipcMain.handle('remove-temp-file', (_, tempFilePath: string) => {
return deleteTempFile(tempFilePath);
});
}

View file

@ -1,5 +1,25 @@
import { ipcRenderer } from 'electron';
import log from 'electron-log';
import { LOG_FILENAME, MAX_LOG_SIZE } from '../config';
export function logError(error: Error, message: string, info?: string): void {
ipcRenderer.invoke('log-error', error, message, info);
export function setupLogging(isDev?: boolean) {
log.transports.file.fileName = LOG_FILENAME;
log.transports.file.maxSize = MAX_LOG_SIZE;
if (!isDev) {
log.transports.console.level = false;
}
log.transports.file.format =
'[{y}-{m}-{d}T{h}:{i}:{s}{z}] [{level}]{scope} {text}';
}
export function makeID(length: number) {
let result = '';
const characters =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
const charactersLength = characters.length;
for (let i = 0; i < length; i++) {
result += characters.charAt(
Math.floor(Math.random() * charactersLength)
);
}
return result;
}

View file

@ -4,22 +4,31 @@ import electronReload from 'electron-reload';
import serveNextAt from 'next-electron-server';
import path from 'path';
import { existsSync } from 'promise-fs';
import appUpdater from '../services/appUpdater';
import { isDev } from './common';
import { buildContextMenu, buildMenuBar } from './menu';
import autoLauncher from '../services/autoLauncher';
import { getHideDockIconPreference } from '../services/userPreference';
import {
checkForUpdateAndNotify,
setupAutoUpdater,
} from '../services/appUpdater';
import ElectronLog from 'electron-log';
import os from 'os';
export function handleUpdates(mainWindow: BrowserWindow, tray: Tray) {
export function handleUpdates(mainWindow: BrowserWindow) {
if (!isDev) {
appUpdater.checkForUpdate(tray, mainWindow);
setupAutoUpdater();
checkForUpdateAndNotify(mainWindow);
}
}
export function setupTrayItem(mainWindow: BrowserWindow) {
const trayImgPath = isDev
? 'build/taskbar-icon.png'
: path.join(process.resourcesPath, 'taskbar-icon.png');
const iconName = isPlatform('mac')
? 'taskbar-icon-Template.png'
: 'taskbar-icon.png';
const trayImgPath = path.join(
isDev ? 'build' : process.resourcesPath,
iconName
);
const trayIcon = nativeImage.createFromPath(trayImgPath);
const tray = new Tray(trayIcon);
tray.setToolTip('ente');
@ -79,19 +88,23 @@ export function setupNextElectronServe() {
});
}
export function isPlatformMac() {
return process.platform === 'darwin';
}
export function isPlatformWindows() {
return process.platform === 'win32';
export function isPlatform(platform: 'mac' | 'windows' | 'linux') {
if (process.platform === 'darwin') {
return platform === 'mac';
} else if (process.platform === 'win32') {
return platform === 'windows';
} else if (process.platform === 'linux') {
return platform === 'linux';
} else {
return false;
}
}
export async function handleDockIconHideOnAutoLaunch() {
const shouldHideDockIcon = getHideDockIconPreference();
const wasAutoLaunched = await autoLauncher.wasAutoLaunched();
if (isPlatformMac() && shouldHideDockIcon && wasAutoLaunched) {
if (isPlatform('mac') && shouldHideDockIcon && wasAutoLaunched) {
app.dock.hide();
}
}
@ -99,3 +112,10 @@ export async function handleDockIconHideOnAutoLaunch() {
export function enableSharedArrayBufferSupport() {
app.commandLine.appendSwitch('enable-features', 'SharedArrayBuffer');
}
export function logSystemInfo() {
const systemVersion = process.getSystemVersion();
const osName = process.platform;
const osRelease = os.release();
ElectronLog.info({ osName, osRelease, systemVersion });
}

View file

@ -9,11 +9,10 @@ import {
getHideDockIconPreference,
setHideDockIconPreference,
} from '../services/userPreference';
import { isUpdateAvailable, setIsAppQuitting } from '../main';
import { setIsAppQuitting } from '../main';
import autoLauncher from '../services/autoLauncher';
import { isPlatformMac } from './main';
import { showUpdateDialog } from '../services/appUpdater';
import { isDev } from './common';
import { isPlatform } from './main';
import ElectronLog from 'electron-log';
export function buildContextMenu(
mainWindow: BrowserWindow,
@ -26,15 +25,6 @@ export function buildContextMenu(
paused,
} = args;
const contextMenu = Menu.buildFromTemplate([
...(isUpdateAvailable()
? [
{
label: 'Update available',
click: () => showUpdateDialog(),
},
]
: []),
{ type: 'separator' },
...(exportProgress
? [
{
@ -87,6 +77,7 @@ export function buildContextMenu(
{
label: 'Quit ente',
click: function () {
ElectronLog.log('user quit the app');
setIsAppQuitting(true);
app.quit();
},
@ -97,7 +88,7 @@ export function buildContextMenu(
export async function buildMenuBar(): Promise<Menu> {
let isAutoLaunchEnabled = await autoLauncher.isEnabled();
const isMac = isPlatformMac();
const isMac = isPlatform('mac');
let shouldHideDockIcon = getHideDockIconPreference();
const template: MenuItemConstructorOptions[] = [
{
@ -216,6 +207,7 @@ export async function buildMenuBar(): Promise<Menu> {
{
label: 'Window',
submenu: [
{ role: 'close', label: 'Close' },
{ role: 'minimize', label: 'Minimize' },
...((isMac
? [
@ -224,9 +216,31 @@ export async function buildMenuBar(): Promise<Menu> {
{ type: 'separator' },
{ role: 'window', label: 'ente' },
]
: [
{ role: 'close', label: 'Close ente' },
]) as MenuItemConstructorOptions[]),
: []) as MenuItemConstructorOptions[]),
],
},
{
label: 'Help',
submenu: [
{
label: 'FAQ',
click: () => shell.openExternal('https://ente.io/faq/'),
},
{ type: 'separator' },
{
label: 'Support',
click: () => shell.openExternal('mailto:support@ente.io'),
},
{
label: 'Product updates',
click: () => shell.openExternal('https://ente.io/blog/'),
},
{
label: 'View logs',
click: () => {
shell.openPath(app.getPath('logs'));
},
},
],
},
{

39
src/utils/processStats.ts Normal file
View file

@ -0,0 +1,39 @@
import ElectronLog from 'electron-log';
import { webFrame } from 'electron/renderer';
const FIVE_MINUTES_IN_MICROSECONDS = 30 * 1000;
async function logMainProcessStats() {
const systemMemoryInfo = process.getSystemMemoryInfo();
const cpuUsage = process.getCPUUsage();
const processMemoryInfo = await process.getProcessMemoryInfo();
const heapStatistics = process.getHeapStatistics();
ElectronLog.log('main process stats', {
systemMemoryInfo,
cpuUsage,
processMemoryInfo,
heapStatistics,
});
}
async function logRendererProcessStats() {
const blinkMemoryInfo = process.getBlinkMemoryInfo();
const heapStatistics = process.getHeapStatistics();
const processMemoryInfo = process.getProcessMemoryInfo();
const webFrameResourceUsage = webFrame.getResourceUsage();
ElectronLog.log('renderer process stats', {
blinkMemoryInfo,
heapStatistics,
processMemoryInfo,
webFrameResourceUsage,
});
}
export function setupMainProcessStatsLogger() {
setInterval(logMainProcessStats, FIVE_MINUTES_IN_MICROSECONDS);
}
export function setupRendererProcessStatsLogger() {
setInterval(logRendererProcessStats, FIVE_MINUTES_IN_MICROSECONDS);
}

38
src/utils/temp.ts Normal file
View file

@ -0,0 +1,38 @@
import { app } from 'electron';
import path from 'path';
import { existsSync, mkdir } from 'promise-fs';
const ENTE_TEMP_DIRECTORY = 'ente';
const CHARACTERS =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
export async function getTempDirPath() {
const tempDirPath = path.join(app.getPath('temp'), ENTE_TEMP_DIRECTORY);
if (!existsSync(tempDirPath)) {
await mkdir(tempDirPath);
}
return tempDirPath;
}
function generateTempName(length: number) {
let result = '';
const charactersLength = CHARACTERS.length;
for (let i = 0; i < length; i++) {
result += CHARACTERS.charAt(
Math.floor(Math.random() * charactersLength)
);
}
return result;
}
export async function generateTempFilePath(formatSuffix: string) {
const tempDirPath = await getTempDirPath();
const namePrefix = generateTempName(10);
const tempFilePath = path.join(
tempDirPath,
namePrefix + '-' + formatSuffix
);
return tempFilePath;
}

153
yarn.lock
View file

@ -35,6 +35,16 @@
chalk "^2.0.0"
js-tokens "^4.0.0"
"@derhuerst/http-basic@^8.2.0":
version "8.2.4"
resolved "https://registry.yarnpkg.com/@derhuerst/http-basic/-/http-basic-8.2.4.tgz#d021ebb8f65d54bea681ae6f4a8733ce89e7f59b"
integrity sha512-F9rL9k9Xjf5blCz8HsJRO4diy111cayL2vkY2XE4r4t3n0yPXVYy3KD3nJ1qbrSn9743UWSXH4IwuCa/HWlGFw==
dependencies:
caseless "^0.12.0"
concat-stream "^2.0.0"
http-response-object "^3.0.1"
parse-cache-control "^1.0.1"
"@develar/schema-utils@~2.6.5":
version "2.6.5"
resolved "https://registry.yarnpkg.com/@develar/schema-utils/-/schema-utils-2.6.5.tgz#3ece22c5838402419a6e0425f85742b961d9b6c6"
@ -43,7 +53,7 @@
ajv "^6.12.0"
ajv-keywords "^3.4.1"
"@electron/get@^1.13.0":
"@electron/get@^1.14.1":
version "1.14.1"
resolved "https://registry.yarnpkg.com/@electron/get/-/get-1.14.1.tgz#16ba75f02dffb74c23965e72d617adc721d27f40"
integrity sha512-BrZYyL/6m0ZXz/lDxy/nlVhQz+WF+iPS6qXolEU8atw7h6v1aYkjwJZ63m+bJMBTxDE66X+r2tPS4a/8C82sZw==
@ -277,6 +287,11 @@
dependencies:
"@types/ms" "*"
"@types/ffmpeg-static@^3.0.1":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@types/ffmpeg-static/-/ffmpeg-static-3.0.1.tgz#1003f003624bcd2f569b56185a62dcbacd935c39"
integrity sha512-hEJdQMv/g1olk9qTiWqh23BfbKsDKE6Tc7DilNJWF1MgZsU9fYOPKrgQ448vfT7aP2Yt5re9vgJDVv9TXEoTyQ==
"@types/fs-extra@^9.0.11":
version "9.0.13"
resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-9.0.13.tgz#7594fbae04fe7f1918ce8b3d213f74ff44ac1f45"
@ -312,15 +327,28 @@
resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197"
integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==
"@types/node-fetch@^2.6.2":
version "2.6.2"
resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.2.tgz#d1a9c5fd049d9415dce61571557104dec3ec81da"
integrity sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A==
dependencies:
"@types/node" "*"
form-data "^3.0.0"
"@types/node@*":
version "18.0.3"
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.0.3.tgz#463fc47f13ec0688a33aec75d078a0541a447199"
integrity sha512-HzNRZtp4eepNitP+BD6k2L6DROIDG4Q0fm4x+dwfsr6LGmROENnok75VGw40628xf+iR24WeMFcHuuBDUAzzsQ==
"@types/node@^14.14.37", "@types/node@^14.6.2":
version "14.18.21"
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.21.tgz#0155ee46f6be28b2ff0342ca1a9b9fd4468bef41"
integrity sha512-x5W9s+8P4XteaxT/jKF0PSb7XEvo5VmqEWgsMlyeY4ZlLK8I6aH6g5TPPyDlLAep+GYf4kefb7HFyc7PAO3m+Q==
"@types/node@^10.0.3":
version "10.17.60"
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.60.tgz#35f3d6213daed95da7f0f73e75bcc6980e90597b"
integrity sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==
"@types/node@^16.11.26", "@types/node@^16.18.3":
version "16.18.3"
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.3.tgz#d7f7ba828ad9e540270f01ce00d391c54e6e0abc"
integrity sha512-jh6m0QUhIRcZpNv7Z/rpN+ZWXOicUUQbSoWks7Htkbb9IjFQj4kzcX/xFCkjstCj5flMsN8FiSvt+q+Tcs4Llg==
"@types/normalize-package-data@^2.4.0":
version "2.4.1"
@ -342,6 +370,11 @@
dependencies:
"@types/node" "*"
"@types/semver-compare@^1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@types/semver-compare/-/semver-compare-1.0.1.tgz#17d1dc62c516c133ab01efb7803a537ee6eaf3d5"
integrity sha512-wx2LQVvKlEkhXp/HoKIZ/aSL+TvfJdKco8i0xJS3aR877mg4qBHzNT6+B5a61vewZHo79EdZavskGnRXEC2H6A==
"@types/semver@^7.3.6":
version "7.3.10"
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.10.tgz#5f19ee40cbeff87d916eedc8c2bfe2305d957f73"
@ -364,6 +397,13 @@
dependencies:
"@types/yargs-parser" "*"
"@types/yauzl@^2.9.1":
version "2.10.0"
resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.0.tgz#b3248295276cf8c6f153ebe6a9aba0c988cb2599"
integrity sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==
dependencies:
"@types/node" "*"
"@typescript-eslint/eslint-plugin@^5.28.0":
version "5.30.6"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.30.6.tgz#9c6017b6c1d04894141b4a87816388967f64c359"
@ -554,6 +594,11 @@ ansi-styles@^6.0.0:
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.1.0.tgz#87313c102b8118abd57371afab34618bf7350ed3"
integrity sha512-VbqNsoz55SYGczauuup0MFUyXNQviSpFTj1RQtFzmQLk18qbVSpTFFGMT293rmDaQuKCT6InmbuEyUne4mTuxQ==
any-shell-escape@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/any-shell-escape/-/any-shell-escape-0.1.1.tgz#d55ab972244c71a9a5e1ab0879f30bf110806959"
integrity sha512-36j4l5HVkboyRhIWgtMh1I9i8LTdFqVwDEHy1cp+QioJyKgAUG40X0W8s7jakWRta/Sjvm8mUG1fU6Tj8mWagQ==
anymatch@~3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716"
@ -887,7 +932,7 @@ camelcase@^6.2.0:
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a"
integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==
caseless@~0.12.0:
caseless@^0.12.0, caseless@~0.12.0:
version "0.12.0"
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
integrity sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==
@ -1066,14 +1111,14 @@ concat-map@0.0.1:
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
concat-stream@^1.6.2:
version "1.6.2"
resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34"
integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==
concat-stream@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-2.0.0.tgz#414cf5af790a48c60ab9be4527d56d5e41133cb1"
integrity sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==
dependencies:
buffer-from "^1.0.0"
inherits "^2.0.3"
readable-stream "^2.2.2"
readable-stream "^3.0.2"
typedarray "^0.0.6"
concurrently@^7.0.0:
@ -1194,7 +1239,7 @@ debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, d
dependencies:
ms "2.1.2"
debug@^2.1.3, debug@^2.2.0, debug@^2.6.8, debug@^2.6.9:
debug@^2.1.3, debug@^2.2.0, debug@^2.6.8:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
@ -1469,14 +1514,14 @@ electron-updater@^4.3.8:
lodash.isequal "^4.5.0"
semver "^7.3.5"
electron@^15.3.0:
version "15.5.7"
resolved "https://registry.yarnpkg.com/electron/-/electron-15.5.7.tgz#aadb0081c504f2c2d8f81ea5fd23e38881afe86a"
integrity sha512-n4mVlxoMc4eYx07wWFWGficL+iOMz5xZEf5dBtE/wwLm0fQpYVyW4AlknMFG9F8Css0MM0JSwNMOyRg5e1vDtg==
electron@^21.2.2:
version "21.3.0"
resolved "https://registry.yarnpkg.com/electron/-/electron-21.3.0.tgz#e9905e240add950443dc115b4be13d36162f0a05"
integrity sha512-MGRpshN8fBcx4IRuBABIsGDv0tB/MclIFsyFHFFXsBCUc+vIXaE/E6vuWaniGIFSz5WyeuapfTH5IeRb+7yIfw==
dependencies:
"@electron/get" "^1.13.0"
"@types/node" "^14.6.2"
extract-zip "^1.0.3"
"@electron/get" "^1.14.1"
"@types/node" "^16.11.26"
extract-zip "^2.0.1"
emoji-regex@^8.0.0:
version "8.0.0"
@ -1705,15 +1750,16 @@ extend@~3.0.2:
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
extract-zip@^1.0.3:
version "1.7.0"
resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.7.0.tgz#556cc3ae9df7f452c493a0cfb51cc30277940927"
integrity sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA==
extract-zip@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a"
integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==
dependencies:
concat-stream "^1.6.2"
debug "^2.6.9"
mkdirp "^0.5.4"
debug "^4.1.1"
get-stream "^5.1.0"
yauzl "^2.10.0"
optionalDependencies:
"@types/yauzl" "^2.9.1"
extsprintf@1.3.0:
version "1.3.0"
@ -1765,6 +1811,16 @@ fd-slicer@~1.1.0:
dependencies:
pend "~1.2.0"
ffmpeg-static@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/ffmpeg-static/-/ffmpeg-static-5.1.0.tgz#133500f4566570c5a0e96795152b0526d8c936ad"
integrity sha512-eEWOiGdbf7HKPeJI5PoJ0oCwkL0hckL2JdS4JOuB/gUETppwkEpq8nF0+e6VEQnDCo/iuoipbTUsn9QJmtpNkg==
dependencies:
"@derhuerst/http-basic" "^8.2.0"
env-paths "^2.2.0"
https-proxy-agent "^5.0.0"
progress "^2.0.3"
file-entry-cache@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027"
@ -1819,6 +1875,15 @@ forever-agent@~0.6.1:
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
integrity sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==
form-data@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f"
integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.8"
mime-types "^2.1.12"
form-data@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
@ -2147,6 +2212,13 @@ http-proxy-agent@^5.0.0:
agent-base "6"
debug "4"
http-response-object@^3.0.1:
version "3.0.2"
resolved "https://registry.yarnpkg.com/http-response-object/-/http-response-object-3.0.2.tgz#7f435bb210454e4360d074ef1f989d5ea8aa9810"
integrity sha512-bqX0XTF6fnXSQcEJ2Iuyr75yVakyjIDCqroJQ/aHfSdlM743Cwqoi2nDYMzLGWUcuTWGWy8AAvOKXTfiv6q9RA==
dependencies:
"@types/node" "^10.0.3"
http-signature@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1"
@ -2745,7 +2817,7 @@ minizlib@^2.1.1:
minipass "^3.0.0"
yallist "^4.0.0"
mkdirp@^0.5.1, mkdirp@^0.5.4, mkdirp@^0.5.5:
mkdirp@^0.5.1, mkdirp@^0.5.5:
version "0.5.6"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6"
integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==
@ -2973,6 +3045,11 @@ parent-module@^1.0.0:
dependencies:
callsites "^3.0.0"
parse-cache-control@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/parse-cache-control/-/parse-cache-control-1.0.1.tgz#8eeab3e54fa56920fe16ba38f77fa21aacc2d74e"
integrity sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg==
parse-json@^5.0.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd"
@ -3193,7 +3270,7 @@ read-pkg@^5.2.0:
parse-json "^5.0.0"
type-fest "^0.6.0"
readable-stream@^2.0.6, readable-stream@^2.2.2:
readable-stream@^2.0.6:
version "2.3.7"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57"
integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==
@ -3206,6 +3283,15 @@ readable-stream@^2.0.6, readable-stream@^2.2.2:
string_decoder "~1.1.1"
util-deprecate "~1.0.1"
readable-stream@^3.0.2:
version "3.6.0"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==
dependencies:
inherits "^2.0.3"
string_decoder "^1.1.1"
util-deprecate "^1.0.1"
readable-stream@~1.1.9:
version "1.1.14"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9"
@ -3350,7 +3436,7 @@ rxjs@^7.0.0, rxjs@^7.5.5:
dependencies:
tslib "^2.1.0"
safe-buffer@^5.0.1, safe-buffer@^5.1.2:
safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0:
version "5.2.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
@ -3594,6 +3680,13 @@ string-width@^5.0.0:
emoji-regex "^9.2.2"
strip-ansi "^7.0.1"
string_decoder@^1.1.1:
version "1.3.0"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e"
integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==
dependencies:
safe-buffer "~5.2.0"
string_decoder@~0.10.x:
version "0.10.31"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
@ -3941,7 +4034,7 @@ utf8-byte-length@^1.0.1:
resolved "https://registry.yarnpkg.com/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz#f45f150c4c66eee968186505ab93fcbb8ad6bf61"
integrity sha512-4+wkEYLBbWxqTahEsWrhxepcoVOJ+1z5PGIjPZxRkytcdSUaNjIjBM7Xn8E+pdSuV7SzvWovBFA54FO0JSoqhA==
util-deprecate@~1.0.1:
util-deprecate@^1.0.1, util-deprecate@~1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==