Skip to content

Commit

Permalink
refactor: reduce IPC & API handlers - shared node logics (janhq#2011)
Browse files Browse the repository at this point in the history
  • Loading branch information
louis-jan authored Feb 14, 2024
1 parent 8a1e3ba commit 5f95841
Show file tree
Hide file tree
Showing 51 changed files with 789 additions and 965 deletions.
3 changes: 2 additions & 1 deletion core/rollup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ export default [
'url',
'http',
'os',
'util'
'util',
'child_process'
],
watch: {
include: 'src/node/**',
Expand Down
27 changes: 22 additions & 5 deletions core/src/api/index.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
/**
* App Route APIs
* Native Route APIs
* @description Enum of all the routes exposed by the app
*/
export enum AppRoute {
export enum NativeRoute {
openExternalUrl = 'openExternalUrl',
openAppDirectory = 'openAppDirectory',
openFileExplore = 'openFileExplorer',
selectDirectory = 'selectDirectory',
relaunch = 'relaunch',
}

/**
* App Route APIs
* @description Enum of all the routes exposed by the app
*/
export enum AppRoute {
getAppConfigurations = 'getAppConfigurations',
updateAppConfiguration = 'updateAppConfiguration',
relaunch = 'relaunch',
joinPath = 'joinPath',
isSubdirectory = 'isSubdirectory',
baseName = 'baseName',
Expand Down Expand Up @@ -69,6 +76,10 @@ export enum FileManagerRoute {

export type ApiFunction = (...args: any[]) => any

export type NativeRouteFunctions = {
[K in NativeRoute]: ApiFunction
}

export type AppRouteFunctions = {
[K in AppRoute]: ApiFunction
}
Expand Down Expand Up @@ -97,19 +108,25 @@ export type FileManagerRouteFunctions = {
[K in FileManagerRoute]: ApiFunction
}

export type APIFunctions = AppRouteFunctions &
export type APIFunctions = NativeRouteFunctions &
AppRouteFunctions &
AppEventFunctions &
DownloadRouteFunctions &
DownloadEventFunctions &
ExtensionRouteFunctions &
FileSystemRouteFunctions &
FileManagerRoute

export const APIRoutes = [
export const CoreRoutes = [
...Object.values(AppRoute),
...Object.values(DownloadRoute),
...Object.values(ExtensionRoute),
...Object.values(FileSystemRoute),
...Object.values(FileManagerRoute),
]

export const APIRoutes = [
...CoreRoutes,
...Object.values(NativeRoute),
]
export const APIEvents = [...Object.values(AppEvent), ...Object.values(DownloadEvent)]
7 changes: 7 additions & 0 deletions core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,10 @@ export * from './extension'
* @module
*/
export * from './extensions/index'

/**
* Declare global object
*/
declare global {
var core: any | undefined
}
37 changes: 37 additions & 0 deletions core/src/node/api/common/adapter.ts
Original file line number Diff line number Diff line change
@@ -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 changes: 23 additions & 0 deletions core/src/node/api/common/handler.ts
Original file line number Diff line number Diff line change
@@ -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
})
})
}
}
3 changes: 2 additions & 1 deletion core/src/node/api/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './HttpServer'
export * from './routes'
export * from './restful/v1'
export * from './common/handler'
3 changes: 3 additions & 0 deletions core/src/node/api/processors/Processor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export abstract class Processor {
abstract process(key: string, ...args: any[]): any
}
97 changes: 97 additions & 0 deletions core/src/node/api/processors/app.ts
Original file line number Diff line number Diff line change
@@ -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 changes: 105 additions & 0 deletions core/src/node/api/processors/download.ts
Original file line number Diff line number Diff line change
@@ -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()
}
}
Loading

0 comments on commit 5f95841

Please sign in to comment.