Skip to content

Commit

Permalink
fix: swagger CSP issue (janhq#1284)
Browse files Browse the repository at this point in the history
  • Loading branch information
louis-jan authored Jan 2, 2024
1 parent 32a82ad commit 12b037e
Show file tree
Hide file tree
Showing 10 changed files with 146 additions and 67 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ build
.DS_Store
electron/renderer
electron/models
electron/docs
package-lock.json

*.log
Expand Down
18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 2 additions & 0 deletions core/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ export enum AppRoute {
relaunch = 'relaunch',
joinPath = 'joinPath',
baseName = 'baseName',
startServer = 'startServer',
stopServer = 'stopServer',
}

export enum AppEvent {
Expand Down
Empty file added electron/docs/openapi/.gitkeep
Empty file.
20 changes: 19 additions & 1 deletion electron/handlers/app.ts
Original file line number Diff line number Diff line change
@@ -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() {
/**
Expand Down Expand Up @@ -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.
Expand Down
6 changes: 0 additions & 6 deletions electron/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -34,7 +29,6 @@ app
.then(handleIPCs)
.then(handleAppUpdates)
.then(createMainWindow)
.then(startServer)
.then(() => {
app.on('activate', () => {
if (!BrowserWindow.getAllWindows().length) {
Expand Down
6 changes: 4 additions & 2 deletions electron/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@
"build/*.{js,map}",
"build/**/*.{js,map}",
"pre-install",
"models/**/*"
"models/**/*",
"docs/**/*"
],
"asarUnpack": [
"pre-install",
"models"
"models",
"docs"
],
"publish": [
{
Expand Down
9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand Down
121 changes: 77 additions & 44 deletions server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
},
});
server.register(require("@fastify/swagger-ui"), {
routePrefix: "/docs",
baseDir: path.join(__dirname, "../..", "./docs/openapi"),
uiConfig: {
docExpansion: "full",
deepLinking: false,
},
staticCSP: true,
transformSpecificationClone: true,
let server: any | undefined = undefined;

var log_file = fs.createWriteStream(serverLogPath, {
flags: "a",
});
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;

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

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}`
);
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 = () => {
server.close();
export const stopServer = async () => {
try {
await server.close();
} catch (e) {
logServer(e);
}
};
30 changes: 29 additions & 1 deletion web/screens/Settings/Advanced/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<boolean>(false)

const Advanced = () => {
const { experimentalFeatureEnabed, setExperimentalFeatureEnabled } =
useContext(FeatureToggleContext)
const [gpuEnabled, setGpuEnabled] = useState<boolean>(false)
const [serverEnabled, setServerEnabled] = useAtom(serverEnabledAtom)
const { readSettings, saveSettings, validateSettings, setShowNotification } =
useSettings()

Expand Down Expand Up @@ -87,6 +91,30 @@ const Advanced = () => {
}}
/>
</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 && (
<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">
Expand Down

0 comments on commit 12b037e

Please sign in to comment.