diff --git a/web/apps/photos/src/components/Directory/index.tsx b/web/apps/photos/src/components/Directory/index.tsx index 245be0f5b..a99581134 100644 --- a/web/apps/photos/src/components/Directory/index.tsx +++ b/web/apps/photos/src/components/Directory/index.tsx @@ -1,4 +1,4 @@ -import ElectronAPIs from "@/next/electron"; +import { ensureElectron } from "@/next/electron"; import log from "@/next/log"; import LinkButton from "@ente/shared/components/LinkButton"; import { Tooltip } from "@mui/material"; @@ -19,7 +19,7 @@ const DirectoryPathContainer = styled(LinkButton)( export const DirectoryPath = ({ width, path }) => { const handleClick = async () => { try { - await ElectronAPIs.openDirectory(path); + await ensureElectron().openDirectory(path); } catch (e) { log.error("openDirectory failed", e); } diff --git a/web/apps/photos/src/services/clipService.ts b/web/apps/photos/src/services/clipService.ts index b4f989f10..53e026d4f 100644 --- a/web/apps/photos/src/services/clipService.ts +++ b/web/apps/photos/src/services/clipService.ts @@ -1,4 +1,4 @@ -import ElectronAPIs from "@/next/electron"; +import { ensureElectron } from "@/next/electron"; import log from "@/next/log"; import ComlinkCryptoWorker from "@ente/shared/crypto"; import { CustomError } from "@ente/shared/error"; @@ -157,7 +157,7 @@ class ClipServiceImpl { model: Model = Model.ONNX_CLIP, ): Promise => { try { - return ElectronAPIs.computeTextEmbedding(model, text); + return ensureElectron().computeTextEmbedding(model, text); } catch (e) { if (e?.message?.includes(CustomError.UNSUPPORTED_PLATFORM)) { this.unsupportedPlatform = true; @@ -304,7 +304,10 @@ class ClipServiceImpl { const file = await localFile .arrayBuffer() .then((buffer) => new Uint8Array(buffer)); - const embedding = await ElectronAPIs.computeImageEmbedding(model, file); + const embedding = await ensureElectron().computeImageEmbedding( + model, + file, + ); return embedding; }; @@ -344,7 +347,7 @@ class ClipServiceImpl { file: EnteFile, ) => { const thumb = await downloadManager.getThumbnail(file); - const embedding = await ElectronAPIs.computeImageEmbedding( + const embedding = await ensureElectron().computeImageEmbedding( model, thumb, ); diff --git a/web/apps/photos/src/services/export/index.ts b/web/apps/photos/src/services/export/index.ts index da9b3b169..70ebf2027 100644 --- a/web/apps/photos/src/services/export/index.ts +++ b/web/apps/photos/src/services/export/index.ts @@ -57,7 +57,6 @@ import downloadManager from "../download"; import { getAllLocalFiles } from "../fileService"; import { decodeLivePhoto } from "../livePhotoService"; import { migrateExport } from "./migration"; -import type { Electron } from "@/next/types/ipc"; const EXPORT_RECORD_FILE_NAME = "export_status.json"; @@ -71,15 +70,6 @@ export const NULL_EXPORT_RECORD: ExportRecord = { collectionExportNames: {}, }; -const electron = (): Electron => { - const et = globalThis.electron; - if (!et) - throw new Error( - "Attempting to use ExportService in an unsupported non-electron context", - ); - return et; -}; - class ExportService { private exportSettings: ExportSettings; private exportInProgress: RequestCanceller = null; @@ -166,12 +156,12 @@ class ExportService { async changeExportDirectory() { try { - const newRootDir = await electron().selectDirectory(); + const newRootDir = await ensureElectron().selectDirectory(); if (!newRootDir) { throw Error(CustomError.SELECT_FOLDER_ABORTED); } const newExportDir = `${newRootDir}/${ENTE_EXPORT_DIRECTORY}`; - await electron().checkExistsAndCreateDir(newExportDir); + await ensureElectron().checkExistsAndCreateDir(newExportDir); return newExportDir; } catch (e) { if (e.message !== CustomError.SELECT_FOLDER_ABORTED) { @@ -521,7 +511,7 @@ class ExportService { newCollectionExportName, ); try { - await electron().rename( + await ensureElectron().rename( oldCollectionExportPath, newCollectionExportPath, ); @@ -606,11 +596,13 @@ class ExportService { ); try { // delete the collection metadata folder - await electron().deleteFolder( + await ensureElectron().deleteFolder( getMetadataFolderExportPath(collectionExportPath), ); // delete the collection folder - await electron().deleteFolder(collectionExportPath); + await ensureElectron().deleteFolder( + collectionExportPath, + ); } catch (e) { await this.addCollectionExportedRecord( exportFolder, @@ -693,10 +685,10 @@ class ExportService { exportDir, collectionExportName, ); - await electron().checkExistsAndCreateDir( + await ensureElectron().checkExistsAndCreateDir( collectionExportPath, ); - await electron().checkExistsAndCreateDir( + await ensureElectron().checkExistsAndCreateDir( getMetadataFolderExportPath(collectionExportPath), ); await this.downloadAndSave( @@ -776,7 +768,7 @@ class ExportService { `moving image file ${imageExportPath} to trash folder`, ); if (await this.exists(imageExportPath)) { - await electron().moveFile( + await ensureElectron().moveFile( imageExportPath, await getTrashedFileExportPath( exportDir, @@ -791,7 +783,7 @@ class ExportService { if ( await this.exists(imageMetadataFileExportPath) ) { - await electron().moveFile( + await ensureElectron().moveFile( imageMetadataFileExportPath, await getTrashedFileExportPath( exportDir, @@ -808,7 +800,7 @@ class ExportService { `moving video file ${videoExportPath} to trash folder`, ); if (await this.exists(videoExportPath)) { - await electron().moveFile( + await ensureElectron().moveFile( videoExportPath, await getTrashedFileExportPath( exportDir, @@ -821,7 +813,7 @@ class ExportService { if ( await this.exists(videoMetadataFileExportPath) ) { - await electron().moveFile( + await ensureElectron().moveFile( videoMetadataFileExportPath, await getTrashedFileExportPath( exportDir, @@ -843,7 +835,7 @@ class ExportService { `moving file ${fileExportPath} to ${trashedFilePath} trash folder`, ); if (await this.exists(fileExportPath)) { - await electron().moveFile( + await ensureElectron().moveFile( fileExportPath, trashedFilePath, ); @@ -851,7 +843,7 @@ class ExportService { const metadataFileExportPath = getMetadataFileExportPath(fileExportPath); if (await this.exists(metadataFileExportPath)) { - await electron().moveFile( + await ensureElectron().moveFile( metadataFileExportPath, await getTrashedFileExportPath( exportDir, @@ -990,7 +982,7 @@ class ExportService { try { const exportRecord = await this.getExportRecord(folder); const newRecord: ExportRecord = { ...exportRecord, ...newData }; - await electron().saveFileToDisk( + await ensureElectron().saveFileToDisk( `${folder}/${EXPORT_RECORD_FILE_NAME}`, JSON.stringify(newRecord, null, 2), ); @@ -1012,7 +1004,7 @@ class ExportService { return this.createEmptyExportRecord(exportRecordJSONPath); } const recordFile = - await electron().readTextFile(exportRecordJSONPath); + await ensureElectron().readTextFile(exportRecordJSONPath); try { return JSON.parse(recordFile); } catch (e) { @@ -1048,8 +1040,8 @@ class ExportService { exportFolder, collectionExportName, ); - await electron().checkExistsAndCreateDir(collectionExportPath); - await electron().checkExistsAndCreateDir( + await ensureElectron().checkExistsAndCreateDir(collectionExportPath); + await ensureElectron().checkExistsAndCreateDir( getMetadataFolderExportPath(collectionExportPath), ); @@ -1096,7 +1088,7 @@ class ExportService { fileExportName, file, ); - await electron().saveStreamToDisk( + await ensureElectron().saveStreamToDisk( getFileExportPath(collectionExportPath, fileExportName), updatedFileStream, ); @@ -1144,7 +1136,7 @@ class ExportService { imageExportName, file, ); - await electron().saveStreamToDisk( + await ensureElectron().saveStreamToDisk( getFileExportPath(collectionExportPath, imageExportName), imageStream, ); @@ -1156,12 +1148,12 @@ class ExportService { file, ); try { - await electron().saveStreamToDisk( + await ensureElectron().saveStreamToDisk( getFileExportPath(collectionExportPath, videoExportName), videoStream, ); } catch (e) { - await electron().deleteFile( + await ensureElectron().deleteFile( getFileExportPath(collectionExportPath, imageExportName), ); throw e; @@ -1177,7 +1169,7 @@ class ExportService { fileExportName: string, file: EnteFile, ) { - await electron().saveFileToDisk( + await ensureElectron().saveFileToDisk( getFileMetadataExportPath(collectionExportPath, fileExportName), getGoogleLikeMetadataFile(fileExportName, file), ); @@ -1188,15 +1180,15 @@ class ExportService { }; exists = (path: string) => { - return electron().fs.exists(path); + return ensureElectron().fs.exists(path); }; rename = (oldPath: string, newPath: string) => { - return electron().rename(oldPath, newPath); + return ensureElectron().rename(oldPath, newPath); }; checkExistsAndCreateDir = (path: string) => { - return electron().checkExistsAndCreateDir(path); + return ensureElectron().checkExistsAndCreateDir(path); }; exportFolderExists = async (exportFolder: string) => { @@ -1218,7 +1210,7 @@ class ExportService { private createEmptyExportRecord = async (exportRecordJSONPath: string) => { const exportRecord: ExportRecord = NULL_EXPORT_RECORD; - await electron().saveFileToDisk( + await ensureElectron().saveFileToDisk( exportRecordJSONPath, JSON.stringify(exportRecord, null, 2), ); diff --git a/web/apps/photos/src/services/importService.ts b/web/apps/photos/src/services/importService.ts index 8e15e9fef..6d2c46a85 100644 --- a/web/apps/photos/src/services/importService.ts +++ b/web/apps/photos/src/services/importService.ts @@ -1,5 +1,5 @@ +import { ensureElectron } from "@/next/electron"; import log from "@/next/log"; -import type { Electron } from "@/next/types/ipc"; import { PICKED_UPLOAD_TYPE } from "constants/upload"; import { Collection } from "types/collection"; import { ElectronFile, FileWithCollection } from "types/upload"; @@ -10,20 +10,11 @@ interface PendingUploads { type: PICKED_UPLOAD_TYPE; } -const electron = (): Electron => { - const et = globalThis.electron; - if (!et) - throw new Error( - "Attempting to use ExportService in an unsupported non-electron context", - ); - return et; -}; - class ImportService { async getPendingUploads(): Promise { try { const pendingUploads = - (await electron().getPendingUploads()) as PendingUploads; + (await ensureElectron().getPendingUploads()) as PendingUploads; return pendingUploads; } catch (e) { if (e?.message?.includes("ENOENT: no such file or directory")) { @@ -49,7 +40,7 @@ class ImportService { if (collections.length === 1) { collectionName = collections[0].name; } - await electron().setToUploadCollection(collectionName); + await ensureElectron().setToUploadCollection(collectionName); } async updatePendingUploads(files: FileWithCollection[]) { @@ -66,13 +57,17 @@ class ImportService { filePaths.push((fileWithCollection.file as ElectronFile).path); } } - await electron().setToUploadFiles(PICKED_UPLOAD_TYPE.FILES, filePaths); + await ensureElectron().setToUploadFiles( + PICKED_UPLOAD_TYPE.FILES, + filePaths, + ); } async cancelRemainingUploads() { - await electron().setToUploadCollection(null); - await electron().setToUploadFiles(PICKED_UPLOAD_TYPE.ZIPS, []); - await electron().setToUploadFiles(PICKED_UPLOAD_TYPE.FILES, []); + const electron = ensureElectron(); + await electron.setToUploadCollection(null); + await electron.setToUploadFiles(PICKED_UPLOAD_TYPE.ZIPS, []); + await electron.setToUploadFiles(PICKED_UPLOAD_TYPE.FILES, []); } } diff --git a/web/apps/photos/src/services/upload/thumbnailService.ts b/web/apps/photos/src/services/upload/thumbnailService.ts index 62a90f48e..071ef3078 100644 --- a/web/apps/photos/src/services/upload/thumbnailService.ts +++ b/web/apps/photos/src/services/upload/thumbnailService.ts @@ -1,4 +1,4 @@ -import ElectronAPIs from "@/next/electron"; +import { ensureElectron } from "@/next/electron"; import { convertBytesToHumanReadable, getFileNameSize } from "@/next/file"; import log from "@/next/log"; import { CustomError } from "@ente/shared/error"; @@ -98,7 +98,7 @@ const generateImageThumbnailInElectron = async ( ): Promise => { try { const startTime = Date.now(); - const thumb = await ElectronAPIs.generateImageThumbnail( + const thumb = await ensureElectron().generateImageThumbnail( inputFile, maxDimension, maxSize, diff --git a/web/apps/photos/src/services/watchFolder/watchFolderService.ts b/web/apps/photos/src/services/watchFolder/watchFolderService.ts index 6d37c759f..791aed445 100644 --- a/web/apps/photos/src/services/watchFolder/watchFolderService.ts +++ b/web/apps/photos/src/services/watchFolder/watchFolderService.ts @@ -1,5 +1,5 @@ +import { ensureElectron } from "@/next/electron"; import log from "@/next/log"; -import type { Electron } from "@/next/types/ipc"; import { UPLOAD_RESULT, UPLOAD_STRATEGY } from "constants/upload"; import debounce from "debounce"; import uploadManager from "services/upload/uploadManager"; @@ -22,15 +22,6 @@ import { diskFolderRemovedCallback, } from "./watchFolderEventHandlers"; -const electron = (): Electron => { - const et = globalThis.electron; - if (!et) - throw new Error( - "Attempting to use ExportService in an unsupported non-electron context", - ); - return et; -}; - class watchFolderService { private eventQueue: EventQueueItem[] = []; private currentEvent: EventQueueItem; @@ -92,7 +83,7 @@ class watchFolderService { for (const mapping of mappings) { const filesOnDisk: ElectronFile[] = - await electron().getDirFiles(mapping.folderPath); + await ensureElectron().getDirFiles(mapping.folderPath); this.uploadDiffOfFiles(mapping, filesOnDisk); this.trashDiffOfFiles(mapping, filesOnDisk); @@ -159,9 +150,11 @@ class watchFolderService { ): Promise { const notDeletedMappings = []; for (const mapping of mappings) { - const mappingExists = await electron().isFolder(mapping.folderPath); + const mappingExists = await ensureElectron().isFolder( + mapping.folderPath, + ); if (!mappingExists) { - electron().removeWatchMapping(mapping.folderPath); + ensureElectron().removeWatchMapping(mapping.folderPath); } else { notDeletedMappings.push(mapping); } @@ -179,7 +172,7 @@ class watchFolderService { } private setupWatcherFunctions() { - electron().registerWatcherFunctions( + ensureElectron().registerWatcherFunctions( diskFileAddedCallback, diskFileRemovedCallback, diskFolderRemovedCallback, @@ -192,7 +185,7 @@ class watchFolderService { uploadStrategy: UPLOAD_STRATEGY, ) { try { - await electron().addWatchMapping( + await ensureElectron().addWatchMapping( rootFolderName, folderPath, uploadStrategy, @@ -205,7 +198,7 @@ class watchFolderService { async removeWatchMapping(folderPath: string) { try { - await electron().removeWatchMapping(folderPath); + await ensureElectron().removeWatchMapping(folderPath); } catch (e) { log.error("error while removing watch mapping", e); } @@ -213,7 +206,7 @@ class watchFolderService { async getWatchMappings(): Promise { try { - return (await electron().getWatchMappings()) ?? []; + return (await ensureElectron().getWatchMappings()) ?? []; } catch (e) { log.error("error while getting watch mappings", e); return []; @@ -385,7 +378,7 @@ class watchFolderService { ...this.currentlySyncedMapping.syncedFiles, ...syncedFiles, ]; - await electron().updateWatchMappingSyncedFiles( + await ensureElectron().updateWatchMappingSyncedFiles( this.currentlySyncedMapping.folderPath, this.currentlySyncedMapping.syncedFiles, ); @@ -395,7 +388,7 @@ class watchFolderService { ...this.currentlySyncedMapping.ignoredFiles, ...ignoredFiles, ]; - await electron().updateWatchMappingIgnoredFiles( + await ensureElectron().updateWatchMappingIgnoredFiles( this.currentlySyncedMapping.folderPath, this.currentlySyncedMapping.ignoredFiles, ); @@ -510,7 +503,7 @@ class watchFolderService { this.currentlySyncedMapping.syncedFiles.filter( (file) => !filePathsToRemove.has(file.path), ); - await electron().updateWatchMappingSyncedFiles( + await ensureElectron().updateWatchMappingSyncedFiles( this.currentlySyncedMapping.folderPath, this.currentlySyncedMapping.syncedFiles, ); @@ -602,7 +595,7 @@ class watchFolderService { async selectFolder(): Promise { try { - const folderPath = await electron().selectDirectory(); + const folderPath = await ensureElectron().selectDirectory(); return folderPath; } catch (e) { log.error("error while selecting folder", e); @@ -630,7 +623,7 @@ class watchFolderService { async isFolder(folderPath: string) { try { - const isFolder = await electron().isFolder(folderPath); + const isFolder = await ensureElectron().isFolder(folderPath); return isFolder; } catch (e) { log.error("error while checking if folder exists", e); diff --git a/web/apps/photos/src/utils/collection/index.ts b/web/apps/photos/src/utils/collection/index.ts index f4b1aa88b..c18861515 100644 --- a/web/apps/photos/src/utils/collection/index.ts +++ b/web/apps/photos/src/utils/collection/index.ts @@ -1,4 +1,3 @@ -import ElectronAPIs from "@/next/electron"; import log from "@/next/log"; import { CustomError } from "@ente/shared/error"; import { getAlbumsURL } from "@ente/shared/network/api"; @@ -17,7 +16,6 @@ import { SYSTEM_COLLECTION_TYPES, } from "constants/collection"; import { t } from "i18next"; -import isElectron from "is-electron"; import { addToCollection, createAlbum, @@ -152,8 +150,9 @@ export async function downloadCollectionFiles( return; } let downloadDirPath: string; - if (isElectron()) { - const selectedDir = await ElectronAPIs.selectDirectory(); + const electron = globalThis.electron; + if (electron) { + const selectedDir = await electron.selectDirectory(); if (!selectedDir) { return; } diff --git a/web/packages/next/electron.ts b/web/packages/next/electron.ts index 34c42119b..de9611fec 100644 --- a/web/packages/next/electron.ts +++ b/web/packages/next/electron.ts @@ -20,3 +20,27 @@ const ElectronAPIs = (globalThis as unknown as any)[ // export const globalElectron = globalThis.electron; export default ElectronAPIs; + +/** + * A wrapper over a non-null assertion of `globalThis.electron`. + * + * This is useful where we have previously verified that the code path in which + * we're running only executes when we're in electron (usually by directly + * checking that `globalThis.electron` is defined somewhere up the chain). + * + * Generally, this should not be required - the check and the use should be + * colocated, or the unwrapped non-null value saved somewhere. But sometimes + * doing so requires code refactoring, so as an escape hatch we provide this + * convenience function. + * + * It will throw if `globalThis.electron` is undefined. + * + * @see `global-electron.d.ts`. + */ +export const ensureElectron = (): Electron => { + const et = globalThis.electron; + if (et) return et; + throw new Error( + "Attempting to assert globalThis.electron in a non-electron context", + ); +}; diff --git a/web/packages/next/worker/comlink-worker.ts b/web/packages/next/worker/comlink-worker.ts index af384bf59..033c79fa8 100644 --- a/web/packages/next/worker/comlink-worker.ts +++ b/web/packages/next/worker/comlink-worker.ts @@ -1,4 +1,4 @@ -import ElectronAPIs from "@/next/electron"; +import { ensureElectron } from "@/next/electron"; import log, { logToDisk } from "@/next/log"; import { expose, wrap, type Remote } from "comlink"; @@ -45,7 +45,7 @@ export class ComlinkWorker InstanceType> { const workerBridge = { logToDisk, convertToJPEG: (inputFileData: Uint8Array, filename: string) => - ElectronAPIs.convertToJPEG(inputFileData, filename), + ensureElectron().convertToJPEG(inputFileData, filename), }; export type WorkerBridge = typeof workerBridge;