fix: swagger CSP issue (#1284)

This commit is contained in:
Louis 2024-01-02 12:48:00 +07:00 committed by GitHub
parent 32a82ad565
commit 12b037e2cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 148 additions and 69 deletions

1
.gitignore vendored
View File

@ -11,6 +11,7 @@ build
.DS_Store .DS_Store
electron/renderer electron/renderer
electron/models electron/models
electron/docs
package-lock.json package-lock.json
*.log *.log

View File

@ -148,19 +148,19 @@ Contributions are welcome! Please read the [CONTRIBUTING.md](CONTRIBUTING.md) fi
1. **Clone the repository and prepare:** 1. **Clone the repository and prepare:**
```bash ```bash
git clone https://github.com/janhq/jan git clone https://github.com/janhq/jan
cd jan cd jan
git checkout -b DESIRED_BRANCH git checkout -b DESIRED_BRANCH
``` ```
2. **Run development and use Jan Desktop** 2. **Run development and use Jan Desktop**
``` ```bash
make dev 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 ### For production build

View File

@ -11,6 +11,8 @@ export enum AppRoute {
relaunch = 'relaunch', relaunch = 'relaunch',
joinPath = 'joinPath', joinPath = 'joinPath',
baseName = 'baseName', baseName = 'baseName',
startServer = 'startServer',
stopServer = 'stopServer',
} }
export enum AppEvent { export enum AppEvent {

View File

View File

@ -1,9 +1,10 @@
import { app, ipcMain, shell, nativeTheme } from 'electron' import { app, ipcMain, shell, nativeTheme } from 'electron'
import { join, basename } from 'path' import { join, basename } from 'path'
import { WindowManager } from './../managers/window' import { WindowManager } from './../managers/window'
import { userSpacePath } from './../utils/path' import { getResourcePath, userSpacePath } from './../utils/path'
import { AppRoute } from '@janhq/core' import { AppRoute } from '@janhq/core'
import { ExtensionManager, ModuleManager } from '@janhq/core/node' import { ExtensionManager, ModuleManager } from '@janhq/core/node'
import { startServer, stopServer } from '@janhq/server'
export function handleAppIPCs() { export function handleAppIPCs() {
/** /**
@ -56,6 +57,23 @@ export function handleAppIPCs() {
basename(path) 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. * Relaunches the app in production - reload window in development.
* @param _event - The IPC event object. * @param _event - The IPC event object.

View File

@ -20,11 +20,6 @@ import { handleAppUpdates } from './handlers/update'
import { handleFsIPCs } from './handlers/fs' import { handleFsIPCs } from './handlers/fs'
import { migrateExtensions } from './utils/migration' import { migrateExtensions } from './utils/migration'
/**
* Server
*/
import { startServer } from '@janhq/server'
app app
.whenReady() .whenReady()
.then(createUserSpace) .then(createUserSpace)
@ -34,7 +29,6 @@ app
.then(handleIPCs) .then(handleIPCs)
.then(handleAppUpdates) .then(handleAppUpdates)
.then(createMainWindow) .then(createMainWindow)
.then(startServer)
.then(() => { .then(() => {
app.on('activate', () => { app.on('activate', () => {
if (!BrowserWindow.getAllWindows().length) { if (!BrowserWindow.getAllWindows().length) {

View File

@ -14,11 +14,13 @@
"build/*.{js,map}", "build/*.{js,map}",
"build/**/*.{js,map}", "build/**/*.{js,map}",
"pre-install", "pre-install",
"models/**/*" "models/**/*",
"docs/**/*"
], ],
"asarUnpack": [ "asarUnpack": [
"pre-install", "pre-install",
"models" "models",
"docs"
], ],
"publish": [ "publish": [
{ {

View File

@ -25,7 +25,8 @@
"scripts": { "scripts": {
"lint": "yarn workspace jan lint && yarn workspace jan-web lint", "lint": "yarn workspace jan lint && yarn workspace jan-web lint",
"test": "yarn workspace jan test:e2e", "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:web": "yarn workspace jan-web dev",
"dev": "concurrently --kill-others \"yarn dev:web\" \"wait-on http://localhost:3000 && yarn dev:electron\"", "dev": "concurrently --kill-others \"yarn dev:web\" \"wait-on http://localhost:3000 && yarn dev:electron\"",
"test-local": "yarn lint && yarn build:test && yarn test", "test-local": "yarn lint && yarn build:test && yarn test",
@ -34,15 +35,15 @@
"build:server": "cd server && yarn install && yarn run build", "build:server": "cd server && yarn install && yarn run build",
"build:core": "cd core && 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: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: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: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: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: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: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": "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": { "devDependencies": {
"concurrently": "^8.2.1", "concurrently": "^8.2.1",

View File

@ -2,33 +2,61 @@ import fastify from "fastify";
import dotenv from "dotenv"; import dotenv from "dotenv";
import { v1Router } from "@janhq/core/node"; import { v1Router } from "@janhq/core/node";
import path from "path"; import path from "path";
import fs from "fs";
import util from "util";
import os from "os";
dotenv.config(); 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 JAN_API_PORT = Number.parseInt(process.env.JAN_API_PORT || "1337");
const serverLogPath = path.join(os.homedir(), "jan", "server.log");
const server = fastify(); let server: any | undefined = undefined;
server.register(require("@fastify/cors"), {});
server.register(require("@fastify/swagger"), { var log_file = fs.createWriteStream(serverLogPath, {
flags: "a",
});
var log_stdout = process.stdout;
var log_stderr = process.stderr;
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 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", mode: "static",
specification: { specification: {
path: "./../docs/openapi/jan.yaml", path: schemaPath ?? "./../docs/openapi/jan.yaml",
baseDir: "./../docs/openapi", baseDir: baseDir ?? "./../docs/openapi",
}, },
}); });
server.register(require("@fastify/swagger-ui"), {
await server.register(require("@fastify/swagger-ui"), {
routePrefix: "/docs", routePrefix: "/docs",
baseDir: path.join(__dirname, "../..", "./docs/openapi"), baseDir: baseDir ?? path.join(__dirname, "../..", "./docs/openapi"),
uiConfig: { uiConfig: {
docExpansion: "full", docExpansion: "full",
deepLinking: false, deepLinking: false,
}, },
staticCSP: true, staticCSP: false,
transformSpecificationClone: true, transformSpecificationClone: true,
}); });
server.register(
(childContext, _, done) => { await server.register(
(childContext: any, _: any, done: any) => {
childContext.register(require("@fastify/static"), { childContext.register(require("@fastify/static"), {
root: root:
process.env.EXTENSION_ROOT || process.env.EXTENSION_ROOT ||
@ -39,22 +67,27 @@ server.register(
done(); done();
}, },
{ prefix: "extensions" } { prefix: "extensions" }
); );
server.register(v1Router, { prefix: "/v1" }); await server.register(v1Router, { prefix: "/v1" });
await server
export const startServer = () => {
server
.listen({ .listen({
port: JAN_API_PORT, port: JAN_API_PORT,
host: JAN_API_HOST, host: JAN_API_HOST,
}) })
.then(() => { .then(() => {
console.log( logServer(
`JAN API listening at: http://${JAN_API_HOST}:${JAN_API_PORT}` `JAN API listening at: http://${JAN_API_HOST}:${JAN_API_PORT}`
); );
}); });
} catch (e) {
logServer(e);
}
}; };
export const stopServer = () => { export const stopServer = async () => {
server.close(); try {
await server.close();
} catch (e) {
logServer(e);
}
}; };

View File

@ -11,19 +11,23 @@ import {
ModalHeader, ModalHeader,
ModalTitle, ModalTitle,
ModalTrigger, ModalTrigger,
Badge,
} from '@janhq/uikit' } from '@janhq/uikit'
import { atom, useAtom } from 'jotai'
import ShortCut from '@/containers/Shortcut' import ShortCut from '@/containers/Shortcut'
import { FeatureToggleContext } from '@/context/FeatureToggle' import { FeatureToggleContext } from '@/context/FeatureToggle'
import { useSettings } from '@/hooks/useSettings' import { useSettings } from '@/hooks/useSettings'
const serverEnabledAtom = atom<boolean>(false)
const Advanced = () => { const Advanced = () => {
const { experimentalFeatureEnabed, setExperimentalFeatureEnabled } = const { experimentalFeatureEnabed, setExperimentalFeatureEnabled } =
useContext(FeatureToggleContext) useContext(FeatureToggleContext)
const [gpuEnabled, setGpuEnabled] = useState<boolean>(false) const [gpuEnabled, setGpuEnabled] = useState<boolean>(false)
const [serverEnabled, setServerEnabled] = useAtom(serverEnabledAtom)
const { readSettings, saveSettings, validateSettings, setShowNotification } = const { readSettings, saveSettings, validateSettings, setShowNotification } =
useSettings() useSettings()
@ -87,6 +91,30 @@ const Advanced = () => {
}} }}
/> />
</div> </div>
{/* Server */}
<div className="flex w-full items-start justify-between border-b border-border py-4 first:pt-0 last:border-none">
<div className="w-4/5 flex-shrink-0 space-y-1.5">
<div className="flex gap-x-2">
<h6 className="text-sm font-semibold capitalize">
Enable API Server
</h6>
</div>
<p className="whitespace-pre-wrap leading-relaxed">
Enable API server for Jan app.
</p>
</div>
<Switch
checked={serverEnabled}
onCheckedChange={(e: boolean) => {
if (e === true) {
window.core?.api?.startServer()
} else {
window.core?.api?.stopServer()
}
setServerEnabled(e)
}}
/>
</div>
{window.electronAPI && ( {window.electronAPI && (
<div className="flex w-full items-start justify-between border-b border-border py-4 first:pt-0 last:border-none"> <div className="flex w-full items-start justify-between border-b border-border py-4 first:pt-0 last:border-none">
<div className="w-4/5 flex-shrink-0 space-y-1.5"> <div className="w-4/5 flex-shrink-0 space-y-1.5">