refactor: deprecate invokers - auto proxying apis - strict types (#924)

* refactor: deprecate invokers

* refactor: define routes and auto proxying routes

* refactor: declare types for APIs, avoid making dynamic calls to any functions from the web

* chore: deprecate route handling from preload script

* refactor: deprecate unused apis
This commit is contained in:
Louis 2023-12-11 13:10:53 +07:00 committed by GitHub
parent df977143ec
commit c4d8defe94
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 341 additions and 552 deletions

100
core/src/api/index.ts Normal file
View File

@ -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)]

View File

@ -30,13 +30,6 @@ const downloadFile: (url: string, fileName: string) => Promise<any> = (url, file
const abortDownload: (fileName: string) => Promise<any> = (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<any> = () => global.core.api?.appDataPath()
/**
* Gets the user space path.
* @returns {Promise<any>} A Promise that resolves with the user space path.
@ -70,7 +63,6 @@ export {
executeOnMain,
downloadFile,
abortDownload,
appDataPath,
getUserSpace,
openFileExplorer,
getResourcePath,

View File

@ -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'

View File

@ -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()

View File

@ -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',

View File

@ -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)
}
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)
}
// Reload all renderer pages if needed
reload && webContents.getAllWebContents().forEach((wc) => wc.reload())
return true
})
// Reload all renderer pages if needed
reload && webContents.getAllWebContents().forEach((wc) => wc.reload())
return true
}
)
// 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)
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)
}
// Reload all renderer pages if needed
if (updated.length && reload)
webContents.getAllWebContents().forEach((wc) => wc.reload())
return JSON.parse(JSON.stringify(updated))
}
// 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 check if updates are available for a extension
ipcMain.handle('extension:updatesAvailable', (e, names) => {
const extensions = names
? names.map((name: string) => getExtension(name))
: getAllExtensions()
const updates: Record<string, Extension> = {}
for (const extension of extensions) {
updates[extension.name] = extension.isUpdateAvailable()
}
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)))
})
}

View File

@ -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<string> => 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<boolean> => {
const fullPath = join(userSpacePath, path)
return Promise.resolve(
fs.existsSync(fullPath) && fs.lstatSync(fullPath).isDirectory()
)
})
ipcMain.handle(
FileSystemRoute.isDirectory,
(_event, path: string): Promise<boolean> => {
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<string> => {
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<string> => {
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<void> => {
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<void> => {
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<void> => {
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<void> => {
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<void> => {
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<string[]> => {
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)
return new Promise((res, rej) => {
try {
const readInterface = readline.createInterface({
input: fs.createReadStream(fullPath),
})
.on('close', function () {
res(lines)
})
} catch (err) {
rej(err)
}
})
})
const lines: any = []
readInterface
.on('line', function (line) {
lines.push(line)
})
.on('close', function () {
res(lines)
})
} catch (err) {
rej(err)
}
})
}
)
}

View File

@ -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";
});
}

View File

@ -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()
}
}

View File

@ -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<string>} A promise that resolves to the application data path.
*/
appDataPath: () => ipcRenderer.invoke('appDataPath'),
/**
* Retrieves the application version.
* @returns {Promise<string>} 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<void>} A promise that resolves when the URL has been opened.
*/
openExternalUrl: (url: string) =>
ipcRenderer.invoke('openExternalUrl', url),
/**
* Relaunches the application.
* @returns {Promise<void>} A promise that resolves when the application has been relaunched.
*/
relaunch: () => ipcRenderer.invoke('relaunch'),
/**
* Opens the application directory.
* @returns {Promise<void>} 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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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<string>} A promise that resolves to the resource path.
*/
getResourcePath: () => ipcRenderer.invoke('getResourcePath'),
}
return interfaces
}

View File

@ -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()
}

View File

@ -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",

View File

@ -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')
contextBridge.exposeInMainWorld('electronAPI', {
...extensionInvokers(),
...downloadInvokers(),
...fsInvokers(),
...appInvokers(),
// 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', {
...interfaces,
})

View File

@ -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)

View File

@ -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

View File

@ -58,7 +58,7 @@ export class ExtensionManager {
* @returns An array of extensions.
*/
async getActive(): Promise<Extension[]> {
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)
}
}

View File

@ -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,
}

View File

@ -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 ?? '')
})
}

View File

@ -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()
}
/**

View File

@ -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
}
}