diff --git a/core/src/api/index.ts b/core/src/api/index.ts new file mode 100644 index 0000000000..b45a41d0e3 --- /dev/null +++ b/core/src/api/index.ts @@ -0,0 +1,100 @@ +/** + * App Route APIs + * @description Enum of all the routes exposed by the app + */ +export enum AppRoute { + setNativeThemeLight = 'setNativeThemeLight', + setNativeThemeDark = 'setNativeThemeDark', + setNativeThemeSystem = 'setNativeThemeSystem', + appDataPath = 'appDataPath', + appVersion = 'appVersion', + getResourcePath = 'getResourcePath', + openExternalUrl = 'openExternalUrl', + openAppDirectory = 'openAppDirectory', + openFileExplore = 'openFileExplorer', + relaunch = 'relaunch', +} + +export enum AppEvent { + onAppUpdateDownloadUpdate = 'onAppUpdateDownloadUpdate', + onAppUpdateDownloadError = 'onAppUpdateDownloadError', + onAppUpdateDownloadSuccess = 'onAppUpdateDownloadSuccess', +} + +export enum DownloadRoute { + abortDownload = 'abortDownload', + downloadFile = 'downloadFile', + pauseDownload = 'pauseDownload', + resumeDownload = 'resumeDownload', +} + +export enum DownloadEvent { + onFileDownloadUpdate = 'onFileDownloadUpdate', + onFileDownloadError = 'onFileDownloadError', + onFileDownloadSuccess = 'onFileDownloadSuccess', +} + +export enum ExtensionRoute { + baseExtensions = 'baseExtensions', + getActiveExtensions = 'getActiveExtensions', + installExtension = 'installExtension', + invokeExtensionFunc = 'invokeExtensionFunc', + updateExtension = 'updateExtension', + uninstallExtension = 'uninstallExtension', +} +export enum FileSystemRoute { + appendFile = 'appendFile', + copyFile = 'copyFile', + deleteFile = 'deleteFile', + exists = 'exists', + getResourcePath = 'getResourcePath', + getUserSpace = 'getUserSpace', + isDirectory = 'isDirectory', + listFiles = 'listFiles', + mkdir = 'mkdir', + readFile = 'readFile', + readLineByLine = 'readLineByLine', + rmdir = 'rmdir', + writeFile = 'writeFile', +} + +export type ApiFunction = (...args: any[]) => any + +export type AppRouteFunctions = { + [K in AppRoute]: ApiFunction +} + +export type AppEventFunctions = { + [K in AppEvent]: ApiFunction +} + +export type DownloadRouteFunctions = { + [K in DownloadRoute]: ApiFunction +} + +export type DownloadEventFunctions = { + [K in DownloadEvent]: ApiFunction +} + +export type ExtensionRouteFunctions = { + [K in ExtensionRoute]: ApiFunction +} + +export type FileSystemRouteFunctions = { + [K in FileSystemRoute]: ApiFunction +} + +export type APIFunctions = AppRouteFunctions & + AppEventFunctions & + DownloadRouteFunctions & + DownloadEventFunctions & + ExtensionRouteFunctions & + FileSystemRouteFunctions + +export const APIRoutes = [ + ...Object.values(AppRoute), + ...Object.values(DownloadRoute), + ...Object.values(ExtensionRoute), + ...Object.values(FileSystemRoute), +] +export const APIEvents = [...Object.values(AppEvent), ...Object.values(DownloadEvent)] diff --git a/core/src/core.ts b/core/src/core.ts index b86c6fa6fa..f268233b7f 100644 --- a/core/src/core.ts +++ b/core/src/core.ts @@ -30,13 +30,6 @@ const downloadFile: (url: string, fileName: string) => Promise = (url, file const abortDownload: (fileName: string) => Promise = (fileName) => global.core.api?.abortDownload(fileName) -/** - * Retrieves the path to the app data directory using the `coreAPI` object. - * If the `coreAPI` object is not available, the function returns `undefined`. - * @returns A Promise that resolves with the path to the app data directory, or `undefined` if the `coreAPI` object is not available. - */ -const appDataPath: () => Promise = () => global.core.api?.appDataPath() - /** * Gets the user space path. * @returns {Promise} A Promise that resolves with the user space path. @@ -70,7 +63,6 @@ export { executeOnMain, downloadFile, abortDownload, - appDataPath, getUserSpace, openFileExplorer, getResourcePath, diff --git a/core/src/index.ts b/core/src/index.ts index ff233ffb3c..a56b6f0e13 100644 --- a/core/src/index.ts +++ b/core/src/index.ts @@ -2,34 +2,39 @@ * Export all types. * @module */ -export * from "./types/index"; +export * from './types/index' + +/** + * Export all routes + */ +export * from './api' /** * Export Core module * @module */ -export * from "./core"; +export * from './core' /** * Export Event module. * @module */ -export * from "./events"; +export * from './events' /** * Export Filesystem module. * @module */ -export * from "./fs"; +export * from './fs' /** * Export Extension module. * @module */ -export * from "./extension"; +export * from './extension' /** * Export all base extensions. * @module */ -export * from "./extensions/index"; +export * from './extensions/index' diff --git a/electron/handlers/app.ts b/electron/handlers/app.ts index adbc875b2e..d2b3acda15 100644 --- a/electron/handlers/app.ts +++ b/electron/handlers/app.ts @@ -1,18 +1,35 @@ -import { app, ipcMain, shell } from 'electron' +import { app, ipcMain, shell, nativeTheme } from 'electron' import { ModuleManager } from './../managers/module' import { join } from 'path' import { ExtensionManager } from './../managers/extension' import { WindowManager } from './../managers/window' import { userSpacePath } from './../utils/path' +import { AppRoute } from '@janhq/core' +import { getResourcePath } from './../utils/path' export function handleAppIPCs() { /** - * Retrieves the path to the app data directory using the `coreAPI` object. - * If the `coreAPI` object is not available, the function returns `undefined`. - * @returns A Promise that resolves with the path to the app data directory, or `undefined` if the `coreAPI` object is not available. + * Handles the "setNativeThemeLight" IPC message by setting the native theme source to "light". + * This will change the appearance of the app to the light theme. */ - ipcMain.handle('appDataPath', async (_event) => { - return app.getPath('userData') + ipcMain.handle(AppRoute.setNativeThemeLight, () => { + nativeTheme.themeSource = 'light' + }) + + /** + * Handles the "setNativeThemeDark" IPC message by setting the native theme source to "dark". + * This will change the appearance of the app to the dark theme. + */ + ipcMain.handle(AppRoute.setNativeThemeDark, () => { + nativeTheme.themeSource = 'dark' + }) + + /** + * Handles the "setNativeThemeSystem" IPC message by setting the native theme source to "system". + * This will change the appearance of the app to match the system's current theme. + */ + ipcMain.handle(AppRoute.setNativeThemeSystem, () => { + nativeTheme.themeSource = 'system' }) /** @@ -20,7 +37,7 @@ export function handleAppIPCs() { * @param _event - The IPC event object. * @returns The version of the app. */ - ipcMain.handle('appVersion', async (_event) => { + ipcMain.handle(AppRoute.appVersion, async (_event) => { return app.getVersion() }) @@ -29,16 +46,20 @@ export function handleAppIPCs() { * The `shell.openPath` method is used to open the directory in the user's default file explorer. * @param _event - The IPC event object. */ - ipcMain.handle('openAppDirectory', async (_event) => { + ipcMain.handle(AppRoute.openAppDirectory, async (_event) => { shell.openPath(userSpacePath) }) + ipcMain.handle(AppRoute.getResourcePath, async (_event) => { + return getResourcePath() + }) + /** * Opens a URL in the user's default browser. * @param _event - The IPC event object. * @param url - The URL to open. */ - ipcMain.handle('openExternalUrl', async (_event, url) => { + ipcMain.handle(AppRoute.openExternalUrl, async (_event, url) => { shell.openExternal(url) }) @@ -47,7 +68,7 @@ export function handleAppIPCs() { * @param _event - The IPC event object. * @param url - The URL to reload. */ - ipcMain.handle('relaunch', async (_event, url) => { + ipcMain.handle(AppRoute.relaunch, async (_event, url) => { ModuleManager.instance.clearImportedModules() if (app.isPackaged) { @@ -56,9 +77,7 @@ export function handleAppIPCs() { } else { for (const modulePath in ModuleManager.instance.requiredModules) { delete require.cache[ - require.resolve( - join(userSpacePath, 'extensions', modulePath) - ) + require.resolve(join(userSpacePath, 'extensions', modulePath)) ] } ExtensionManager.instance.setupExtensions() diff --git a/electron/handlers/download.ts b/electron/handlers/download.ts index 1776fccd9a..6e64d23e2f 100644 --- a/electron/handlers/download.ts +++ b/electron/handlers/download.ts @@ -4,7 +4,7 @@ import { resolve, join } from 'path' import { WindowManager } from './../managers/window' import request from 'request' import { createWriteStream } from 'fs' -import { getResourcePath } from './../utils/path' +import { DownloadEvent, DownloadRoute } from '@janhq/core' const progress = require('request-progress') export function handleDownloaderIPCs() { @@ -13,7 +13,7 @@ export function handleDownloaderIPCs() { * @param _event - The IPC event object. * @param fileName - The name of the file being downloaded. */ - ipcMain.handle('pauseDownload', async (_event, fileName) => { + ipcMain.handle(DownloadRoute.pauseDownload, async (_event, fileName) => { DownloadManager.instance.networkRequests[fileName]?.pause() }) @@ -22,7 +22,7 @@ export function handleDownloaderIPCs() { * @param _event - The IPC event object. * @param fileName - The name of the file being downloaded. */ - ipcMain.handle('resumeDownload', async (_event, fileName) => { + ipcMain.handle(DownloadRoute.resumeDownload, async (_event, fileName) => { DownloadManager.instance.networkRequests[fileName]?.resume() }) @@ -32,23 +32,19 @@ export function handleDownloaderIPCs() { * @param _event - The IPC event object. * @param fileName - The name of the file being downloaded. */ - ipcMain.handle('abortDownload', async (_event, fileName) => { + ipcMain.handle(DownloadRoute.abortDownload, async (_event, fileName) => { const rq = DownloadManager.instance.networkRequests[fileName] DownloadManager.instance.networkRequests[fileName] = undefined rq?.abort() }) - ipcMain.handle('getResourcePath', async (_event) => { - return getResourcePath() - }) - /** * Downloads a file from a given URL. * @param _event - The IPC event object. * @param url - The URL to download the file from. * @param fileName - The name to give the downloaded file. */ - ipcMain.handle('downloadFile', async (_event, url, fileName) => { + ipcMain.handle(DownloadRoute.downloadFile, async (_event, url, fileName) => { const userDataPath = join(app.getPath('home'), 'jan') const destination = resolve(userDataPath, fileName) const rq = request(url) @@ -56,7 +52,7 @@ export function handleDownloaderIPCs() { progress(rq, {}) .on('progress', function (state: any) { WindowManager?.instance.currentWindow?.webContents.send( - 'FILE_DOWNLOAD_UPDATE', + DownloadEvent.onFileDownloadUpdate, { ...state, fileName, @@ -65,7 +61,7 @@ export function handleDownloaderIPCs() { }) .on('error', function (err: Error) { WindowManager?.instance.currentWindow?.webContents.send( - 'FILE_DOWNLOAD_ERROR', + DownloadEvent.onFileDownloadError, { fileName, err, @@ -75,7 +71,7 @@ export function handleDownloaderIPCs() { .on('end', function () { if (DownloadManager.instance.networkRequests[fileName]) { WindowManager?.instance.currentWindow?.webContents.send( - 'FILE_DOWNLOAD_COMPLETE', + DownloadEvent.onFileDownloadSuccess, { fileName, } @@ -83,7 +79,7 @@ export function handleDownloaderIPCs() { DownloadManager.instance.setRequest(fileName, undefined) } else { WindowManager?.instance.currentWindow?.webContents.send( - 'FILE_DOWNLOAD_ERROR', + DownloadEvent.onFileDownloadError, { fileName, err: 'Download cancelled', diff --git a/electron/handlers/extension.ts b/electron/handlers/extension.ts index 5c2c13ff4f..f89206eac3 100644 --- a/electron/handlers/extension.ts +++ b/electron/handlers/extension.ts @@ -11,6 +11,7 @@ import { getExtension } from './../extension/store' import { removeExtension } from './../extension/store' import Extension from './../extension/extension' import { getResourcePath, userSpacePath } from './../utils/path' +import { ExtensionRoute } from '@janhq/core' export function handleExtensionIPCs() { /**MARK: General handlers */ @@ -23,7 +24,7 @@ export function handleExtensionIPCs() { * @returns The result of the invoked function. */ ipcMain.handle( - 'extension:invokeExtensionFunc', + ExtensionRoute.invokeExtensionFunc, async (_event, modulePath, method, ...args) => { const module = require( /* webpackIgnore: true */ join(userSpacePath, 'extensions', modulePath) @@ -44,81 +45,59 @@ export function handleExtensionIPCs() { * @param _event - The IPC event object. * @returns An array of paths to the base extensions. */ - ipcMain.handle('extension:baseExtensions', async (_event) => { + ipcMain.handle(ExtensionRoute.baseExtensions, async (_event) => { const baseExtensionPath = join(getResourcePath(), 'pre-install') return readdirSync(baseExtensionPath) .filter((file) => extname(file) === '.tgz') .map((file) => join(baseExtensionPath, file)) }) - /** - * Returns the path to the user's extension directory. - * @param _event - The IPC event extension. - * @returns The path to the user's extension directory. - */ - ipcMain.handle('extension:extensionPath', async (_event) => { - return join(userSpacePath, 'extensions') - }) - /**MARK: Extension Manager handlers */ - ipcMain.handle('extension:install', async (e, extensions) => { + ipcMain.handle(ExtensionRoute.installExtension, async (e, extensions) => { // Install and activate all provided extensions const installed = await installExtensions(extensions) return JSON.parse(JSON.stringify(installed)) }) // Register IPC route to uninstall a extension - ipcMain.handle('extension:uninstall', async (e, extensions, reload) => { - // Uninstall all provided extensions - for (const ext of extensions) { - const extension = getExtension(ext) - await extension.uninstall() - if (extension.name) removeExtension(extension.name) - } - - // Reload all renderer pages if needed - reload && webContents.getAllWebContents().forEach((wc) => wc.reload()) - return true - }) + ipcMain.handle( + ExtensionRoute.uninstallExtension, + async (e, extensions, reload) => { + // Uninstall all provided extensions + for (const ext of extensions) { + const extension = getExtension(ext) + await extension.uninstall() + if (extension.name) removeExtension(extension.name) + } - // Register IPC route to update a extension - ipcMain.handle('extension:update', async (e, extensions, reload) => { - // Update all provided extensions - const updated: Extension[] = [] - for (const ext of extensions) { - const extension = getExtension(ext) - const res = await extension.update() - if (res) updated.push(extension) + // Reload all renderer pages if needed + reload && webContents.getAllWebContents().forEach((wc) => wc.reload()) + return true } + ) - // Reload all renderer pages if needed - if (updated.length && reload) - webContents.getAllWebContents().forEach((wc) => wc.reload()) - - return JSON.parse(JSON.stringify(updated)) - }) + // Register IPC route to update a extension + ipcMain.handle( + ExtensionRoute.updateExtension, + async (e, extensions, reload) => { + // Update all provided extensions + const updated: Extension[] = [] + for (const ext of extensions) { + const extension = getExtension(ext) + const res = await extension.update() + if (res) updated.push(extension) + } - // Register IPC route to check if updates are available for a extension - ipcMain.handle('extension:updatesAvailable', (e, names) => { - const extensions = names - ? names.map((name: string) => getExtension(name)) - : getAllExtensions() + // Reload all renderer pages if needed + if (updated.length && reload) + webContents.getAllWebContents().forEach((wc) => wc.reload()) - const updates: Record = {} - for (const extension of extensions) { - updates[extension.name] = extension.isUpdateAvailable() + return JSON.parse(JSON.stringify(updated)) } - return updates - }) + ) // Register IPC route to get the list of active extensions - ipcMain.handle('extension:getActiveExtensions', () => { + ipcMain.handle(ExtensionRoute.getActiveExtensions, () => { return JSON.parse(JSON.stringify(getActiveExtensions())) }) - - // Register IPC route to toggle the active state of a extension - ipcMain.handle('extension:toggleExtensionActive', (e, plg, active) => { - const extension = getExtension(plg) - return JSON.parse(JSON.stringify(extension.setActive(active))) - }) } diff --git a/electron/handlers/fs.ts b/electron/handlers/fs.ts index acc0ed2dab..614461ef39 100644 --- a/electron/handlers/fs.ts +++ b/electron/handlers/fs.ts @@ -4,6 +4,7 @@ import fse from 'fs-extra' import { join } from 'path' import readline from 'readline' import { userSpacePath } from './../utils/path' +import { FileSystemRoute } from '@janhq/core' /** * Handles file system operations. @@ -15,7 +16,7 @@ export function handleFsIPCs() { * @returns A promise that resolves with the path to the user data directory. */ ipcMain.handle( - 'getUserSpace', + FileSystemRoute.getUserSpace, (): Promise => Promise.resolve(userSpacePath) ) @@ -25,12 +26,15 @@ export function handleFsIPCs() { * @param path - The path to check. * @returns A promise that resolves with a boolean indicating whether the path is a directory. */ - ipcMain.handle('isDirectory', (_event, path: string): Promise => { - const fullPath = join(userSpacePath, path) - return Promise.resolve( - fs.existsSync(fullPath) && fs.lstatSync(fullPath).isDirectory() - ) - }) + ipcMain.handle( + FileSystemRoute.isDirectory, + (_event, path: string): Promise => { + const fullPath = join(userSpacePath, path) + return Promise.resolve( + fs.existsSync(fullPath) && fs.lstatSync(fullPath).isDirectory() + ) + } + ) /** * Reads a file from the user data directory. @@ -38,17 +42,20 @@ export function handleFsIPCs() { * @param path - The path of the file to read. * @returns A promise that resolves with the contents of the file. */ - ipcMain.handle('readFile', async (event, path: string): Promise => { - return new Promise((resolve, reject) => { - fs.readFile(join(userSpacePath, path), 'utf8', (err, data) => { - if (err) { - reject(err) - } else { - resolve(data) - } + ipcMain.handle( + FileSystemRoute.readFile, + async (event, path: string): Promise => { + return new Promise((resolve, reject) => { + fs.readFile(join(userSpacePath, path), 'utf8', (err, data) => { + if (err) { + reject(err) + } else { + resolve(data) + } + }) }) - }) - }) + } + ) /** * Checks whether a file exists in the user data directory. @@ -56,7 +63,7 @@ export function handleFsIPCs() { * @param path - The path of the file to check. * @returns A promise that resolves with a boolean indicating whether the file exists. */ - ipcMain.handle('exists', async (_event, path: string) => { + ipcMain.handle(FileSystemRoute.exists, async (_event, path: string) => { return new Promise((resolve, reject) => { const fullPath = join(userSpacePath, path) fs.existsSync(fullPath) ? resolve(true) : resolve(false) @@ -71,7 +78,7 @@ export function handleFsIPCs() { * @returns A promise that resolves when the file has been written. */ ipcMain.handle( - 'writeFile', + FileSystemRoute.writeFile, async (event, path: string, data: string): Promise => { try { await fs.writeFileSync(join(userSpacePath, path), data, 'utf8') @@ -87,13 +94,16 @@ export function handleFsIPCs() { * @param path - The path of the directory to create. * @returns A promise that resolves when the directory has been created. */ - ipcMain.handle('mkdir', async (event, path: string): Promise => { - try { - fs.mkdirSync(join(userSpacePath, path), { recursive: true }) - } catch (err) { - console.error(`mkdir ${path} result: ${err}`) + ipcMain.handle( + FileSystemRoute.mkdir, + async (event, path: string): Promise => { + try { + fs.mkdirSync(join(userSpacePath, path), { recursive: true }) + } catch (err) { + console.error(`mkdir ${path} result: ${err}`) + } } - }) + ) /** * Removes a directory in the user data directory. @@ -101,13 +111,16 @@ export function handleFsIPCs() { * @param path - The path of the directory to remove. * @returns A promise that resolves when the directory is removed successfully. */ - ipcMain.handle('rmdir', async (event, path: string): Promise => { - try { - await fs.rmSync(join(userSpacePath, path), { recursive: true }) - } catch (err) { - console.error(`rmdir ${path} result: ${err}`) + ipcMain.handle( + FileSystemRoute.rmdir, + async (event, path: string): Promise => { + try { + await fs.rmSync(join(userSpacePath, path), { recursive: true }) + } catch (err) { + console.error(`rmdir ${path} result: ${err}`) + } } - }) + ) /** * Lists the files in a directory in the user data directory. @@ -116,7 +129,7 @@ export function handleFsIPCs() { * @returns A promise that resolves with an array of file names. */ ipcMain.handle( - 'listFiles', + FileSystemRoute.listFiles, async (event, path: string): Promise => { return new Promise((resolve, reject) => { fs.readdir(join(userSpacePath, path), (err, files) => { @@ -136,7 +149,7 @@ export function handleFsIPCs() { * @param filePath - The path to the file to delete. * @returns A string indicating the result of the operation. */ - ipcMain.handle('deleteFile', async (_event, filePath) => { + ipcMain.handle(FileSystemRoute.deleteFile, async (_event, filePath) => { try { await fs.unlinkSync(join(userSpacePath, filePath)) } catch (err) { @@ -151,19 +164,25 @@ export function handleFsIPCs() { * @param data - The data to append to the file. * @returns A promise that resolves when the file has been written. */ - ipcMain.handle('appendFile', async (_event, path: string, data: string) => { - try { - await fs.appendFileSync(join(userSpacePath, path), data, 'utf8') - } catch (err) { - console.error(`appendFile ${path} result: ${err}`) + ipcMain.handle( + FileSystemRoute.appendFile, + async (_event, path: string, data: string) => { + try { + await fs.appendFileSync(join(userSpacePath, path), data, 'utf8') + } catch (err) { + console.error(`appendFile ${path} result: ${err}`) + } } - }) + ) - ipcMain.handle('copyFile', async (_event, src: string, dest: string) => { - console.debug(`Copying file from ${src} to ${dest}`) + ipcMain.handle( + FileSystemRoute.copyFile, + async (_event, src: string, dest: string) => { + console.debug(`Copying file from ${src} to ${dest}`) - return fse.copySync(src, dest, { overwrite: false }) - }) + return fse.copySync(src, dest, { overwrite: false }) + } + ) /** * Reads a file line by line. @@ -171,25 +190,28 @@ export function handleFsIPCs() { * @param path - The path of the file to read. * @returns A promise that resolves with the contents of the file. */ - ipcMain.handle('readLineByLine', async (_event, path: string) => { - const fullPath = join(userSpacePath, path) + ipcMain.handle( + FileSystemRoute.readLineByLine, + async (_event, path: string) => { + const fullPath = join(userSpacePath, path) - return new Promise((res, rej) => { - try { - const readInterface = readline.createInterface({ - input: fs.createReadStream(fullPath), - }) - const lines: any = [] - readInterface - .on('line', function (line) { - lines.push(line) - }) - .on('close', function () { - res(lines) + return new Promise((res, rej) => { + try { + const readInterface = readline.createInterface({ + input: fs.createReadStream(fullPath), }) - } catch (err) { - rej(err) - } - }) - }) + const lines: any = [] + readInterface + .on('line', function (line) { + lines.push(line) + }) + .on('close', function () { + res(lines) + }) + } catch (err) { + rej(err) + } + }) + } + ) } diff --git a/electron/handlers/theme.ts b/electron/handlers/theme.ts deleted file mode 100644 index 0038002a86..0000000000 --- a/electron/handlers/theme.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { ipcMain, nativeTheme } from "electron"; - -export function handleThemesIPCs() { - /** - * Handles the "setNativeThemeLight" IPC message by setting the native theme source to "light". - * This will change the appearance of the app to the light theme. - */ - ipcMain.handle("setNativeThemeLight", () => { - nativeTheme.themeSource = "light"; - }); - - /** - * Handles the "setNativeThemeDark" IPC message by setting the native theme source to "dark". - * This will change the appearance of the app to the dark theme. - */ - ipcMain.handle("setNativeThemeDark", () => { - nativeTheme.themeSource = "dark"; - }); - - /** - * Handles the "setNativeThemeSystem" IPC message by setting the native theme source to "system". - * This will change the appearance of the app to match the system's current theme. - */ - ipcMain.handle("setNativeThemeSystem", () => { - nativeTheme.themeSource = "system"; - }); -} diff --git a/electron/handlers/update.ts b/electron/handlers/update.ts index 667acca956..cbb34c22b5 100644 --- a/electron/handlers/update.ts +++ b/electron/handlers/update.ts @@ -1,57 +1,58 @@ -import { app, dialog } from "electron"; -import { WindowManager } from "./../managers/window"; -import { autoUpdater } from "electron-updater"; +import { app, dialog } from 'electron' +import { WindowManager } from './../managers/window' +import { autoUpdater } from 'electron-updater' +import { AppEvent } from '@janhq/core' export function handleAppUpdates() { /* Should not check for update during development */ if (!app.isPackaged) { - return; + return } /* New Update Available */ - autoUpdater.on("update-available", async (_info: any) => { + autoUpdater.on('update-available', async (_info: any) => { const action = await dialog.showMessageBox({ message: `Update available. Do you want to download the latest update?`, - buttons: ["Download", "Later"], - }); - if (action.response === 0) await autoUpdater.downloadUpdate(); - }); + buttons: ['Download', 'Later'], + }) + if (action.response === 0) await autoUpdater.downloadUpdate() + }) /* App Update Completion Message */ - autoUpdater.on("update-downloaded", async (_info: any) => { + autoUpdater.on('update-downloaded', async (_info: any) => { WindowManager.instance.currentWindow?.webContents.send( - "APP_UPDATE_COMPLETE", + AppEvent.onAppUpdateDownloadSuccess, {} - ); + ) const action = await dialog.showMessageBox({ message: `Update downloaded. Please restart the application to apply the updates.`, - buttons: ["Restart", "Later"], - }); + buttons: ['Restart', 'Later'], + }) if (action.response === 0) { - autoUpdater.quitAndInstall(); + autoUpdater.quitAndInstall() } - }); + }) /* App Update Error */ - autoUpdater.on("error", (info: any) => { + autoUpdater.on('error', (info: any) => { WindowManager.instance.currentWindow?.webContents.send( - "APP_UPDATE_ERROR", + AppEvent.onAppUpdateDownloadError, {} - ); - }); + ) + }) /* App Update Progress */ - autoUpdater.on("download-progress", (progress: any) => { - console.debug("app update progress: ", progress.percent); + autoUpdater.on('download-progress', (progress: any) => { + console.debug('app update progress: ', progress.percent) WindowManager.instance.currentWindow?.webContents.send( - "APP_UPDATE_PROGRESS", + AppEvent.onAppUpdateDownloadUpdate, { percent: progress.percent, } - ); - }); - autoUpdater.autoDownload = false; - autoUpdater.autoInstallOnAppQuit = true; - if (process.env.CI !== "e2e") { - autoUpdater.checkForUpdates(); + ) + }) + autoUpdater.autoDownload = false + autoUpdater.autoInstallOnAppQuit = true + if (process.env.CI !== 'e2e') { + autoUpdater.checkForUpdates() } } diff --git a/electron/invokers/app.ts b/electron/invokers/app.ts deleted file mode 100644 index a5bc028c2f..0000000000 --- a/electron/invokers/app.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { shell } from 'electron' - -const { ipcRenderer } = require('electron') - -export function appInvokers() { - const interfaces = { - /** - * Sets the native theme to light. - */ - setNativeThemeLight: () => ipcRenderer.invoke('setNativeThemeLight'), - - /** - * Sets the native theme to dark. - */ - setNativeThemeDark: () => ipcRenderer.invoke('setNativeThemeDark'), - - /** - * Sets the native theme to system default. - */ - setNativeThemeSystem: () => ipcRenderer.invoke('setNativeThemeSystem'), - - /** - * Retrieves the application data path. - * @returns {Promise} A promise that resolves to the application data path. - */ - appDataPath: () => ipcRenderer.invoke('appDataPath'), - - /** - * Retrieves the application version. - * @returns {Promise} A promise that resolves to the application version. - */ - appVersion: () => ipcRenderer.invoke('appVersion'), - - /** - * Opens an external URL. - * @param {string} url - The URL to open. - * @returns {Promise} A promise that resolves when the URL has been opened. - */ - openExternalUrl: (url: string) => - ipcRenderer.invoke('openExternalUrl', url), - - /** - * Relaunches the application. - * @returns {Promise} A promise that resolves when the application has been relaunched. - */ - relaunch: () => ipcRenderer.invoke('relaunch'), - - /** - * Opens the application directory. - * @returns {Promise} A promise that resolves when the application directory has been opened. - */ - openAppDirectory: () => ipcRenderer.invoke('openAppDirectory'), - - /** - * Opens the file explorer at a specific path. - * @param {string} path - The path to open in the file explorer. - */ - openFileExplorer: (path: string) => shell.openPath(path), - } - - return interfaces -} diff --git a/electron/invokers/download.ts b/electron/invokers/download.ts deleted file mode 100644 index d99def3fd0..0000000000 --- a/electron/invokers/download.ts +++ /dev/null @@ -1,77 +0,0 @@ -const { ipcRenderer } = require('electron') - -export function downloadInvokers() { - const interfaces = { - /** - * Opens the file explorer at a specific path. - * @param {string} path - The path to open in the file explorer. - */ - downloadFile: (url: string, path: string) => - ipcRenderer.invoke('downloadFile', url, path), - - /** - * Pauses the download of a file. - * @param {string} fileName - The name of the file whose download should be paused. - */ - pauseDownload: (fileName: string) => - ipcRenderer.invoke('pauseDownload', fileName), - - /** - * Pauses the download of a file. - * @param {string} fileName - The name of the file whose download should be paused. - */ - resumeDownload: (fileName: string) => - ipcRenderer.invoke('resumeDownload', fileName), - - /** - * Pauses the download of a file. - * @param {string} fileName - The name of the file whose download should be paused. - */ - abortDownload: (fileName: string) => - ipcRenderer.invoke('abortDownload', fileName), - - /** - * Pauses the download of a file. - * @param {string} fileName - The name of the file whose download should be paused. - */ - onFileDownloadUpdate: (callback: any) => - ipcRenderer.on('FILE_DOWNLOAD_UPDATE', callback), - - /** - * Listens for errors on file downloads. - * @param {Function} callback - The function to call when there is an error. - */ - onFileDownloadError: (callback: any) => - ipcRenderer.on('FILE_DOWNLOAD_ERROR', callback), - - /** - * Listens for the successful completion of file downloads. - * @param {Function} callback - The function to call when a download is complete. - */ - onFileDownloadSuccess: (callback: any) => - ipcRenderer.on('FILE_DOWNLOAD_COMPLETE', callback), - - /** - * Listens for updates on app update downloads. - * @param {Function} callback - The function to call when there is an update. - */ - onAppUpdateDownloadUpdate: (callback: any) => - ipcRenderer.on('APP_UPDATE_PROGRESS', callback), - - /** - * Listens for errors on app update downloads. - * @param {Function} callback - The function to call when there is an error. - */ - onAppUpdateDownloadError: (callback: any) => - ipcRenderer.on('APP_UPDATE_ERROR', callback), - - /** - * Listens for the successful completion of app update downloads. - * @param {Function} callback - The function to call when an update download is complete. - */ - onAppUpdateDownloadSuccess: (callback: any) => - ipcRenderer.on('APP_UPDATE_COMPLETE', callback), - } - - return interfaces -} diff --git a/electron/invokers/extension.ts b/electron/invokers/extension.ts deleted file mode 100644 index c575f8add3..0000000000 --- a/electron/invokers/extension.ts +++ /dev/null @@ -1,78 +0,0 @@ -const { ipcRenderer } = require('electron') - -export function extensionInvokers() { - const interfaces = { - /** - * Installs the given extensions. - * @param {any[]} extensions - The extensions to install. - */ - install(extensions: any[]) { - return ipcRenderer.invoke('extension:install', extensions) - }, - /** - * Uninstalls the given extensions. - * @param {any[]} extensions - The extensions to uninstall. - * @param {boolean} reload - Whether to reload after uninstalling. - */ - uninstall(extensions: any[], reload: boolean) { - return ipcRenderer.invoke('extension:uninstall', extensions, reload) - }, - /** - * Retrieves the active extensions. - */ - getActive() { - return ipcRenderer.invoke('extension:getActiveExtensions') - }, - /** - * Updates the given extensions. - * @param {any[]} extensions - The extensions to update. - * @param {boolean} reload - Whether to reload after updating. - */ - update(extensions: any[], reload: boolean) { - return ipcRenderer.invoke('extension:update', extensions, reload) - }, - /** - * Checks if updates are available for the given extension. - * @param {any} extension - The extension to check for updates. - */ - updatesAvailable(extension: any) { - return ipcRenderer.invoke('extension:updatesAvailable', extension) - }, - /** - * Toggles the active state of the given extension. - * @param {any} extension - The extension to toggle. - * @param {boolean} active - The new active state. - */ - toggleActive(extension: any, active: boolean) { - return ipcRenderer.invoke( - 'extension:toggleExtensionActive', - extension, - active - ) - }, - - /** - * Invokes a function of the given extension. - * @param {any} extension - The extension whose function should be invoked. - * @param {any} method - The function to invoke. - * @param {any[]} args - The arguments to pass to the function. - */ - invokeExtensionFunc: (extension: any, method: any, ...args: any[]) => - ipcRenderer.invoke( - 'extension:invokeExtensionFunc', - extension, - method, - ...args - ), - /** - * Retrieves the base extensions. - */ - baseExtensions: () => ipcRenderer.invoke('extension:baseExtensions'), - /** - * Retrieves the extension path. - */ - extensionPath: () => ipcRenderer.invoke('extension:extensionPath'), - } - - return interfaces -} diff --git a/electron/invokers/fs.ts b/electron/invokers/fs.ts deleted file mode 100644 index e1aa67cca3..0000000000 --- a/electron/invokers/fs.ts +++ /dev/null @@ -1,93 +0,0 @@ -const { ipcRenderer } = require('electron') - -export function fsInvokers() { - const interfaces = { - /** - * Deletes a file at the specified path. - * @param {string} filePath - The path of the file to delete. - */ - deleteFile: (filePath: string) => - ipcRenderer.invoke('deleteFile', filePath), - - /** - * Checks if the path points to a directory. - * @param {string} filePath - The path to check. - */ - isDirectory: (filePath: string) => - ipcRenderer.invoke('isDirectory', filePath), - - /** - * Retrieves the user's space. - */ - getUserSpace: () => ipcRenderer.invoke('getUserSpace'), - - /** - * Reads a file at the specified path. - * @param {string} path - The path of the file to read. - */ - readFile: (path: string) => ipcRenderer.invoke('readFile', path), - - /** - * Reads a file at the specified path. - * @param {string} path - The path of the file to read. - */ - exists: (path: string) => ipcRenderer.invoke('exists', path), - - /** - * Writes data to a file at the specified path. - * @param {string} path - The path of the file to write to. - * @param {string} data - The data to write. - */ - writeFile: (path: string, data: string) => - ipcRenderer.invoke('writeFile', path, data), - - /** - * Lists the files in a directory at the specified path. - * @param {string} path - The path of the directory to list files from. - */ - listFiles: (path: string) => ipcRenderer.invoke('listFiles', path), - - /** - * Appends data to a file at the specified path. - * @param {string} path - The path of the file to append to. - * @param {string} data - The data to append. - */ - appendFile: (path: string, data: string) => - ipcRenderer.invoke('appendFile', path, data), - - /** - * Reads a file line by line at the specified path. - * @param {string} path - The path of the file to read. - */ - readLineByLine: (path: string) => - ipcRenderer.invoke('readLineByLine', path), - - /** - * Creates a directory at the specified path. - * @param {string} path - The path where the directory should be created. - */ - mkdir: (path: string) => ipcRenderer.invoke('mkdir', path), - - /** - * Removes a directory at the specified path. - * @param {string} path - The path of the directory to remove. - */ - rmdir: (path: string) => ipcRenderer.invoke('rmdir', path), - - /** - * Copies a file from the source path to the destination path. - * @param {string} src - The source path of the file to copy. - * @param {string} dest - The destination path where the file should be copied. - */ - copyFile: (src: string, dest: string) => ipcRenderer.invoke('copyFile', src, dest), - - /** - * Retrieves the resource path. - * @returns {Promise} A promise that resolves to the resource path. - */ - getResourcePath: () => ipcRenderer.invoke('getResourcePath'), - - } - - return interfaces -} diff --git a/electron/main.ts b/electron/main.ts index 1898368661..542875312b 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -1,7 +1,7 @@ import { app, BrowserWindow } from 'electron' import { join } from 'path' import { setupMenu } from './utils/menu' -import { createUserSpace, getResourcePath } from './utils/path' +import { createUserSpace } from './utils/path' /** * Managers @@ -14,7 +14,6 @@ import { ExtensionManager } from './managers/extension' * IPC Handlers **/ import { handleDownloaderIPCs } from './handlers/download' -import { handleThemesIPCs } from './handlers/theme' import { handleExtensionIPCs } from './handlers/extension' import { handleAppIPCs } from './handlers/app' import { handleAppUpdates } from './handlers/update' @@ -79,7 +78,6 @@ function createMainWindow() { function handleIPCs() { handleFsIPCs() handleDownloaderIPCs() - handleThemesIPCs() handleExtensionIPCs() handleAppIPCs() } diff --git a/electron/package.json b/electron/package.json index 627f5ad541..623071e38d 100644 --- a/electron/package.json +++ b/electron/package.json @@ -67,6 +67,7 @@ "build:publish:linux": "tsc -p . && electron-builder -p onTagOrDraft -l deb" }, "dependencies": { + "@janhq/core": "link:./core", "@npmcli/arborist": "^7.1.0", "@types/request": "^2.48.12", "@uiball/loaders": "^1.3.0", diff --git a/electron/preload.ts b/electron/preload.ts index a335f6ce2a..c4a646c41e 100644 --- a/electron/preload.ts +++ b/electron/preload.ts @@ -3,19 +3,27 @@ * @module preload */ -// TODO: Refactor this file for less dependencies and more modularity -// TODO: Most of the APIs should be done using RestAPIs from extensions +import { APIEvents, APIRoutes } from '@janhq/core' +import { contextBridge, ipcRenderer } from 'electron' -import { fsInvokers } from './invokers/fs' -import { appInvokers } from './invokers/app' -import { downloadInvokers } from './invokers/download' -import { extensionInvokers } from './invokers/extension' +const interfaces: { [key: string]: (...args: any[]) => any } = {} -const { contextBridge } = require('electron') +// Loop over each route in APIRoutes +APIRoutes.forEach((method) => { + // For each method, create a function on the interfaces object + // This function invokes the method on the ipcRenderer with any provided arguments + interfaces[method] = (...args: any[]) => ipcRenderer.invoke(method, ...args) +}) +// Loop over each method in APIEvents +APIEvents.forEach((method) => { + // For each method, create a function on the interfaces object + // This function sets up an event listener on the ipcRenderer for the method + // The handler for the event is provided as an argument to the function + interfaces[method] = (handler: any) => ipcRenderer.on(method, handler) +}) +// Expose the 'interfaces' object in the main world under the name 'electronAPI' +// This allows the renderer process to access these methods directly contextBridge.exposeInMainWorld('electronAPI', { - ...extensionInvokers(), - ...downloadInvokers(), - ...fsInvokers(), - ...appInvokers(), + ...interfaces, }) diff --git a/web/containers/Providers/EventHandler.tsx b/web/containers/Providers/EventHandler.tsx index 6d0f5ff26f..92fb3150a2 100644 --- a/web/containers/Providers/EventHandler.tsx +++ b/web/containers/Providers/EventHandler.tsx @@ -118,7 +118,7 @@ export default function EventHandler({ children }: { children: ReactNode }) { } useEffect(() => { - if (window.core.events) { + if (window.core?.events) { events.on(EventName.OnMessageResponse, handleNewMessageResponse) events.on(EventName.OnMessageUpdate, handleMessageResponseUpdate) events.on(EventName.OnModelReady, handleModelReady) diff --git a/web/containers/Providers/index.tsx b/web/containers/Providers/index.tsx index 95f61bd27c..2aabb96bf5 100644 --- a/web/containers/Providers/index.tsx +++ b/web/containers/Providers/index.tsx @@ -53,7 +53,7 @@ const Providers = (props: PropsWithChildren) => { useEffect(() => { if (setupCore) { // Electron - if (window && window.core.api) { + if (window && window.core?.api) { setupExtensions() } else { // Host diff --git a/web/extension/ExtensionManager.ts b/web/extension/ExtensionManager.ts index dda88595c4..4a53cb4915 100644 --- a/web/extension/ExtensionManager.ts +++ b/web/extension/ExtensionManager.ts @@ -58,7 +58,7 @@ export class ExtensionManager { * @returns An array of extensions. */ async getActive(): Promise { - const res = await window.core.api?.getActive() + const res = await window.core?.api?.getActiveExtensions() if (!res || !Array.isArray(res)) return [] const extensions: Extension[] = res.map( @@ -119,7 +119,7 @@ export class ExtensionManager { if (typeof window === 'undefined') { return } - const res = await window.core.api?.install(extensions) + const res = await window.core?.api?.installExtension(extensions) if (res.cancelled) return false return res.map(async (ext: any) => { const extension = new Extension(ext.name, ext.url, ext.active) @@ -138,7 +138,7 @@ export class ExtensionManager { if (typeof window === 'undefined') { return } - return window.core.api?.uninstall(extensions, reload) + return window.core?.api?.uninstallExtension(extensions, reload) } } diff --git a/web/hooks/useCreateNewThread.ts b/web/hooks/useCreateNewThread.ts index 2ba9adb3fb..e2f2aa35dc 100644 --- a/web/hooks/useCreateNewThread.ts +++ b/web/hooks/useCreateNewThread.ts @@ -15,7 +15,6 @@ import { threadsAtom, setActiveThreadIdAtom, threadStatesAtom, - activeThreadAtom, updateThreadAtom, } from '@/helpers/atoms/Conversation.atom' @@ -67,7 +66,7 @@ export const useCreateNewThread = () => { top_p: 0, stream: false, }, - engine: undefined + engine: undefined, }, instructions: assistant.instructions, } diff --git a/web/hooks/useGetAppVersion.ts b/web/hooks/useGetAppVersion.ts index 60f550fe7d..ef82add457 100644 --- a/web/hooks/useGetAppVersion.ts +++ b/web/hooks/useGetAppVersion.ts @@ -8,7 +8,7 @@ export function useGetAppVersion() { }, []) const getAppVersion = () => { - window.core.api?.appVersion().then((version: string | undefined) => { + window.core?.api?.appVersion().then((version: string | undefined) => { setVersion(version ?? '') }) } diff --git a/web/screens/Settings/CoreExtensions/ExtensionsCatalog/index.tsx b/web/screens/Settings/CoreExtensions/ExtensionsCatalog/index.tsx index 3394c24b5a..b7ac59aa6a 100644 --- a/web/screens/Settings/CoreExtensions/ExtensionsCatalog/index.tsx +++ b/web/screens/Settings/CoreExtensions/ExtensionsCatalog/index.tsx @@ -67,7 +67,7 @@ const ExtensionCatalog = () => { // Send the filename of the to be installed extension // to the main process for installation const installed = await extensionManager.install([extensionFile]) - if (installed) window.core.api?.relaunch() + if (installed) window.core?.api?.relaunch() } /** @@ -80,7 +80,7 @@ const ExtensionCatalog = () => { // Send the filename of the to be uninstalled extension // to the main process for removal const res = await extensionManager.uninstall([name]) - if (res) window.core.api?.relaunch() + if (res) window.core?.api?.relaunch() } /** diff --git a/web/types/index.d.ts b/web/types/index.d.ts index 50b5bbda29..ddb608a7aa 100644 --- a/web/types/index.d.ts +++ b/web/types/index.d.ts @@ -1,11 +1,17 @@ +import { APIFunctions } from '@janhq/core' + /* eslint-disable @typescript-eslint/no-explicit-any */ export {} declare global { declare const PLUGIN_CATALOG: string declare const VERSION: string + interface Core { + api: APIFunctions + events: EventEmitter + } interface Window { - core?: any | undefined + core?: Core | undefined electronAPI?: any | undefined } }