From 12b037e2cb8635f0ff9a41c51d7e97c2ba415dd7 Mon Sep 17 00:00:00 2001 From: Louis Date: Tue, 2 Jan 2024 12:48:00 +0700 Subject: [PATCH] fix: swagger CSP issue (#1284) --- .gitignore | 1 + README.md | 18 ++-- core/src/api/index.ts | 2 + electron/docs/openapi/.gitkeep | 0 electron/handlers/app.ts | 20 +++- electron/main.ts | 6 -- electron/package.json | 6 +- package.json | 9 +- server/index.ts | 125 +++++++++++++++--------- web/screens/Settings/Advanced/index.tsx | 30 +++++- 10 files changed, 148 insertions(+), 69 deletions(-) create mode 100644 electron/docs/openapi/.gitkeep diff --git a/.gitignore b/.gitignore index ba5983d2e..dbf94335a 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ build .DS_Store electron/renderer electron/models +electron/docs package-lock.json *.log diff --git a/README.md b/README.md index aae55a87d..9350daa46 100644 --- a/README.md +++ b/README.md @@ -148,19 +148,19 @@ Contributions are welcome! Please read the [CONTRIBUTING.md](CONTRIBUTING.md) fi 1. **Clone the repository and prepare:** -```bash - git clone https://github.com/janhq/jan - cd jan - git checkout -b DESIRED_BRANCH -``` + ```bash + git clone https://github.com/janhq/jan + cd jan + git checkout -b DESIRED_BRANCH + ``` 2. **Run development and use Jan Desktop** - ``` - make dev - ``` + ```bash + make dev + ``` - This will start the development server and open the desktop app. +This will start the development server and open the desktop app. ### For production build diff --git a/core/src/api/index.ts b/core/src/api/index.ts index 3cf2693e7..58b94f38b 100644 --- a/core/src/api/index.ts +++ b/core/src/api/index.ts @@ -11,6 +11,8 @@ export enum AppRoute { relaunch = 'relaunch', joinPath = 'joinPath', baseName = 'baseName', + startServer = 'startServer', + stopServer = 'stopServer', } export enum AppEvent { diff --git a/electron/docs/openapi/.gitkeep b/electron/docs/openapi/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/electron/handlers/app.ts b/electron/handlers/app.ts index 726ed612e..2cbc13848 100644 --- a/electron/handlers/app.ts +++ b/electron/handlers/app.ts @@ -1,9 +1,10 @@ import { app, ipcMain, shell, nativeTheme } from 'electron' import { join, basename } from 'path' import { WindowManager } from './../managers/window' -import { userSpacePath } from './../utils/path' +import { getResourcePath, userSpacePath } from './../utils/path' import { AppRoute } from '@janhq/core' import { ExtensionManager, ModuleManager } from '@janhq/core/node' +import { startServer, stopServer } from '@janhq/server' export function handleAppIPCs() { /** @@ -56,6 +57,23 @@ export function handleAppIPCs() { basename(path) ) + /** + * Start Jan API Server. + */ + ipcMain.handle(AppRoute.startServer, async (_event) => + startServer( + app.isPackaged + ? join(getResourcePath(), 'docs', 'openapi', 'jan.yaml') + : undefined, + app.isPackaged ? join(getResourcePath(), 'docs', 'openapi') : undefined + ) + ) + + /** + * Stop Jan API Server. + */ + ipcMain.handle(AppRoute.stopServer, async (_event) => stopServer()) + /** * Relaunches the app in production - reload window in development. * @param _event - The IPC event object. diff --git a/electron/main.ts b/electron/main.ts index fae3a1ffa..6eaa0acfe 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -20,11 +20,6 @@ import { handleAppUpdates } from './handlers/update' import { handleFsIPCs } from './handlers/fs' import { migrateExtensions } from './utils/migration' -/** - * Server - */ -import { startServer } from '@janhq/server' - app .whenReady() .then(createUserSpace) @@ -34,7 +29,6 @@ app .then(handleIPCs) .then(handleAppUpdates) .then(createMainWindow) - .then(startServer) .then(() => { app.on('activate', () => { if (!BrowserWindow.getAllWindows().length) { diff --git a/electron/package.json b/electron/package.json index 3cc5e7680..af98aa3b8 100644 --- a/electron/package.json +++ b/electron/package.json @@ -14,11 +14,13 @@ "build/*.{js,map}", "build/**/*.{js,map}", "pre-install", - "models/**/*" + "models/**/*", + "docs/**/*" ], "asarUnpack": [ "pre-install", - "models" + "models", + "docs" ], "publish": [ { diff --git a/package.json b/package.json index e4a1cae89..4b9492e3d 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,8 @@ "scripts": { "lint": "yarn workspace jan lint && yarn workspace jan-web lint", "test": "yarn workspace jan test:e2e", - "dev:electron": "cpx \"models/**\" \"electron/models/\" && yarn workspace jan dev", + "copy:assets": "cpx \"models/**\" \"electron/models/\" && cpx \"docs/openapi/**\" \"electron/docs/openapi\"", + "dev:electron": "yarn copy:assets && yarn workspace jan dev", "dev:web": "yarn workspace jan-web dev", "dev": "concurrently --kill-others \"yarn dev:web\" \"wait-on http://localhost:3000 && yarn dev:electron\"", "test-local": "yarn lint && yarn build:test && yarn test", @@ -34,15 +35,15 @@ "build:server": "cd server && yarn install && yarn run build", "build:core": "cd core && yarn install && yarn run build", "build:web": "yarn workspace jan-web build && cpx \"web/out/**\" \"electron/renderer/\"", - "build:electron": "cpx \"models/**\" \"electron/models/\" && yarn workspace jan build", + "build:electron": "yarn copy:assets && yarn workspace jan build", "build:electron:test": "yarn workspace jan build:test", "build:extensions:windows": "rimraf ./electron/pre-install/*.tgz && powershell -command \"$jobs = Get-ChildItem -Path './extensions' -Directory | ForEach-Object { Start-Job -Name ($_.Name) -ScriptBlock { param($_dir); try { Set-Location $_dir; npm install; npm run build:publish; Write-Output 'Build successful in ' + $_dir } catch { Write-Error 'Error in ' + $_dir; throw } } -ArgumentList $_.FullName }; $jobs | Wait-Job; $jobs | ForEach-Object { Receive-Job -Job $_ -Keep } | ForEach-Object { Write-Host $_ }; $failed = $jobs | Where-Object { $_.State -ne 'Completed' -or $_.ChildJobs[0].JobStateInfo.State -ne 'Completed' }; if ($failed) { Exit 1 }\"", "build:extensions:linux": "rimraf ./electron/pre-install/*.tgz && find ./extensions -mindepth 1 -maxdepth 1 -type d -print0 | xargs -0 -n 1 -P 4 -I {} sh -c 'cd {} && npm install && npm run build:publish'", "build:extensions:darwin": "rimraf ./electron/pre-install/*.tgz && find ./extensions -mindepth 1 -maxdepth 1 -type d -print0 | xargs -0 -n 1 -P 4 -I {} sh -c 'cd {} && npm install && npm run build:publish'", "build:extensions": "run-script-os", - "build:test": "yarn build:web && yarn workspace jan build:test", + "build:test": "yarn copy:assets && yarn build:web && yarn workspace jan build:test", "build": "yarn build:web && yarn build:electron", - "build:publish": "cpx \"models/**\" \"electron/models/\" && yarn build:web && yarn workspace jan build:publish" + "build:publish": "yarn copy:assets && yarn build:web && yarn workspace jan build:publish" }, "devDependencies": { "concurrently": "^8.2.1", diff --git a/server/index.ts b/server/index.ts index bc031305e..3d6ede634 100644 --- a/server/index.ts +++ b/server/index.ts @@ -2,59 +2,92 @@ import fastify from "fastify"; import dotenv from "dotenv"; import { v1Router } from "@janhq/core/node"; import path from "path"; +import fs from "fs"; +import util from "util"; +import os from "os"; dotenv.config(); -const JAN_API_HOST = process.env.JAN_API_HOST || "0.0.0.0"; +const JAN_API_HOST = process.env.JAN_API_HOST || "127.0.0.1"; const JAN_API_PORT = Number.parseInt(process.env.JAN_API_PORT || "1337"); +const serverLogPath = path.join(os.homedir(), "jan", "server.log"); -const server = fastify(); -server.register(require("@fastify/cors"), {}); -server.register(require("@fastify/swagger"), { - mode: "static", - specification: { - path: "./../docs/openapi/jan.yaml", - baseDir: "./../docs/openapi", - }, +let server: any | undefined = undefined; + +var log_file = fs.createWriteStream(serverLogPath, { + flags: "a", }); -server.register(require("@fastify/swagger-ui"), { - routePrefix: "/docs", - baseDir: path.join(__dirname, "../..", "./docs/openapi"), - uiConfig: { - docExpansion: "full", - deepLinking: false, - }, - staticCSP: true, - transformSpecificationClone: true, -}); -server.register( - (childContext, _, done) => { - childContext.register(require("@fastify/static"), { - root: - process.env.EXTENSION_ROOT || - path.join(require("os").homedir(), "jan", "extensions"), - wildcard: false, - }); +var log_stdout = process.stdout; +var log_stderr = process.stderr; - done(); - }, - { prefix: "extensions" } -); -server.register(v1Router, { prefix: "/v1" }); - -export const startServer = () => { - server - .listen({ - port: JAN_API_PORT, - host: JAN_API_HOST, - }) - .then(() => { - console.log( - `JAN API listening at: http://${JAN_API_HOST}:${JAN_API_PORT}` - ); - }); +const logServer = function (d: any) { + log_file.write(util.format(d) + "\n"); + log_stdout.write(util.format(d) + "\n"); + log_stderr.write(util.format(d) + "\n"); }; -export const stopServer = () => { - server.close(); +export const startServer = async (schemaPath?: string, baseDir?: string) => { + try { + server = fastify({ + logger: { + level: "info", + file: serverLogPath, + }, + }); + await server.register(require("@fastify/cors"), {}); + + await server.register(require("@fastify/swagger"), { + mode: "static", + specification: { + path: schemaPath ?? "./../docs/openapi/jan.yaml", + baseDir: baseDir ?? "./../docs/openapi", + }, + }); + + await server.register(require("@fastify/swagger-ui"), { + routePrefix: "/docs", + baseDir: baseDir ?? path.join(__dirname, "../..", "./docs/openapi"), + uiConfig: { + docExpansion: "full", + deepLinking: false, + }, + staticCSP: false, + transformSpecificationClone: true, + }); + + await server.register( + (childContext: any, _: any, done: any) => { + childContext.register(require("@fastify/static"), { + root: + process.env.EXTENSION_ROOT || + path.join(require("os").homedir(), "jan", "extensions"), + wildcard: false, + }); + + done(); + }, + { prefix: "extensions" } + ); + await server.register(v1Router, { prefix: "/v1" }); + await server + .listen({ + port: JAN_API_PORT, + host: JAN_API_HOST, + }) + .then(() => { + logServer( + `JAN API listening at: http://${JAN_API_HOST}:${JAN_API_PORT}` + ); + }); + } catch (e) { + logServer(e); + } +}; + +export const stopServer = async () => { + try { + await server.close(); + } catch (e) { + logServer(e); + } }; diff --git a/web/screens/Settings/Advanced/index.tsx b/web/screens/Settings/Advanced/index.tsx index 1a40b52c6..f0b58b984 100644 --- a/web/screens/Settings/Advanced/index.tsx +++ b/web/screens/Settings/Advanced/index.tsx @@ -11,19 +11,23 @@ import { ModalHeader, ModalTitle, ModalTrigger, - Badge, } from '@janhq/uikit' +import { atom, useAtom } from 'jotai' + import ShortCut from '@/containers/Shortcut' import { FeatureToggleContext } from '@/context/FeatureToggle' import { useSettings } from '@/hooks/useSettings' +const serverEnabledAtom = atom(false) + const Advanced = () => { const { experimentalFeatureEnabed, setExperimentalFeatureEnabled } = useContext(FeatureToggleContext) const [gpuEnabled, setGpuEnabled] = useState(false) + const [serverEnabled, setServerEnabled] = useAtom(serverEnabledAtom) const { readSettings, saveSettings, validateSettings, setShowNotification } = useSettings() @@ -87,6 +91,30 @@ const Advanced = () => { }} /> + {/* Server */} +
+
+
+
+ Enable API Server +
+
+

+ Enable API server for Jan app. +

+
+ { + if (e === true) { + window.core?.api?.startServer() + } else { + window.core?.api?.stopServer() + } + setServerEnabled(e) + }} + /> +
{window.electronAPI && (