refactor: reduce IPC & API handlers - shared node logics (#2011)
This commit is contained in:
parent
8a1e3ba43d
commit
5f95841fab
@ -54,7 +54,8 @@ export default [
|
|||||||
'url',
|
'url',
|
||||||
'http',
|
'http',
|
||||||
'os',
|
'os',
|
||||||
'util'
|
'util',
|
||||||
|
'child_process'
|
||||||
],
|
],
|
||||||
watch: {
|
watch: {
|
||||||
include: 'src/node/**',
|
include: 'src/node/**',
|
||||||
|
|||||||
@ -1,15 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* Native Route APIs
|
||||||
|
* @description Enum of all the routes exposed by the app
|
||||||
|
*/
|
||||||
|
export enum NativeRoute {
|
||||||
|
openExternalUrl = 'openExternalUrl',
|
||||||
|
openAppDirectory = 'openAppDirectory',
|
||||||
|
openFileExplore = 'openFileExplorer',
|
||||||
|
selectDirectory = 'selectDirectory',
|
||||||
|
relaunch = 'relaunch',
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* App Route APIs
|
* App Route APIs
|
||||||
* @description Enum of all the routes exposed by the app
|
* @description Enum of all the routes exposed by the app
|
||||||
*/
|
*/
|
||||||
export enum AppRoute {
|
export enum AppRoute {
|
||||||
openExternalUrl = 'openExternalUrl',
|
|
||||||
openAppDirectory = 'openAppDirectory',
|
|
||||||
openFileExplore = 'openFileExplorer',
|
|
||||||
selectDirectory = 'selectDirectory',
|
|
||||||
getAppConfigurations = 'getAppConfigurations',
|
getAppConfigurations = 'getAppConfigurations',
|
||||||
updateAppConfiguration = 'updateAppConfiguration',
|
updateAppConfiguration = 'updateAppConfiguration',
|
||||||
relaunch = 'relaunch',
|
|
||||||
joinPath = 'joinPath',
|
joinPath = 'joinPath',
|
||||||
isSubdirectory = 'isSubdirectory',
|
isSubdirectory = 'isSubdirectory',
|
||||||
baseName = 'baseName',
|
baseName = 'baseName',
|
||||||
@ -69,6 +76,10 @@ export enum FileManagerRoute {
|
|||||||
|
|
||||||
export type ApiFunction = (...args: any[]) => any
|
export type ApiFunction = (...args: any[]) => any
|
||||||
|
|
||||||
|
export type NativeRouteFunctions = {
|
||||||
|
[K in NativeRoute]: ApiFunction
|
||||||
|
}
|
||||||
|
|
||||||
export type AppRouteFunctions = {
|
export type AppRouteFunctions = {
|
||||||
[K in AppRoute]: ApiFunction
|
[K in AppRoute]: ApiFunction
|
||||||
}
|
}
|
||||||
@ -97,7 +108,8 @@ export type FileManagerRouteFunctions = {
|
|||||||
[K in FileManagerRoute]: ApiFunction
|
[K in FileManagerRoute]: ApiFunction
|
||||||
}
|
}
|
||||||
|
|
||||||
export type APIFunctions = AppRouteFunctions &
|
export type APIFunctions = NativeRouteFunctions &
|
||||||
|
AppRouteFunctions &
|
||||||
AppEventFunctions &
|
AppEventFunctions &
|
||||||
DownloadRouteFunctions &
|
DownloadRouteFunctions &
|
||||||
DownloadEventFunctions &
|
DownloadEventFunctions &
|
||||||
@ -105,11 +117,16 @@ export type APIFunctions = AppRouteFunctions &
|
|||||||
FileSystemRouteFunctions &
|
FileSystemRouteFunctions &
|
||||||
FileManagerRoute
|
FileManagerRoute
|
||||||
|
|
||||||
export const APIRoutes = [
|
export const CoreRoutes = [
|
||||||
...Object.values(AppRoute),
|
...Object.values(AppRoute),
|
||||||
...Object.values(DownloadRoute),
|
...Object.values(DownloadRoute),
|
||||||
...Object.values(ExtensionRoute),
|
...Object.values(ExtensionRoute),
|
||||||
...Object.values(FileSystemRoute),
|
...Object.values(FileSystemRoute),
|
||||||
...Object.values(FileManagerRoute),
|
...Object.values(FileManagerRoute),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
export const APIRoutes = [
|
||||||
|
...CoreRoutes,
|
||||||
|
...Object.values(NativeRoute),
|
||||||
|
]
|
||||||
export const APIEvents = [...Object.values(AppEvent), ...Object.values(DownloadEvent)]
|
export const APIEvents = [...Object.values(AppEvent), ...Object.values(DownloadEvent)]
|
||||||
|
|||||||
@ -38,3 +38,10 @@ export * from './extension'
|
|||||||
* @module
|
* @module
|
||||||
*/
|
*/
|
||||||
export * from './extensions/index'
|
export * from './extensions/index'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Declare global object
|
||||||
|
*/
|
||||||
|
declare global {
|
||||||
|
var core: any | undefined
|
||||||
|
}
|
||||||
|
|||||||
37
core/src/node/api/common/adapter.ts
Normal file
37
core/src/node/api/common/adapter.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { AppRoute, DownloadRoute, ExtensionRoute, FileManagerRoute, FileSystemRoute } from '../../../api'
|
||||||
|
import { Downloader } from '../processors/download'
|
||||||
|
import { FileSystem } from '../processors/fs'
|
||||||
|
import { Extension } from '../processors/extension'
|
||||||
|
import { FSExt } from '../processors/fsExt'
|
||||||
|
import { App } from '../processors/app'
|
||||||
|
|
||||||
|
export class RequestAdapter {
|
||||||
|
downloader: Downloader
|
||||||
|
fileSystem: FileSystem
|
||||||
|
extension: Extension
|
||||||
|
fsExt: FSExt
|
||||||
|
app: App
|
||||||
|
|
||||||
|
constructor(observer?: Function) {
|
||||||
|
this.downloader = new Downloader(observer)
|
||||||
|
this.fileSystem = new FileSystem()
|
||||||
|
this.extension = new Extension()
|
||||||
|
this.fsExt = new FSExt()
|
||||||
|
this.app = new App()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Clearer Factory pattern here
|
||||||
|
process(route: string, ...args: any) {
|
||||||
|
if (route in DownloadRoute) {
|
||||||
|
return this.downloader.process(route, ...args)
|
||||||
|
} else if (route in FileSystemRoute) {
|
||||||
|
return this.fileSystem.process(route, ...args)
|
||||||
|
} else if (route in ExtensionRoute) {
|
||||||
|
return this.extension.process(route, ...args)
|
||||||
|
} else if (route in FileManagerRoute) {
|
||||||
|
return this.fsExt.process(route, ...args)
|
||||||
|
} else if (route in AppRoute) {
|
||||||
|
return this.app.process(route, ...args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
23
core/src/node/api/common/handler.ts
Normal file
23
core/src/node/api/common/handler.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { CoreRoutes } from '../../../api'
|
||||||
|
import { RequestAdapter } from './adapter'
|
||||||
|
|
||||||
|
export type Handler = (route: string, args: any) => any
|
||||||
|
|
||||||
|
export class RequestHandler {
|
||||||
|
handler: Handler
|
||||||
|
adataper: RequestAdapter
|
||||||
|
|
||||||
|
constructor(handler: Handler, observer?: Function) {
|
||||||
|
this.handler = handler
|
||||||
|
this.adataper = new RequestAdapter(observer)
|
||||||
|
}
|
||||||
|
|
||||||
|
handle() {
|
||||||
|
CoreRoutes.map((route) => {
|
||||||
|
this.handler(route, async (...args: any[]) => {
|
||||||
|
const values = await this.adataper.process(route, ...args)
|
||||||
|
return values
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,2 +1,3 @@
|
|||||||
export * from './HttpServer'
|
export * from './HttpServer'
|
||||||
export * from './routes'
|
export * from './restful/v1'
|
||||||
|
export * from './common/handler'
|
||||||
|
|||||||
3
core/src/node/api/processors/Processor.ts
Normal file
3
core/src/node/api/processors/Processor.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export abstract class Processor {
|
||||||
|
abstract process(key: string, ...args: any[]): any
|
||||||
|
}
|
||||||
97
core/src/node/api/processors/app.ts
Normal file
97
core/src/node/api/processors/app.ts
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
import { basename, isAbsolute, join, relative } from 'path'
|
||||||
|
|
||||||
|
import { AppRoute } from '../../../api'
|
||||||
|
import { Processor } from './Processor'
|
||||||
|
import { getAppConfigurations as appConfiguration, updateAppConfiguration } from '../../helper'
|
||||||
|
import { log as writeLog, logServer as writeServerLog } from '../../helper/log'
|
||||||
|
import { appResourcePath } from '../../helper/path'
|
||||||
|
|
||||||
|
export class App implements Processor {
|
||||||
|
observer?: Function
|
||||||
|
|
||||||
|
constructor(observer?: Function) {
|
||||||
|
this.observer = observer
|
||||||
|
}
|
||||||
|
|
||||||
|
process(key: string, ...args: any[]): any {
|
||||||
|
const instance = this as any
|
||||||
|
const func = instance[key]
|
||||||
|
return func(...args)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Joins multiple paths together, respect to the current OS.
|
||||||
|
*/
|
||||||
|
joinPath(args: any[]) {
|
||||||
|
return join(...args)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the given path is a subdirectory of the given directory.
|
||||||
|
*
|
||||||
|
* @param _event - The IPC event object.
|
||||||
|
* @param from - The path to check.
|
||||||
|
* @param to - The directory to check against.
|
||||||
|
*
|
||||||
|
* @returns {Promise<boolean>} - A promise that resolves with the result.
|
||||||
|
*/
|
||||||
|
isSubdirectory(from: any, to: any) {
|
||||||
|
const rel = relative(from, to)
|
||||||
|
const isSubdir = rel && !rel.startsWith('..') && !isAbsolute(rel)
|
||||||
|
|
||||||
|
if (isSubdir === '') return false
|
||||||
|
else return isSubdir
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve basename from given path, respect to the current OS.
|
||||||
|
*/
|
||||||
|
baseName(args: any) {
|
||||||
|
return basename(args)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log message to log file.
|
||||||
|
*/
|
||||||
|
log(args: any) {
|
||||||
|
writeLog(args)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log message to log file.
|
||||||
|
*/
|
||||||
|
logServer(args: any) {
|
||||||
|
writeServerLog(args)
|
||||||
|
}
|
||||||
|
|
||||||
|
getAppConfigurations() {
|
||||||
|
return appConfiguration()
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateAppConfiguration(args: any) {
|
||||||
|
await updateAppConfiguration(args)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start Jan API Server.
|
||||||
|
*/
|
||||||
|
async startServer(args?: any) {
|
||||||
|
const { startServer } = require('@janhq/server')
|
||||||
|
return startServer({
|
||||||
|
host: args?.host,
|
||||||
|
port: args?.port,
|
||||||
|
isCorsEnabled: args?.isCorsEnabled,
|
||||||
|
isVerboseEnabled: args?.isVerboseEnabled,
|
||||||
|
schemaPath: join(await appResourcePath(), 'docs', 'openapi', 'jan.yaml'),
|
||||||
|
baseDir: join(await appResourcePath(), 'docs', 'openapi'),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop Jan API Server.
|
||||||
|
*/
|
||||||
|
stopServer() {
|
||||||
|
const { stopServer } = require('@janhq/server')
|
||||||
|
return stopServer()
|
||||||
|
}
|
||||||
|
}
|
||||||
105
core/src/node/api/processors/download.ts
Normal file
105
core/src/node/api/processors/download.ts
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
import { resolve, sep } from 'path'
|
||||||
|
import { DownloadEvent } from '../../../api'
|
||||||
|
import { normalizeFilePath } from '../../helper/path'
|
||||||
|
import { getJanDataFolderPath } from '../../helper'
|
||||||
|
import { DownloadManager } from '../../helper/download'
|
||||||
|
import { createWriteStream, renameSync } from 'fs'
|
||||||
|
import { Processor } from './Processor'
|
||||||
|
import { DownloadState } from '../../../types'
|
||||||
|
|
||||||
|
export class Downloader implements Processor {
|
||||||
|
observer?: Function
|
||||||
|
|
||||||
|
constructor(observer?: Function) {
|
||||||
|
this.observer = observer
|
||||||
|
}
|
||||||
|
|
||||||
|
process(key: string, ...args: any[]): any {
|
||||||
|
const instance = this as any
|
||||||
|
const func = instance[key]
|
||||||
|
return func(this.observer, ...args)
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadFile(observer: any, url: string, localPath: string, network: any) {
|
||||||
|
const request = require('request')
|
||||||
|
const progress = require('request-progress')
|
||||||
|
|
||||||
|
const strictSSL = !network?.ignoreSSL
|
||||||
|
const proxy = network?.proxy?.startsWith('http') ? network.proxy : undefined
|
||||||
|
if (typeof localPath === 'string') {
|
||||||
|
localPath = normalizeFilePath(localPath)
|
||||||
|
}
|
||||||
|
const array = localPath.split(sep)
|
||||||
|
const fileName = array.pop() ?? ''
|
||||||
|
const modelId = array.pop() ?? ''
|
||||||
|
|
||||||
|
const destination = resolve(getJanDataFolderPath(), localPath)
|
||||||
|
const rq = request({ url, strictSSL, proxy })
|
||||||
|
|
||||||
|
// Put request to download manager instance
|
||||||
|
DownloadManager.instance.setRequest(localPath, rq)
|
||||||
|
|
||||||
|
// Downloading file to a temp file first
|
||||||
|
const downloadingTempFile = `${destination}.download`
|
||||||
|
|
||||||
|
progress(rq, {})
|
||||||
|
.on('progress', (state: any) => {
|
||||||
|
const downloadState: DownloadState = {
|
||||||
|
...state,
|
||||||
|
modelId,
|
||||||
|
fileName,
|
||||||
|
downloadState: 'downloading',
|
||||||
|
}
|
||||||
|
console.log('progress: ', downloadState)
|
||||||
|
observer?.(DownloadEvent.onFileDownloadUpdate, downloadState)
|
||||||
|
DownloadManager.instance.downloadProgressMap[modelId] = downloadState
|
||||||
|
})
|
||||||
|
.on('error', (error: Error) => {
|
||||||
|
const currentDownloadState = DownloadManager.instance.downloadProgressMap[modelId]
|
||||||
|
const downloadState: DownloadState = {
|
||||||
|
...currentDownloadState,
|
||||||
|
downloadState: 'error',
|
||||||
|
}
|
||||||
|
if (currentDownloadState) {
|
||||||
|
DownloadManager.instance.downloadProgressMap[modelId] = downloadState
|
||||||
|
}
|
||||||
|
|
||||||
|
observer?.(DownloadEvent.onFileDownloadError, downloadState)
|
||||||
|
})
|
||||||
|
.on('end', () => {
|
||||||
|
const currentDownloadState = DownloadManager.instance.downloadProgressMap[modelId]
|
||||||
|
if (currentDownloadState && DownloadManager.instance.networkRequests[localPath]) {
|
||||||
|
// Finished downloading, rename temp file to actual file
|
||||||
|
renameSync(downloadingTempFile, destination)
|
||||||
|
const downloadState: DownloadState = {
|
||||||
|
...currentDownloadState,
|
||||||
|
downloadState: 'end',
|
||||||
|
}
|
||||||
|
observer?.(DownloadEvent.onFileDownloadSuccess, downloadState)
|
||||||
|
DownloadManager.instance.downloadProgressMap[modelId] = downloadState
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.pipe(createWriteStream(downloadingTempFile))
|
||||||
|
}
|
||||||
|
|
||||||
|
abortDownload(observer: any, fileName: string) {
|
||||||
|
const rq = DownloadManager.instance.networkRequests[fileName]
|
||||||
|
if (rq) {
|
||||||
|
DownloadManager.instance.networkRequests[fileName] = undefined
|
||||||
|
rq?.abort()
|
||||||
|
} else {
|
||||||
|
observer?.(DownloadEvent.onFileDownloadError, {
|
||||||
|
fileName,
|
||||||
|
error: 'aborted',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resumeDownload(observer: any, fileName: any) {
|
||||||
|
DownloadManager.instance.networkRequests[fileName]?.resume()
|
||||||
|
}
|
||||||
|
|
||||||
|
pauseDownload(observer: any, fileName: any) {
|
||||||
|
DownloadManager.instance.networkRequests[fileName]?.pause()
|
||||||
|
}
|
||||||
|
}
|
||||||
89
core/src/node/api/processors/extension.ts
Normal file
89
core/src/node/api/processors/extension.ts
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
import { readdirSync } from 'fs'
|
||||||
|
import { join, extname } from 'path'
|
||||||
|
|
||||||
|
import { ExtensionRoute } from '../../../api'
|
||||||
|
import { Processor } from './Processor'
|
||||||
|
import { ModuleManager } from '../../helper/module'
|
||||||
|
import { getJanExtensionsPath as getPath } from '../../helper'
|
||||||
|
import {
|
||||||
|
getActiveExtensions as getExtensions,
|
||||||
|
getExtension,
|
||||||
|
removeExtension,
|
||||||
|
installExtensions,
|
||||||
|
} from '../../extension/store'
|
||||||
|
import { appResourcePath } from '../../helper/path'
|
||||||
|
|
||||||
|
export class Extension implements Processor {
|
||||||
|
observer?: Function
|
||||||
|
|
||||||
|
constructor(observer?: Function) {
|
||||||
|
this.observer = observer
|
||||||
|
}
|
||||||
|
|
||||||
|
process(key: string, ...args: any[]): any {
|
||||||
|
const instance = this as any
|
||||||
|
const func = instance[key]
|
||||||
|
return func(...args)
|
||||||
|
}
|
||||||
|
|
||||||
|
invokeExtensionFunc(modulePath: string, method: string, ...params: any[]) {
|
||||||
|
const module = require(join(getPath(), modulePath))
|
||||||
|
ModuleManager.instance.setModule(modulePath, module)
|
||||||
|
|
||||||
|
if (typeof module[method] === 'function') {
|
||||||
|
return module[method](...params)
|
||||||
|
} else {
|
||||||
|
console.debug(module[method])
|
||||||
|
console.error(`Function "${method}" does not exist in the module.`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the paths of the base extensions.
|
||||||
|
* @returns An array of paths to the base extensions.
|
||||||
|
*/
|
||||||
|
async baseExtensions() {
|
||||||
|
const baseExtensionPath = join(await appResourcePath(), 'pre-install')
|
||||||
|
return readdirSync(baseExtensionPath)
|
||||||
|
.filter((file) => extname(file) === '.tgz')
|
||||||
|
.map((file) => join(baseExtensionPath, file))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**MARK: Extension Manager handlers */
|
||||||
|
async installExtension(extensions: any) {
|
||||||
|
// Install and activate all provided extensions
|
||||||
|
const installed = await installExtensions(extensions)
|
||||||
|
return JSON.parse(JSON.stringify(installed))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register IPC route to uninstall a extension
|
||||||
|
async uninstallExtension(extensions: any) {
|
||||||
|
// 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
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register IPC route to update a extension
|
||||||
|
async updateExtension(extensions: any) {
|
||||||
|
// Update all provided extensions
|
||||||
|
const updated: any[] = []
|
||||||
|
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
|
||||||
|
return JSON.parse(JSON.stringify(updated))
|
||||||
|
}
|
||||||
|
|
||||||
|
getActiveExtensions() {
|
||||||
|
return JSON.parse(JSON.stringify(getExtensions()))
|
||||||
|
}
|
||||||
|
}
|
||||||
25
core/src/node/api/processors/fs.ts
Normal file
25
core/src/node/api/processors/fs.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { join } from 'path'
|
||||||
|
import { normalizeFilePath } from '../../helper/path'
|
||||||
|
import { getJanDataFolderPath } from '../../helper'
|
||||||
|
import { Processor } from './Processor'
|
||||||
|
|
||||||
|
export class FileSystem implements Processor {
|
||||||
|
observer?: Function
|
||||||
|
private static moduleName = 'fs'
|
||||||
|
|
||||||
|
constructor(observer?: Function) {
|
||||||
|
this.observer = observer
|
||||||
|
}
|
||||||
|
|
||||||
|
process(route: string, ...args: any[]): any {
|
||||||
|
return import(FileSystem.moduleName).then((mdl) =>
|
||||||
|
mdl[route](
|
||||||
|
...args.map((arg: any) =>
|
||||||
|
typeof arg === 'string' && (arg.startsWith(`file:/`) || arg.startsWith(`file:\\`))
|
||||||
|
? join(getJanDataFolderPath(), normalizeFilePath(arg))
|
||||||
|
: arg
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
78
core/src/node/api/processors/fsExt.ts
Normal file
78
core/src/node/api/processors/fsExt.ts
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
import { join } from 'path'
|
||||||
|
import fs from 'fs'
|
||||||
|
import { FileManagerRoute } from '../../../api'
|
||||||
|
import { appResourcePath, normalizeFilePath } from '../../helper/path'
|
||||||
|
import { getJanDataFolderPath, getJanDataFolderPath as getPath } from '../../helper'
|
||||||
|
import { Processor } from './Processor'
|
||||||
|
import { FileStat } from '../../../types'
|
||||||
|
|
||||||
|
export class FSExt implements Processor {
|
||||||
|
observer?: Function
|
||||||
|
|
||||||
|
constructor(observer?: Function) {
|
||||||
|
this.observer = observer
|
||||||
|
}
|
||||||
|
|
||||||
|
process(key: string, ...args: any): any {
|
||||||
|
const instance = this as any
|
||||||
|
const func = instance[key]
|
||||||
|
return func(...args)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles the 'syncFile' IPC event. This event is triggered to synchronize a file from a source path to a destination path.
|
||||||
|
syncFile(src: string, dest: string) {
|
||||||
|
const reflect = require('@alumna/reflect')
|
||||||
|
return reflect({
|
||||||
|
src,
|
||||||
|
dest,
|
||||||
|
recursive: true,
|
||||||
|
delete: false,
|
||||||
|
overwrite: true,
|
||||||
|
errorOnExist: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles the 'getJanDataFolderPath' IPC event. This event is triggered to get the user space path.
|
||||||
|
getJanDataFolderPath() {
|
||||||
|
return Promise.resolve(getPath())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles the 'getResourcePath' IPC event. This event is triggered to get the resource path.
|
||||||
|
getResourcePath() {
|
||||||
|
return appResourcePath()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles the 'getUserHomePath' IPC event. This event is triggered to get the user home path.
|
||||||
|
getUserHomePath() {
|
||||||
|
return process.env[process.platform == 'win32' ? 'USERPROFILE' : 'HOME']
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle fs is directory here
|
||||||
|
fileStat(path: string) {
|
||||||
|
const normalizedPath = normalizeFilePath(path)
|
||||||
|
|
||||||
|
const fullPath = join(getJanDataFolderPath(), normalizedPath)
|
||||||
|
const isExist = fs.existsSync(fullPath)
|
||||||
|
if (!isExist) return undefined
|
||||||
|
|
||||||
|
const isDirectory = fs.lstatSync(fullPath).isDirectory()
|
||||||
|
const size = fs.statSync(fullPath).size
|
||||||
|
|
||||||
|
const fileStat: FileStat = {
|
||||||
|
isDirectory,
|
||||||
|
size,
|
||||||
|
}
|
||||||
|
|
||||||
|
return fileStat
|
||||||
|
}
|
||||||
|
|
||||||
|
writeBlob(path: string, data: any) {
|
||||||
|
try {
|
||||||
|
const normalizedPath = normalizeFilePath(path)
|
||||||
|
const dataBuffer = Buffer.from(data, 'base64')
|
||||||
|
fs.writeFileSync(join(getJanDataFolderPath(), normalizedPath), dataBuffer)
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`writeFile ${path} result: ${err}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
23
core/src/node/api/restful/app/download.ts
Normal file
23
core/src/node/api/restful/app/download.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { DownloadRoute } from '../../../../api'
|
||||||
|
import { DownloadManager } from '../../../helper/download'
|
||||||
|
import { HttpServer } from '../../HttpServer'
|
||||||
|
|
||||||
|
export const downloadRouter = async (app: HttpServer) => {
|
||||||
|
app.get(`/download/${DownloadRoute.getDownloadProgress}/:modelId`, async (req, res) => {
|
||||||
|
const modelId = req.params.modelId
|
||||||
|
|
||||||
|
console.debug(`Getting download progress for model ${modelId}`)
|
||||||
|
console.debug(
|
||||||
|
`All Download progress: ${JSON.stringify(DownloadManager.instance.downloadProgressMap)}`
|
||||||
|
)
|
||||||
|
|
||||||
|
// check if null DownloadManager.instance.downloadProgressMap
|
||||||
|
if (!DownloadManager.instance.downloadProgressMap[modelId]) {
|
||||||
|
return res.status(404).send({
|
||||||
|
message: 'Download progress not found',
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
return res.status(200).send(DownloadManager.instance.downloadProgressMap[modelId])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
13
core/src/node/api/restful/app/handlers.ts
Normal file
13
core/src/node/api/restful/app/handlers.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { HttpServer } from '../../HttpServer'
|
||||||
|
import { Handler, RequestHandler } from '../../common/handler'
|
||||||
|
|
||||||
|
export function handleRequests(app: HttpServer) {
|
||||||
|
const restWrapper: Handler = (route: string, listener: (...args: any[]) => any) => {
|
||||||
|
app.post(`/app/${route}`, async (request: any, reply: any) => {
|
||||||
|
const args = JSON.parse(request.body) as any[]
|
||||||
|
reply.send(JSON.stringify(await listener(...args)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const handler = new RequestHandler(restWrapper)
|
||||||
|
handler.handle()
|
||||||
|
}
|
||||||
@ -1,22 +1,24 @@
|
|||||||
import { AppRoute } from '../../../api'
|
|
||||||
import { HttpServer } from '../HttpServer'
|
import { HttpServer } from '../HttpServer'
|
||||||
import { basename, join } from 'path'
|
|
||||||
import {
|
import {
|
||||||
chatCompletions,
|
chatCompletions,
|
||||||
deleteBuilder,
|
deleteBuilder,
|
||||||
downloadModel,
|
downloadModel,
|
||||||
getBuilder,
|
getBuilder,
|
||||||
retrieveBuilder,
|
retrieveBuilder,
|
||||||
} from '../common/builder'
|
createMessage,
|
||||||
|
createThread,
|
||||||
|
getMessages,
|
||||||
|
retrieveMesasge,
|
||||||
|
updateThread,
|
||||||
|
} from './helper/builder'
|
||||||
|
|
||||||
import { JanApiRouteConfiguration } from '../common/configuration'
|
import { JanApiRouteConfiguration } from './helper/configuration'
|
||||||
import { startModel, stopModel } from '../common/startStopModel'
|
import { startModel, stopModel } from './helper/startStopModel'
|
||||||
import { ModelSettingParams } from '../../../types'
|
import { ModelSettingParams } from '../../../types'
|
||||||
import { getJanDataFolderPath } from '../../utils'
|
|
||||||
import { normalizeFilePath } from '../../path'
|
|
||||||
|
|
||||||
export const commonRouter = async (app: HttpServer) => {
|
export const commonRouter = async (app: HttpServer) => {
|
||||||
// Common Routes
|
// Common Routes
|
||||||
|
// Read & Delete :: Threads | Models | Assistants
|
||||||
Object.keys(JanApiRouteConfiguration).forEach((key) => {
|
Object.keys(JanApiRouteConfiguration).forEach((key) => {
|
||||||
app.get(`/${key}`, async (_request) => getBuilder(JanApiRouteConfiguration[key]))
|
app.get(`/${key}`, async (_request) => getBuilder(JanApiRouteConfiguration[key]))
|
||||||
|
|
||||||
@ -29,7 +31,24 @@ export const commonRouter = async (app: HttpServer) => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Download Model Routes
|
// Threads
|
||||||
|
app.post(`/threads/`, async (req, res) => createThread(req.body))
|
||||||
|
|
||||||
|
app.get(`/threads/:threadId/messages`, async (req, res) => getMessages(req.params.threadId))
|
||||||
|
|
||||||
|
app.get(`/threads/:threadId/messages/:messageId`, async (req, res) =>
|
||||||
|
retrieveMesasge(req.params.threadId, req.params.messageId)
|
||||||
|
)
|
||||||
|
|
||||||
|
app.post(`/threads/:threadId/messages`, async (req, res) =>
|
||||||
|
createMessage(req.params.threadId as any, req.body as any)
|
||||||
|
)
|
||||||
|
|
||||||
|
app.patch(`/threads/:threadId`, async (request: any) =>
|
||||||
|
updateThread(request.params.threadId, request.body)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Models
|
||||||
app.get(`/models/download/:modelId`, async (request: any) =>
|
app.get(`/models/download/:modelId`, async (request: any) =>
|
||||||
downloadModel(request.params.modelId, {
|
downloadModel(request.params.modelId, {
|
||||||
ignoreSSL: request.query.ignoreSSL === 'true',
|
ignoreSSL: request.query.ignoreSSL === 'true',
|
||||||
@ -48,24 +67,6 @@ export const commonRouter = async (app: HttpServer) => {
|
|||||||
|
|
||||||
app.put(`/models/:modelId/stop`, async (request: any) => stopModel(request.params.modelId))
|
app.put(`/models/:modelId/stop`, async (request: any) => stopModel(request.params.modelId))
|
||||||
|
|
||||||
// Chat Completion Routes
|
// Chat Completion
|
||||||
app.post(`/chat/completions`, async (request: any, reply: any) => chatCompletions(request, reply))
|
app.post(`/chat/completions`, async (request: any, reply: any) => chatCompletions(request, reply))
|
||||||
|
|
||||||
// App Routes
|
|
||||||
app.post(`/app/${AppRoute.joinPath}`, async (request: any, reply: any) => {
|
|
||||||
const args = JSON.parse(request.body) as any[]
|
|
||||||
|
|
||||||
const paths = args[0].map((arg: string) =>
|
|
||||||
typeof arg === 'string' && (arg.startsWith(`file:/`) || arg.startsWith(`file:\\`))
|
|
||||||
? join(getJanDataFolderPath(), normalizeFilePath(arg))
|
|
||||||
: arg
|
|
||||||
)
|
|
||||||
|
|
||||||
reply.send(JSON.stringify(join(...paths)))
|
|
||||||
})
|
|
||||||
|
|
||||||
app.post(`/app/${AppRoute.baseName}`, async (request: any, reply: any) => {
|
|
||||||
const args = JSON.parse(request.body) as any[]
|
|
||||||
reply.send(JSON.stringify(basename(args[0])))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
@ -1,10 +1,11 @@
|
|||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import { JanApiRouteConfiguration, RouteConfiguration } from './configuration'
|
import { JanApiRouteConfiguration, RouteConfiguration } from './configuration'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import { ContentType, MessageStatus, Model, ThreadMessage } from './../../../index'
|
import { ContentType, MessageStatus, Model, ThreadMessage } from '../../../../index'
|
||||||
import { getEngineConfiguration, getJanDataFolderPath } from '../../utils'
|
import { getEngineConfiguration, getJanDataFolderPath } from '../../../helper'
|
||||||
import { DEFAULT_CHAT_COMPLETION_URL } from './consts'
|
import { DEFAULT_CHAT_COMPLETION_URL } from './consts'
|
||||||
|
|
||||||
|
// TODO: Refactor these
|
||||||
export const getBuilder = async (configuration: RouteConfiguration) => {
|
export const getBuilder = async (configuration: RouteConfiguration) => {
|
||||||
const directoryPath = join(getJanDataFolderPath(), configuration.dirName)
|
const directoryPath = join(getJanDataFolderPath(), configuration.dirName)
|
||||||
try {
|
try {
|
||||||
@ -1,9 +1,9 @@
|
|||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import { getJanDataFolderPath, getJanExtensionsPath, getSystemResourceInfo } from '../../utils'
|
import { getJanDataFolderPath, getJanExtensionsPath, getSystemResourceInfo } from '../../../helper'
|
||||||
import { logServer } from '../../log'
|
import { logServer } from '../../../helper/log'
|
||||||
import { ChildProcessWithoutNullStreams, spawn } from 'child_process'
|
import { ChildProcessWithoutNullStreams, spawn } from 'child_process'
|
||||||
import { Model, ModelSettingParams, PromptTemplate } from '../../../types'
|
import { Model, ModelSettingParams, PromptTemplate } from '../../../../types'
|
||||||
import {
|
import {
|
||||||
LOCAL_HOST,
|
LOCAL_HOST,
|
||||||
NITRO_DEFAULT_PORT,
|
NITRO_DEFAULT_PORT,
|
||||||
16
core/src/node/api/restful/v1.ts
Normal file
16
core/src/node/api/restful/v1.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { HttpServer } from '../HttpServer'
|
||||||
|
import { commonRouter } from './common'
|
||||||
|
import { downloadRouter } from './app/download'
|
||||||
|
import { handleRequests } from './app/handlers'
|
||||||
|
|
||||||
|
export const v1Router = async (app: HttpServer) => {
|
||||||
|
// MARK: Public API Routes
|
||||||
|
app.register(commonRouter)
|
||||||
|
|
||||||
|
// MARK: Internal Application Routes
|
||||||
|
handleRequests(app)
|
||||||
|
|
||||||
|
// Expanded route for tracking download progress
|
||||||
|
// TODO: Replace by Observer Wrapper (ZeroMQ / Vanilla Websocket)
|
||||||
|
app.register(downloadRouter)
|
||||||
|
}
|
||||||
@ -1,112 +0,0 @@
|
|||||||
import { DownloadRoute } from '../../../api'
|
|
||||||
import { join, sep } from 'path'
|
|
||||||
import { DownloadManager } from '../../download'
|
|
||||||
import { HttpServer } from '../HttpServer'
|
|
||||||
import { createWriteStream } from 'fs'
|
|
||||||
import { getJanDataFolderPath } from '../../utils'
|
|
||||||
import { normalizeFilePath } from '../../path'
|
|
||||||
import { DownloadState } from '../../../types'
|
|
||||||
|
|
||||||
export const downloadRouter = async (app: HttpServer) => {
|
|
||||||
app.get(`/${DownloadRoute.getDownloadProgress}/:modelId`, async (req, res) => {
|
|
||||||
const modelId = req.params.modelId
|
|
||||||
|
|
||||||
console.debug(`Getting download progress for model ${modelId}`)
|
|
||||||
console.debug(
|
|
||||||
`All Download progress: ${JSON.stringify(DownloadManager.instance.downloadProgressMap)}`
|
|
||||||
)
|
|
||||||
|
|
||||||
// check if null DownloadManager.instance.downloadProgressMap
|
|
||||||
if (!DownloadManager.instance.downloadProgressMap[modelId]) {
|
|
||||||
return res.status(404).send({
|
|
||||||
message: 'Download progress not found',
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
return res.status(200).send(DownloadManager.instance.downloadProgressMap[modelId])
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
app.post(`/${DownloadRoute.downloadFile}`, async (req, res) => {
|
|
||||||
const strictSSL = !(req.query.ignoreSSL === 'true')
|
|
||||||
const proxy = req.query.proxy?.startsWith('http') ? req.query.proxy : undefined
|
|
||||||
const body = JSON.parse(req.body as any)
|
|
||||||
const normalizedArgs = body.map((arg: any) => {
|
|
||||||
if (typeof arg === 'string' && arg.startsWith('file:')) {
|
|
||||||
return join(getJanDataFolderPath(), normalizeFilePath(arg))
|
|
||||||
}
|
|
||||||
return arg
|
|
||||||
})
|
|
||||||
|
|
||||||
const localPath = normalizedArgs[1]
|
|
||||||
const array = localPath.split(sep)
|
|
||||||
const fileName = array.pop() ?? ''
|
|
||||||
const modelId = array.pop() ?? ''
|
|
||||||
console.debug('downloadFile', normalizedArgs, fileName, modelId)
|
|
||||||
|
|
||||||
const request = require('request')
|
|
||||||
const progress = require('request-progress')
|
|
||||||
|
|
||||||
const rq = request({ url: normalizedArgs[0], strictSSL, proxy })
|
|
||||||
progress(rq, {})
|
|
||||||
.on('progress', function (state: any) {
|
|
||||||
const downloadProps: DownloadState = {
|
|
||||||
...state,
|
|
||||||
modelId,
|
|
||||||
fileName,
|
|
||||||
downloadState: 'downloading',
|
|
||||||
}
|
|
||||||
console.debug(`Download ${modelId} onProgress`, downloadProps)
|
|
||||||
DownloadManager.instance.downloadProgressMap[modelId] = downloadProps
|
|
||||||
})
|
|
||||||
.on('error', function (err: Error) {
|
|
||||||
console.debug(`Download ${modelId} onError`, err.message)
|
|
||||||
|
|
||||||
const currentDownloadState = DownloadManager.instance.downloadProgressMap[modelId]
|
|
||||||
if (currentDownloadState) {
|
|
||||||
DownloadManager.instance.downloadProgressMap[modelId] = {
|
|
||||||
...currentDownloadState,
|
|
||||||
downloadState: 'error',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.on('end', function () {
|
|
||||||
console.debug(`Download ${modelId} onEnd`)
|
|
||||||
|
|
||||||
const currentDownloadState = DownloadManager.instance.downloadProgressMap[modelId]
|
|
||||||
if (currentDownloadState) {
|
|
||||||
if (currentDownloadState.downloadState === 'downloading') {
|
|
||||||
// if the previous state is downloading, then set the state to end (success)
|
|
||||||
DownloadManager.instance.downloadProgressMap[modelId] = {
|
|
||||||
...currentDownloadState,
|
|
||||||
downloadState: 'end',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.pipe(createWriteStream(normalizedArgs[1]))
|
|
||||||
|
|
||||||
DownloadManager.instance.setRequest(localPath, rq)
|
|
||||||
res.status(200).send({ message: 'Download started' })
|
|
||||||
})
|
|
||||||
|
|
||||||
app.post(`/${DownloadRoute.abortDownload}`, async (req, res) => {
|
|
||||||
const body = JSON.parse(req.body as any)
|
|
||||||
const normalizedArgs = body.map((arg: any) => {
|
|
||||||
if (typeof arg === 'string' && arg.startsWith('file:')) {
|
|
||||||
return join(getJanDataFolderPath(), normalizeFilePath(arg))
|
|
||||||
}
|
|
||||||
return arg
|
|
||||||
})
|
|
||||||
|
|
||||||
const localPath = normalizedArgs[0]
|
|
||||||
const fileName = localPath.split(sep).pop() ?? ''
|
|
||||||
const rq = DownloadManager.instance.networkRequests[fileName]
|
|
||||||
DownloadManager.instance.networkRequests[fileName] = undefined
|
|
||||||
rq?.abort()
|
|
||||||
if (rq) {
|
|
||||||
res.status(200).send({ message: 'Download aborted' })
|
|
||||||
} else {
|
|
||||||
res.status(404).send({ message: 'Download not found' })
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@ -1,49 +0,0 @@
|
|||||||
import { join, extname } from 'path'
|
|
||||||
import { ExtensionRoute } from '../../../api/index'
|
|
||||||
import { ModuleManager } from '../../module'
|
|
||||||
import { getActiveExtensions, installExtensions } from '../../extension/store'
|
|
||||||
import { HttpServer } from '../HttpServer'
|
|
||||||
|
|
||||||
import { readdirSync } from 'fs'
|
|
||||||
import { getJanExtensionsPath } from '../../utils'
|
|
||||||
|
|
||||||
export const extensionRouter = async (app: HttpServer) => {
|
|
||||||
// TODO: Share code between node projects
|
|
||||||
app.post(`/${ExtensionRoute.getActiveExtensions}`, async (_req, res) => {
|
|
||||||
const activeExtensions = await getActiveExtensions()
|
|
||||||
res.status(200).send(activeExtensions)
|
|
||||||
})
|
|
||||||
|
|
||||||
app.post(`/${ExtensionRoute.baseExtensions}`, async (_req, res) => {
|
|
||||||
const baseExtensionPath = join(__dirname, '..', '..', '..', 'pre-install')
|
|
||||||
const extensions = readdirSync(baseExtensionPath)
|
|
||||||
.filter((file) => extname(file) === '.tgz')
|
|
||||||
.map((file) => join(baseExtensionPath, file))
|
|
||||||
|
|
||||||
res.status(200).send(extensions)
|
|
||||||
})
|
|
||||||
|
|
||||||
app.post(`/${ExtensionRoute.installExtension}`, async (req) => {
|
|
||||||
const extensions = req.body as any
|
|
||||||
const installed = await installExtensions(JSON.parse(extensions)[0])
|
|
||||||
return JSON.parse(JSON.stringify(installed))
|
|
||||||
})
|
|
||||||
|
|
||||||
app.post(`/${ExtensionRoute.invokeExtensionFunc}`, async (req, res) => {
|
|
||||||
const args = JSON.parse(req.body as any)
|
|
||||||
console.debug(args)
|
|
||||||
const module = await import(join(getJanExtensionsPath(), args[0]))
|
|
||||||
|
|
||||||
ModuleManager.instance.setModule(args[0], module)
|
|
||||||
const method = args[1]
|
|
||||||
if (typeof module[method] === 'function') {
|
|
||||||
// remove first item from args
|
|
||||||
const newArgs = args.slice(2)
|
|
||||||
console.log(newArgs)
|
|
||||||
return module[method](...args.slice(2))
|
|
||||||
} else {
|
|
||||||
console.debug(module[method])
|
|
||||||
console.error(`Function "${method}" does not exist in the module.`)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
import { FileManagerRoute } from '../../../api'
|
|
||||||
import { HttpServer } from '../../index'
|
|
||||||
import { join } from 'path'
|
|
||||||
|
|
||||||
export const fileManagerRouter = async (app: HttpServer) => {
|
|
||||||
app.post(`/fs/${FileManagerRoute.syncFile}`, async (request: any, reply: any) => {
|
|
||||||
const reflect = require('@alumna/reflect')
|
|
||||||
const args = JSON.parse(request.body)
|
|
||||||
return reflect({
|
|
||||||
src: args[0],
|
|
||||||
dest: args[1],
|
|
||||||
recursive: true,
|
|
||||||
delete: false,
|
|
||||||
overwrite: true,
|
|
||||||
errorOnExist: false,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
app.post(`/fs/${FileManagerRoute.getJanDataFolderPath}`, async (request: any, reply: any) =>
|
|
||||||
global.core.appPath()
|
|
||||||
)
|
|
||||||
|
|
||||||
app.post(`/fs/${FileManagerRoute.getResourcePath}`, async (request: any, reply: any) =>
|
|
||||||
join(global.core.appPath(), '../../..')
|
|
||||||
)
|
|
||||||
|
|
||||||
app.post(`/app/${FileManagerRoute.getUserHomePath}`, async (request: any, reply: any) => {})
|
|
||||||
app.post(`/fs/${FileManagerRoute.fileStat}`, async (request: any, reply: any) => {})
|
|
||||||
}
|
|
||||||
@ -1,40 +0,0 @@
|
|||||||
import { FileManagerRoute, FileSystemRoute } from '../../../api'
|
|
||||||
import { join } from 'path'
|
|
||||||
import { HttpServer } from '../HttpServer'
|
|
||||||
import { getJanDataFolderPath } from '../../utils'
|
|
||||||
import { normalizeFilePath } from '../../path'
|
|
||||||
import { writeFileSync } from 'fs'
|
|
||||||
|
|
||||||
export const fsRouter = async (app: HttpServer) => {
|
|
||||||
const moduleName = 'fs'
|
|
||||||
// Generate handlers for each fs route
|
|
||||||
Object.values(FileSystemRoute).forEach((route) => {
|
|
||||||
app.post(`/${route}`, async (req, res) => {
|
|
||||||
const body = JSON.parse(req.body as any)
|
|
||||||
try {
|
|
||||||
const result = await import(moduleName).then((mdl) => {
|
|
||||||
return mdl[route](
|
|
||||||
...body.map((arg: any) =>
|
|
||||||
typeof arg === 'string' && (arg.startsWith(`file:/`) || arg.startsWith(`file:\\`))
|
|
||||||
? join(getJanDataFolderPath(), normalizeFilePath(arg))
|
|
||||||
: arg
|
|
||||||
)
|
|
||||||
)
|
|
||||||
})
|
|
||||||
res.status(200).send(result)
|
|
||||||
} catch (ex) {
|
|
||||||
console.log(ex)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
app.post(`/${FileManagerRoute.writeBlob}`, async (request: any, reply: any) => {
|
|
||||||
try {
|
|
||||||
const args = JSON.parse(request.body) as any[]
|
|
||||||
console.log('writeBlob:', args[0])
|
|
||||||
const dataBuffer = Buffer.from(args[1], 'base64')
|
|
||||||
writeFileSync(args[0], dataBuffer)
|
|
||||||
} catch (err) {
|
|
||||||
console.error(`writeFile ${request.body} result: ${err}`)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
export * from './download'
|
|
||||||
export * from './extension'
|
|
||||||
export * from './fs'
|
|
||||||
export * from './thread'
|
|
||||||
export * from './common'
|
|
||||||
export * from './v1'
|
|
||||||
@ -1,30 +0,0 @@
|
|||||||
import { HttpServer } from '../HttpServer'
|
|
||||||
import {
|
|
||||||
createMessage,
|
|
||||||
createThread,
|
|
||||||
getMessages,
|
|
||||||
retrieveMesasge,
|
|
||||||
updateThread,
|
|
||||||
} from '../common/builder'
|
|
||||||
|
|
||||||
export const threadRouter = async (app: HttpServer) => {
|
|
||||||
// create thread
|
|
||||||
app.post(`/`, async (req, res) => createThread(req.body))
|
|
||||||
|
|
||||||
app.get(`/:threadId/messages`, async (req, res) => getMessages(req.params.threadId))
|
|
||||||
|
|
||||||
// retrieve message
|
|
||||||
app.get(`/:threadId/messages/:messageId`, async (req, res) =>
|
|
||||||
retrieveMesasge(req.params.threadId, req.params.messageId),
|
|
||||||
)
|
|
||||||
|
|
||||||
// create message
|
|
||||||
app.post(`/:threadId/messages`, async (req, res) =>
|
|
||||||
createMessage(req.params.threadId as any, req.body as any),
|
|
||||||
)
|
|
||||||
|
|
||||||
// modify thread
|
|
||||||
app.patch(`/:threadId`, async (request: any) =>
|
|
||||||
updateThread(request.params.threadId, request.body),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@ -1,28 +0,0 @@
|
|||||||
import { HttpServer } from '../HttpServer'
|
|
||||||
import { commonRouter } from './common'
|
|
||||||
import { threadRouter } from './thread'
|
|
||||||
import { fsRouter } from './fs'
|
|
||||||
import { extensionRouter } from './extension'
|
|
||||||
import { downloadRouter } from './download'
|
|
||||||
import { fileManagerRouter } from './fileManager'
|
|
||||||
|
|
||||||
export const v1Router = async (app: HttpServer) => {
|
|
||||||
// MARK: External Routes
|
|
||||||
app.register(commonRouter)
|
|
||||||
app.register(threadRouter, {
|
|
||||||
prefix: '/threads',
|
|
||||||
})
|
|
||||||
|
|
||||||
// MARK: Internal Application Routes
|
|
||||||
app.register(fsRouter, {
|
|
||||||
prefix: '/fs',
|
|
||||||
})
|
|
||||||
app.register(fileManagerRouter)
|
|
||||||
|
|
||||||
app.register(extensionRouter, {
|
|
||||||
prefix: '/extension',
|
|
||||||
})
|
|
||||||
app.register(downloadRouter, {
|
|
||||||
prefix: '/download',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@ -94,7 +94,7 @@ export function persistExtensions() {
|
|||||||
* @returns {Promise.<Array.<Extension>>} New extension
|
* @returns {Promise.<Array.<Extension>>} New extension
|
||||||
* @alias extensionManager.installExtensions
|
* @alias extensionManager.installExtensions
|
||||||
*/
|
*/
|
||||||
export async function installExtensions(extensions: any, store = true) {
|
export async function installExtensions(extensions: any) {
|
||||||
const installed: Extension[] = [];
|
const installed: Extension[] = [];
|
||||||
for (const ext of extensions) {
|
for (const ext of extensions) {
|
||||||
// Set install options and activation based on input type
|
// Set install options and activation based on input type
|
||||||
@ -104,11 +104,12 @@ export async function installExtensions(extensions: any, store = true) {
|
|||||||
|
|
||||||
// Install and possibly activate extension
|
// Install and possibly activate extension
|
||||||
const extension = new Extension(...spec);
|
const extension = new Extension(...spec);
|
||||||
|
if(!extension.origin) { continue }
|
||||||
await extension._install();
|
await extension._install();
|
||||||
if (activate) extension.setActive(true);
|
if (activate) extension.setActive(true);
|
||||||
|
|
||||||
// Add extension to store if needed
|
// Add extension to store if needed
|
||||||
if (store) addExtension(extension);
|
addExtension(extension);
|
||||||
installed.push(extension);
|
installed.push(extension);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { AppConfiguration, SystemResourceInfo } from '../../types'
|
|||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import os from 'os'
|
import os from 'os'
|
||||||
import { log, logServer } from '../log'
|
import { log, logServer } from './log'
|
||||||
import childProcess from 'child_process'
|
import childProcess from 'child_process'
|
||||||
|
|
||||||
// TODO: move this to core
|
// TODO: move this to core
|
||||||
@ -56,34 +56,6 @@ export const updateAppConfiguration = (configuration: AppConfiguration): Promise
|
|||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility function to get server log path
|
|
||||||
*
|
|
||||||
* @returns {string} The log path.
|
|
||||||
*/
|
|
||||||
export const getServerLogPath = (): string => {
|
|
||||||
const appConfigurations = getAppConfigurations()
|
|
||||||
const logFolderPath = join(appConfigurations.data_folder, 'logs')
|
|
||||||
if (!fs.existsSync(logFolderPath)) {
|
|
||||||
fs.mkdirSync(logFolderPath, { recursive: true })
|
|
||||||
}
|
|
||||||
return join(logFolderPath, 'server.log')
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility function to get app log path
|
|
||||||
*
|
|
||||||
* @returns {string} The log path.
|
|
||||||
*/
|
|
||||||
export const getAppLogPath = (): string => {
|
|
||||||
const appConfigurations = getAppConfigurations()
|
|
||||||
const logFolderPath = join(appConfigurations.data_folder, 'logs')
|
|
||||||
if (!fs.existsSync(logFolderPath)) {
|
|
||||||
fs.mkdirSync(logFolderPath, { recursive: true })
|
|
||||||
}
|
|
||||||
return join(logFolderPath, 'app.log')
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility function to get data folder path
|
* Utility function to get data folder path
|
||||||
*
|
*
|
||||||
@ -146,18 +118,6 @@ const exec = async (command: string): Promise<string> => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getSystemResourceInfo = async (): Promise<SystemResourceInfo> => {
|
|
||||||
const cpu = await physicalCpuCount()
|
|
||||||
const message = `[NITRO]::CPU informations - ${cpu}`
|
|
||||||
log(message)
|
|
||||||
logServer(message)
|
|
||||||
|
|
||||||
return {
|
|
||||||
numCpuPhysicalCore: cpu,
|
|
||||||
memAvailable: 0, // TODO: this should not be 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getEngineConfiguration = async (engineId: string) => {
|
export const getEngineConfiguration = async (engineId: string) => {
|
||||||
if (engineId !== 'openai') {
|
if (engineId !== 'openai') {
|
||||||
return undefined
|
return undefined
|
||||||
@ -167,3 +127,31 @@ export const getEngineConfiguration = async (engineId: string) => {
|
|||||||
const data = fs.readFileSync(filePath, 'utf-8')
|
const data = fs.readFileSync(filePath, 'utf-8')
|
||||||
return JSON.parse(data)
|
return JSON.parse(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility function to get server log path
|
||||||
|
*
|
||||||
|
* @returns {string} The log path.
|
||||||
|
*/
|
||||||
|
export const getServerLogPath = (): string => {
|
||||||
|
const appConfigurations = getAppConfigurations()
|
||||||
|
const logFolderPath = join(appConfigurations.data_folder, 'logs')
|
||||||
|
if (!fs.existsSync(logFolderPath)) {
|
||||||
|
fs.mkdirSync(logFolderPath, { recursive: true })
|
||||||
|
}
|
||||||
|
return join(logFolderPath, 'server.log')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility function to get app log path
|
||||||
|
*
|
||||||
|
* @returns {string} The log path.
|
||||||
|
*/
|
||||||
|
export const getAppLogPath = (): string => {
|
||||||
|
const appConfigurations = getAppConfigurations()
|
||||||
|
const logFolderPath = join(appConfigurations.data_folder, 'logs')
|
||||||
|
if (!fs.existsSync(logFolderPath)) {
|
||||||
|
fs.mkdirSync(logFolderPath, { recursive: true })
|
||||||
|
}
|
||||||
|
return join(logFolderPath, 'app.log')
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import { DownloadState } from '../types'
|
import { DownloadState } from '../../types'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages file downloads and network requests.
|
* Manages file downloads and network requests.
|
||||||
6
core/src/node/helper/index.ts
Normal file
6
core/src/node/helper/index.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export * from './config'
|
||||||
|
export * from './download'
|
||||||
|
export * from './log'
|
||||||
|
export * from './module'
|
||||||
|
export * from './path'
|
||||||
|
export * from './resource'
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import util from 'util'
|
import util from 'util'
|
||||||
import { getAppLogPath, getServerLogPath } from './utils'
|
import { getAppLogPath, getServerLogPath } from './config'
|
||||||
|
|
||||||
export const log = (message: string) => {
|
export const log = (message: string) => {
|
||||||
const path = getAppLogPath()
|
const path = getAppLogPath()
|
||||||
35
core/src/node/helper/path.ts
Normal file
35
core/src/node/helper/path.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { join } from "path"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize file path
|
||||||
|
* Remove all file protocol prefix
|
||||||
|
* @param path
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function normalizeFilePath(path: string): string {
|
||||||
|
return path.replace(/^(file:[\\/]+)([^:\s]+)$/, '$2')
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function appResourcePath(): Promise<string> {
|
||||||
|
let electron: any = undefined
|
||||||
|
|
||||||
|
try {
|
||||||
|
const moduleName = 'electron'
|
||||||
|
electron = await import(moduleName)
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Electron is not available')
|
||||||
|
}
|
||||||
|
|
||||||
|
// electron
|
||||||
|
if (electron && electron.protocol) {
|
||||||
|
let appPath = join(electron.app.getAppPath(), '..', 'app.asar.unpacked')
|
||||||
|
|
||||||
|
if (!electron.app.isPackaged) {
|
||||||
|
// for development mode
|
||||||
|
appPath = join(electron.app.getAppPath())
|
||||||
|
}
|
||||||
|
return appPath
|
||||||
|
}
|
||||||
|
// server
|
||||||
|
return join(global.core.appPath(), '../../..')
|
||||||
|
}
|
||||||
15
core/src/node/helper/resource.ts
Normal file
15
core/src/node/helper/resource.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { SystemResourceInfo } from "../../types"
|
||||||
|
import { physicalCpuCount } from "./config"
|
||||||
|
import { log, logServer } from "./log"
|
||||||
|
|
||||||
|
export const getSystemResourceInfo = async (): Promise<SystemResourceInfo> => {
|
||||||
|
const cpu = await physicalCpuCount()
|
||||||
|
const message = `[NITRO]::CPU informations - ${cpu}`
|
||||||
|
log(message)
|
||||||
|
logServer(message)
|
||||||
|
|
||||||
|
return {
|
||||||
|
numCpuPhysicalCore: cpu,
|
||||||
|
memAvailable: 0, // TODO: this should not be 0
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,9 +2,5 @@ export * from './extension/index'
|
|||||||
export * from './extension/extension'
|
export * from './extension/extension'
|
||||||
export * from './extension/manager'
|
export * from './extension/manager'
|
||||||
export * from './extension/store'
|
export * from './extension/store'
|
||||||
export * from './download'
|
|
||||||
export * from './module'
|
|
||||||
export * from './api'
|
export * from './api'
|
||||||
export * from './log'
|
export * from './helper'
|
||||||
export * from './utils'
|
|
||||||
export * from './path'
|
|
||||||
|
|||||||
@ -1,9 +0,0 @@
|
|||||||
/**
|
|
||||||
* Normalize file path
|
|
||||||
* Remove all file protocol prefix
|
|
||||||
* @param path
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export function normalizeFilePath(path: string): string {
|
|
||||||
return path.replace(/^(file:[\\/]+)([^:\s]+)$/, "$2");
|
|
||||||
}
|
|
||||||
@ -5,7 +5,7 @@ export type FileStat = {
|
|||||||
|
|
||||||
export type DownloadState = {
|
export type DownloadState = {
|
||||||
modelId: string
|
modelId: string
|
||||||
filename: string
|
fileName: string
|
||||||
time: DownloadTime
|
time: DownloadTime
|
||||||
speed: number
|
speed: number
|
||||||
percent: number
|
percent: number
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { normalizeFilePath } from "../../src/node/path";
|
import { normalizeFilePath } from "../../src/node/helper/path";
|
||||||
|
|
||||||
describe("Test file normalize", () => {
|
describe("Test file normalize", () => {
|
||||||
test("returns no file protocol prefix on Unix", async () => {
|
test("returns no file protocol prefix on Unix", async () => {
|
||||||
|
|||||||
@ -1,173 +0,0 @@
|
|||||||
import { app, ipcMain, dialog, shell } from 'electron'
|
|
||||||
import { join, basename, relative as getRelative, isAbsolute } from 'path'
|
|
||||||
import { WindowManager } from './../managers/window'
|
|
||||||
import { getResourcePath } from './../utils/path'
|
|
||||||
import { AppRoute, AppConfiguration } from '@janhq/core'
|
|
||||||
import { ServerConfig, startServer, stopServer } from '@janhq/server'
|
|
||||||
import {
|
|
||||||
ModuleManager,
|
|
||||||
getJanDataFolderPath,
|
|
||||||
getJanExtensionsPath,
|
|
||||||
init,
|
|
||||||
log,
|
|
||||||
logServer,
|
|
||||||
getAppConfigurations,
|
|
||||||
updateAppConfiguration,
|
|
||||||
} from '@janhq/core/node'
|
|
||||||
|
|
||||||
export function handleAppIPCs() {
|
|
||||||
/**
|
|
||||||
* Handles the "openAppDirectory" IPC message by opening the app's user data directory.
|
|
||||||
* 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(AppRoute.openAppDirectory, async (_event) => {
|
|
||||||
shell.openPath(getJanDataFolderPath())
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Opens a URL in the user's default browser.
|
|
||||||
* @param _event - The IPC event object.
|
|
||||||
* @param url - The URL to open.
|
|
||||||
*/
|
|
||||||
ipcMain.handle(AppRoute.openExternalUrl, async (_event, url) => {
|
|
||||||
shell.openExternal(url)
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Opens a URL in the user's default browser.
|
|
||||||
* @param _event - The IPC event object.
|
|
||||||
* @param url - The URL to open.
|
|
||||||
*/
|
|
||||||
ipcMain.handle(AppRoute.openFileExplore, async (_event, url) => {
|
|
||||||
shell.openPath(url)
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Joins multiple paths together, respect to the current OS.
|
|
||||||
*/
|
|
||||||
ipcMain.handle(AppRoute.joinPath, async (_event, paths: string[]) =>
|
|
||||||
join(...paths)
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the given path is a subdirectory of the given directory.
|
|
||||||
*
|
|
||||||
* @param _event - The IPC event object.
|
|
||||||
* @param from - The path to check.
|
|
||||||
* @param to - The directory to check against.
|
|
||||||
*
|
|
||||||
* @returns {Promise<boolean>} - A promise that resolves with the result.
|
|
||||||
*/
|
|
||||||
ipcMain.handle(
|
|
||||||
AppRoute.isSubdirectory,
|
|
||||||
async (_event, from: string, to: string) => {
|
|
||||||
const relative = getRelative(from, to)
|
|
||||||
const isSubdir =
|
|
||||||
relative && !relative.startsWith('..') && !isAbsolute(relative)
|
|
||||||
|
|
||||||
if (isSubdir === '') return false
|
|
||||||
else return isSubdir
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve basename from given path, respect to the current OS.
|
|
||||||
*/
|
|
||||||
ipcMain.handle(AppRoute.baseName, async (_event, path: string) =>
|
|
||||||
basename(path)
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start Jan API Server.
|
|
||||||
*/
|
|
||||||
ipcMain.handle(AppRoute.startServer, async (_event, configs?: ServerConfig) =>
|
|
||||||
startServer({
|
|
||||||
host: configs?.host,
|
|
||||||
port: configs?.port,
|
|
||||||
isCorsEnabled: configs?.isCorsEnabled,
|
|
||||||
isVerboseEnabled: configs?.isVerboseEnabled,
|
|
||||||
schemaPath: app.isPackaged
|
|
||||||
? join(getResourcePath(), 'docs', 'openapi', 'jan.yaml')
|
|
||||||
: undefined,
|
|
||||||
baseDir: app.isPackaged
|
|
||||||
? join(getResourcePath(), 'docs', 'openapi')
|
|
||||||
: undefined,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stop Jan API Server.
|
|
||||||
*/
|
|
||||||
ipcMain.handle(AppRoute.stopServer, stopServer)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Relaunches the app in production - reload window in development.
|
|
||||||
* @param _event - The IPC event object.
|
|
||||||
* @param url - The URL to reload.
|
|
||||||
*/
|
|
||||||
ipcMain.handle(AppRoute.relaunch, async (_event) => {
|
|
||||||
ModuleManager.instance.clearImportedModules()
|
|
||||||
|
|
||||||
if (app.isPackaged) {
|
|
||||||
app.relaunch()
|
|
||||||
app.exit()
|
|
||||||
} else {
|
|
||||||
for (const modulePath in ModuleManager.instance.requiredModules) {
|
|
||||||
delete require.cache[
|
|
||||||
require.resolve(join(getJanExtensionsPath(), modulePath))
|
|
||||||
]
|
|
||||||
}
|
|
||||||
init({
|
|
||||||
// Function to check from the main process that user wants to install a extension
|
|
||||||
confirmInstall: async (_extensions: string[]) => {
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
// Path to install extension to
|
|
||||||
extensionsPath: getJanExtensionsPath(),
|
|
||||||
})
|
|
||||||
WindowManager.instance.currentWindow?.reload()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Log message to log file.
|
|
||||||
*/
|
|
||||||
ipcMain.handle(AppRoute.log, async (_event, message) => log(message))
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Log message to log file.
|
|
||||||
*/
|
|
||||||
ipcMain.handle(AppRoute.logServer, async (_event, message) =>
|
|
||||||
logServer(message)
|
|
||||||
)
|
|
||||||
|
|
||||||
ipcMain.handle(AppRoute.selectDirectory, async () => {
|
|
||||||
const mainWindow = WindowManager.instance.currentWindow
|
|
||||||
if (!mainWindow) {
|
|
||||||
console.error('No main window found')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const { canceled, filePaths } = await dialog.showOpenDialog(mainWindow, {
|
|
||||||
title: 'Select a folder',
|
|
||||||
buttonLabel: 'Select Folder',
|
|
||||||
properties: ['openDirectory', 'createDirectory'],
|
|
||||||
})
|
|
||||||
if (canceled) {
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
return filePaths[0]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.handle(AppRoute.getAppConfigurations, async () =>
|
|
||||||
getAppConfigurations()
|
|
||||||
)
|
|
||||||
|
|
||||||
ipcMain.handle(
|
|
||||||
AppRoute.updateAppConfiguration,
|
|
||||||
async (_event, appConfiguration: AppConfiguration) => {
|
|
||||||
await updateAppConfiguration(appConfiguration)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
25
electron/handlers/common.ts
Normal file
25
electron/handlers/common.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { Handler, RequestHandler } from '@janhq/core/node'
|
||||||
|
import { ipcMain } from 'electron'
|
||||||
|
import { WindowManager } from '../managers/window'
|
||||||
|
|
||||||
|
export function injectHandler() {
|
||||||
|
const ipcWrapper: Handler = (
|
||||||
|
route: string,
|
||||||
|
listener: (...args: any[]) => any
|
||||||
|
) => {
|
||||||
|
return ipcMain.handle(route, async (event, ...args: any[]) => {
|
||||||
|
return listener(...args)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handler = new RequestHandler(
|
||||||
|
ipcWrapper,
|
||||||
|
(channel: string, args: any) => {
|
||||||
|
return WindowManager.instance.currentWindow?.webContents.send(
|
||||||
|
channel,
|
||||||
|
args
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
handler.handle()
|
||||||
|
}
|
||||||
@ -1,132 +0,0 @@
|
|||||||
import { ipcMain } from 'electron'
|
|
||||||
import { resolve, sep } from 'path'
|
|
||||||
import { WindowManager } from './../managers/window'
|
|
||||||
import request from 'request'
|
|
||||||
import { createWriteStream, renameSync } from 'fs'
|
|
||||||
import { DownloadEvent, DownloadRoute } from '@janhq/core'
|
|
||||||
const progress = require('request-progress')
|
|
||||||
import {
|
|
||||||
DownloadManager,
|
|
||||||
getJanDataFolderPath,
|
|
||||||
normalizeFilePath,
|
|
||||||
} from '@janhq/core/node'
|
|
||||||
|
|
||||||
export function handleDownloaderIPCs() {
|
|
||||||
/**
|
|
||||||
* Handles the "pauseDownload" IPC message by pausing the download associated with the provided fileName.
|
|
||||||
* @param _event - The IPC event object.
|
|
||||||
* @param fileName - The name of the file being downloaded.
|
|
||||||
*/
|
|
||||||
ipcMain.handle(DownloadRoute.pauseDownload, async (_event, fileName) => {
|
|
||||||
DownloadManager.instance.networkRequests[fileName]?.pause()
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles the "resumeDownload" IPC message by resuming the download associated with the provided fileName.
|
|
||||||
* @param _event - The IPC event object.
|
|
||||||
* @param fileName - The name of the file being downloaded.
|
|
||||||
*/
|
|
||||||
ipcMain.handle(DownloadRoute.resumeDownload, async (_event, fileName) => {
|
|
||||||
DownloadManager.instance.networkRequests[fileName]?.resume()
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles the "abortDownload" IPC message by aborting the download associated with the provided fileName.
|
|
||||||
* The network request associated with the fileName is then removed from the networkRequests object.
|
|
||||||
* @param _event - The IPC event object.
|
|
||||||
* @param fileName - The name of the file being downloaded.
|
|
||||||
*/
|
|
||||||
ipcMain.handle(DownloadRoute.abortDownload, async (_event, fileName) => {
|
|
||||||
const rq = DownloadManager.instance.networkRequests[fileName]
|
|
||||||
if (rq) {
|
|
||||||
DownloadManager.instance.networkRequests[fileName] = undefined
|
|
||||||
rq?.abort()
|
|
||||||
} else {
|
|
||||||
WindowManager?.instance.currentWindow?.webContents.send(
|
|
||||||
DownloadEvent.onFileDownloadError,
|
|
||||||
{
|
|
||||||
fileName,
|
|
||||||
error: 'aborted',
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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(
|
|
||||||
DownloadRoute.downloadFile,
|
|
||||||
async (_event, url, localPath, network) => {
|
|
||||||
const strictSSL = !network?.ignoreSSL
|
|
||||||
const proxy = network?.proxy?.startsWith('http')
|
|
||||||
? network.proxy
|
|
||||||
: undefined
|
|
||||||
if (typeof localPath === 'string') {
|
|
||||||
localPath = normalizeFilePath(localPath)
|
|
||||||
}
|
|
||||||
const array = localPath.split(sep)
|
|
||||||
const fileName = array.pop() ?? ''
|
|
||||||
const modelId = array.pop() ?? ''
|
|
||||||
|
|
||||||
const destination = resolve(getJanDataFolderPath(), localPath)
|
|
||||||
const rq = request({ url, strictSSL, proxy })
|
|
||||||
|
|
||||||
// Put request to download manager instance
|
|
||||||
DownloadManager.instance.setRequest(localPath, rq)
|
|
||||||
|
|
||||||
// Downloading file to a temp file first
|
|
||||||
const downloadingTempFile = `${destination}.download`
|
|
||||||
|
|
||||||
progress(rq, {})
|
|
||||||
.on('progress', function (state: any) {
|
|
||||||
WindowManager?.instance.currentWindow?.webContents.send(
|
|
||||||
DownloadEvent.onFileDownloadUpdate,
|
|
||||||
{
|
|
||||||
...state,
|
|
||||||
fileName,
|
|
||||||
modelId,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.on('error', function (error: Error) {
|
|
||||||
WindowManager?.instance.currentWindow?.webContents.send(
|
|
||||||
DownloadEvent.onFileDownloadError,
|
|
||||||
{
|
|
||||||
fileName,
|
|
||||||
modelId,
|
|
||||||
error,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.on('end', function () {
|
|
||||||
if (DownloadManager.instance.networkRequests[localPath]) {
|
|
||||||
// Finished downloading, rename temp file to actual file
|
|
||||||
renameSync(downloadingTempFile, destination)
|
|
||||||
|
|
||||||
WindowManager?.instance.currentWindow?.webContents.send(
|
|
||||||
DownloadEvent.onFileDownloadSuccess,
|
|
||||||
{
|
|
||||||
fileName,
|
|
||||||
modelId,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
DownloadManager.instance.setRequest(localPath, undefined)
|
|
||||||
} else {
|
|
||||||
WindowManager?.instance.currentWindow?.webContents.send(
|
|
||||||
DownloadEvent.onFileDownloadError,
|
|
||||||
{
|
|
||||||
fileName,
|
|
||||||
modelId,
|
|
||||||
error: 'aborted',
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.pipe(createWriteStream(downloadingTempFile))
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@ -1,104 +0,0 @@
|
|||||||
import { ipcMain, webContents } from 'electron'
|
|
||||||
import { readdirSync } from 'fs'
|
|
||||||
import { join, extname } from 'path'
|
|
||||||
|
|
||||||
import {
|
|
||||||
installExtensions,
|
|
||||||
getExtension,
|
|
||||||
removeExtension,
|
|
||||||
getActiveExtensions,
|
|
||||||
ModuleManager,
|
|
||||||
getJanExtensionsPath,
|
|
||||||
} from '@janhq/core/node'
|
|
||||||
|
|
||||||
import { getResourcePath } from './../utils/path'
|
|
||||||
import { ExtensionRoute } from '@janhq/core'
|
|
||||||
|
|
||||||
export function handleExtensionIPCs() {
|
|
||||||
/**MARK: General handlers */
|
|
||||||
/**
|
|
||||||
* Invokes a function from a extension module in main node process.
|
|
||||||
* @param _event - The IPC event object.
|
|
||||||
* @param modulePath - The path to the extension module.
|
|
||||||
* @param method - The name of the function to invoke.
|
|
||||||
* @param args - The arguments to pass to the function.
|
|
||||||
* @returns The result of the invoked function.
|
|
||||||
*/
|
|
||||||
ipcMain.handle(
|
|
||||||
ExtensionRoute.invokeExtensionFunc,
|
|
||||||
async (_event, modulePath, method, ...args) => {
|
|
||||||
const module = require(
|
|
||||||
/* webpackIgnore: true */ join(getJanExtensionsPath(), modulePath)
|
|
||||||
)
|
|
||||||
ModuleManager.instance.setModule(modulePath, module)
|
|
||||||
|
|
||||||
if (typeof module[method] === 'function') {
|
|
||||||
return module[method](...args)
|
|
||||||
} else {
|
|
||||||
console.debug(module[method])
|
|
||||||
console.error(`Function "${method}" does not exist in the module.`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the paths of the base extensions.
|
|
||||||
* @param _event - The IPC event object.
|
|
||||||
* @returns An array of paths to the base extensions.
|
|
||||||
*/
|
|
||||||
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))
|
|
||||||
})
|
|
||||||
|
|
||||||
/**MARK: Extension Manager handlers */
|
|
||||||
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(
|
|
||||||
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
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Register IPC route to update a extension
|
|
||||||
ipcMain.handle(
|
|
||||||
ExtensionRoute.updateExtension,
|
|
||||||
async (e, extensions, reload) => {
|
|
||||||
// Update all provided extensions
|
|
||||||
const updated: any[] = []
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Register IPC route to get the list of active extensions
|
|
||||||
ipcMain.handle(ExtensionRoute.getActiveExtensions, () => {
|
|
||||||
return JSON.parse(JSON.stringify(getActiveExtensions()))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@ -1,83 +0,0 @@
|
|||||||
import { ipcMain, app } from 'electron'
|
|
||||||
// @ts-ignore
|
|
||||||
import reflect from '@alumna/reflect'
|
|
||||||
|
|
||||||
import { FileManagerRoute, FileStat } from '@janhq/core'
|
|
||||||
import { getResourcePath } from './../utils/path'
|
|
||||||
import fs from 'fs'
|
|
||||||
import { join } from 'path'
|
|
||||||
import { getJanDataFolderPath, normalizeFilePath } from '@janhq/core/node'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles file system extensions operations.
|
|
||||||
*/
|
|
||||||
export function handleFileMangerIPCs() {
|
|
||||||
// Handles the 'syncFile' IPC event. This event is triggered to synchronize a file from a source path to a destination path.
|
|
||||||
ipcMain.handle(
|
|
||||||
FileManagerRoute.syncFile,
|
|
||||||
async (_event, src: string, dest: string) => {
|
|
||||||
return reflect({
|
|
||||||
src,
|
|
||||||
dest,
|
|
||||||
recursive: true,
|
|
||||||
delete: false,
|
|
||||||
overwrite: true,
|
|
||||||
errorOnExist: false,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Handles the 'getJanDataFolderPath' IPC event. This event is triggered to get the user space path.
|
|
||||||
ipcMain.handle(
|
|
||||||
FileManagerRoute.getJanDataFolderPath,
|
|
||||||
(): Promise<string> => Promise.resolve(getJanDataFolderPath())
|
|
||||||
)
|
|
||||||
|
|
||||||
// Handles the 'getResourcePath' IPC event. This event is triggered to get the resource path.
|
|
||||||
ipcMain.handle(FileManagerRoute.getResourcePath, async (_event) =>
|
|
||||||
getResourcePath()
|
|
||||||
)
|
|
||||||
|
|
||||||
// Handles the 'getUserHomePath' IPC event. This event is triggered to get the user home path.
|
|
||||||
ipcMain.handle(FileManagerRoute.getUserHomePath, async (_event) =>
|
|
||||||
app.getPath('home')
|
|
||||||
)
|
|
||||||
|
|
||||||
// handle fs is directory here
|
|
||||||
ipcMain.handle(
|
|
||||||
FileManagerRoute.fileStat,
|
|
||||||
async (_event, path: string): Promise<FileStat | undefined> => {
|
|
||||||
const normalizedPath = normalizeFilePath(path)
|
|
||||||
|
|
||||||
const fullPath = join(getJanDataFolderPath(), normalizedPath)
|
|
||||||
const isExist = fs.existsSync(fullPath)
|
|
||||||
if (!isExist) return undefined
|
|
||||||
|
|
||||||
const isDirectory = fs.lstatSync(fullPath).isDirectory()
|
|
||||||
const size = fs.statSync(fullPath).size
|
|
||||||
|
|
||||||
const fileStat: FileStat = {
|
|
||||||
isDirectory,
|
|
||||||
size,
|
|
||||||
}
|
|
||||||
|
|
||||||
return fileStat
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
ipcMain.handle(
|
|
||||||
FileManagerRoute.writeBlob,
|
|
||||||
async (_event, path: string, data: string): Promise<void> => {
|
|
||||||
try {
|
|
||||||
const normalizedPath = normalizeFilePath(path)
|
|
||||||
const dataBuffer = Buffer.from(data, 'base64')
|
|
||||||
fs.writeFileSync(
|
|
||||||
join(getJanDataFolderPath(), normalizedPath),
|
|
||||||
dataBuffer
|
|
||||||
)
|
|
||||||
} catch (err) {
|
|
||||||
console.error(`writeFile ${path} result: ${err}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
import { ipcMain } from 'electron'
|
|
||||||
|
|
||||||
import { getJanDataFolderPath, normalizeFilePath } from '@janhq/core/node'
|
|
||||||
import { FileSystemRoute } from '@janhq/core'
|
|
||||||
import { join } from 'path'
|
|
||||||
/**
|
|
||||||
* Handles file system operations.
|
|
||||||
*/
|
|
||||||
export function handleFsIPCs() {
|
|
||||||
const moduleName = 'fs'
|
|
||||||
Object.values(FileSystemRoute).forEach((route) => {
|
|
||||||
ipcMain.handle(route, async (event, ...args) => {
|
|
||||||
return import(moduleName).then((mdl) =>
|
|
||||||
mdl[route](
|
|
||||||
...args.map((arg) =>
|
|
||||||
typeof arg === 'string' &&
|
|
||||||
(arg.startsWith(`file:/`) || arg.startsWith(`file:\\`))
|
|
||||||
? join(getJanDataFolderPath(), normalizeFilePath(arg))
|
|
||||||
: arg
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
86
electron/handlers/native.ts
Normal file
86
electron/handlers/native.ts
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
import { app, ipcMain, dialog, shell } from 'electron'
|
||||||
|
import { join } from 'path'
|
||||||
|
import { WindowManager } from '../managers/window'
|
||||||
|
import {
|
||||||
|
ModuleManager,
|
||||||
|
getJanDataFolderPath,
|
||||||
|
getJanExtensionsPath,
|
||||||
|
init,
|
||||||
|
} from '@janhq/core/node'
|
||||||
|
import { NativeRoute } from '@janhq/core'
|
||||||
|
|
||||||
|
export function handleAppIPCs() {
|
||||||
|
/**
|
||||||
|
* Handles the "openAppDirectory" IPC message by opening the app's user data directory.
|
||||||
|
* 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(NativeRoute.openAppDirectory, async (_event) => {
|
||||||
|
shell.openPath(getJanDataFolderPath())
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens a URL in the user's default browser.
|
||||||
|
* @param _event - The IPC event object.
|
||||||
|
* @param url - The URL to open.
|
||||||
|
*/
|
||||||
|
ipcMain.handle(NativeRoute.openExternalUrl, async (_event, url) => {
|
||||||
|
shell.openExternal(url)
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens a URL in the user's default browser.
|
||||||
|
* @param _event - The IPC event object.
|
||||||
|
* @param url - The URL to open.
|
||||||
|
*/
|
||||||
|
ipcMain.handle(NativeRoute.openFileExplore, async (_event, url) => {
|
||||||
|
shell.openPath(url)
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Relaunches the app in production - reload window in development.
|
||||||
|
* @param _event - The IPC event object.
|
||||||
|
* @param url - The URL to reload.
|
||||||
|
*/
|
||||||
|
ipcMain.handle(NativeRoute.relaunch, async (_event) => {
|
||||||
|
ModuleManager.instance.clearImportedModules()
|
||||||
|
|
||||||
|
if (app.isPackaged) {
|
||||||
|
app.relaunch()
|
||||||
|
app.exit()
|
||||||
|
} else {
|
||||||
|
for (const modulePath in ModuleManager.instance.requiredModules) {
|
||||||
|
delete require.cache[
|
||||||
|
require.resolve(join(getJanExtensionsPath(), modulePath))
|
||||||
|
]
|
||||||
|
}
|
||||||
|
init({
|
||||||
|
// Function to check from the main process that user wants to install a extension
|
||||||
|
confirmInstall: async (_extensions: string[]) => {
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
// Path to install extension to
|
||||||
|
extensionsPath: getJanExtensionsPath(),
|
||||||
|
})
|
||||||
|
WindowManager.instance.currentWindow?.reload()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcMain.handle(NativeRoute.selectDirectory, async () => {
|
||||||
|
const mainWindow = WindowManager.instance.currentWindow
|
||||||
|
if (!mainWindow) {
|
||||||
|
console.error('No main window found')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const { canceled, filePaths } = await dialog.showOpenDialog(mainWindow, {
|
||||||
|
title: 'Select a folder',
|
||||||
|
buttonLabel: 'Select Folder',
|
||||||
|
properties: ['openDirectory', 'createDirectory'],
|
||||||
|
})
|
||||||
|
if (canceled) {
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
return filePaths[0]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@ -9,12 +9,9 @@ import { log } from '@janhq/core/node'
|
|||||||
/**
|
/**
|
||||||
* IPC Handlers
|
* IPC Handlers
|
||||||
**/
|
**/
|
||||||
import { handleDownloaderIPCs } from './handlers/download'
|
import { injectHandler } from './handlers/common'
|
||||||
import { handleExtensionIPCs } from './handlers/extension'
|
|
||||||
import { handleFileMangerIPCs } from './handlers/fileManager'
|
|
||||||
import { handleAppIPCs } from './handlers/app'
|
|
||||||
import { handleAppUpdates } from './handlers/update'
|
import { handleAppUpdates } from './handlers/update'
|
||||||
import { handleFsIPCs } from './handlers/fs'
|
import { handleAppIPCs } from './handlers/native'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utils
|
* Utils
|
||||||
@ -92,11 +89,11 @@ function createMainWindow() {
|
|||||||
* Handles various IPC messages from the renderer process.
|
* Handles various IPC messages from the renderer process.
|
||||||
*/
|
*/
|
||||||
function handleIPCs() {
|
function handleIPCs() {
|
||||||
handleFsIPCs()
|
// Inject core handlers for IPCs
|
||||||
handleDownloaderIPCs()
|
injectHandler()
|
||||||
handleExtensionIPCs()
|
|
||||||
|
// Handle native IPCs
|
||||||
handleAppIPCs()
|
handleAppIPCs()
|
||||||
handleFileMangerIPCs()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import { app, Menu, dialog, shell } from 'electron'
|
import { app, Menu, shell } from 'electron'
|
||||||
const isMac = process.platform === 'darwin'
|
const isMac = process.platform === 'darwin'
|
||||||
import { autoUpdater } from 'electron-updater'
|
import { autoUpdater } from 'electron-updater'
|
||||||
import { compareSemanticVersions } from './versionDiff'
|
|
||||||
|
|
||||||
const template: (Electron.MenuItemConstructorOptions | Electron.MenuItem)[] = [
|
const template: (Electron.MenuItemConstructorOptions | Electron.MenuItem)[] = [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
import { join } from 'path'
|
|
||||||
import { app } from 'electron'
|
|
||||||
import { mkdir } from 'fs-extra'
|
import { mkdir } from 'fs-extra'
|
||||||
import { existsSync } from 'fs'
|
import { existsSync } from 'fs'
|
||||||
import { getJanDataFolderPath } from '@janhq/core/node'
|
import { getJanDataFolderPath } from '@janhq/core/node'
|
||||||
@ -16,13 +14,3 @@ export async function createUserSpace(): Promise<void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getResourcePath() {
|
|
||||||
let appPath = join(app.getAppPath(), '..', 'app.asar.unpacked')
|
|
||||||
|
|
||||||
if (!app.isPackaged) {
|
|
||||||
// for development mode
|
|
||||||
appPath = join(__dirname, '..', '..')
|
|
||||||
}
|
|
||||||
return appPath
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,21 +0,0 @@
|
|||||||
export const compareSemanticVersions = (a: string, b: string) => {
|
|
||||||
|
|
||||||
// 1. Split the strings into their parts.
|
|
||||||
const a1 = a.split('.');
|
|
||||||
const b1 = b.split('.');
|
|
||||||
// 2. Contingency in case there's a 4th or 5th version
|
|
||||||
const len = Math.min(a1.length, b1.length);
|
|
||||||
// 3. Look through each version number and compare.
|
|
||||||
for (let i = 0; i < len; i++) {
|
|
||||||
const a2 = +a1[ i ] || 0;
|
|
||||||
const b2 = +b1[ i ] || 0;
|
|
||||||
|
|
||||||
if (a2 !== b2) {
|
|
||||||
return a2 > b2 ? 1 : -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. We hit this if the all checked versions so far are equal
|
|
||||||
//
|
|
||||||
return b1.length - a1.length;
|
|
||||||
};
|
|
||||||
@ -1,6 +1,7 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import {
|
import {
|
||||||
AppRoute,
|
AppRoute,
|
||||||
|
CoreRoutes,
|
||||||
DownloadRoute,
|
DownloadRoute,
|
||||||
ExtensionRoute,
|
ExtensionRoute,
|
||||||
FileManagerRoute,
|
FileManagerRoute,
|
||||||
@ -15,16 +16,7 @@ export function openExternalUrl(url: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Define API routes based on different route types
|
// Define API routes based on different route types
|
||||||
export const APIRoutes = [
|
export const APIRoutes = [...CoreRoutes.map((r) => ({ path: `app`, route: r }))]
|
||||||
...Object.values(AppRoute).map((r) => ({ path: 'app', route: r })),
|
|
||||||
...Object.values(DownloadRoute).map((r) => ({ path: `download`, route: r })),
|
|
||||||
...Object.values(ExtensionRoute).map((r) => ({
|
|
||||||
path: `extension`,
|
|
||||||
route: r,
|
|
||||||
})),
|
|
||||||
...Object.values(FileSystemRoute).map((r) => ({ path: `fs`, route: r })),
|
|
||||||
...Object.values(FileManagerRoute).map((r) => ({ path: `fs`, route: r })),
|
|
||||||
]
|
|
||||||
|
|
||||||
// Define the restAPI object with methods for each API route
|
// Define the restAPI object with methods for each API route
|
||||||
export const restAPI = {
|
export const restAPI = {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user