mirror of
https://github.com/th-ch/youtube-music.git
synced 2026-01-11 10:31:47 +00:00
feat(api-server): Add HTTPS support and custom certificate configuration (#3874)
This commit is contained in:
@ -323,6 +323,22 @@
|
|||||||
},
|
},
|
||||||
"port": {
|
"port": {
|
||||||
"label": "Port"
|
"label": "Port"
|
||||||
|
},
|
||||||
|
"https": {
|
||||||
|
"label": "HTTPS & Certificates",
|
||||||
|
"submenu": {
|
||||||
|
"enable-https": {
|
||||||
|
"label": "Enable HTTPS"
|
||||||
|
},
|
||||||
|
"cert": {
|
||||||
|
"label": "Certificate file (.crt/.pem)",
|
||||||
|
"dialogTitle": "Select HTTPS certificate file"
|
||||||
|
},
|
||||||
|
"key": {
|
||||||
|
"label": "Private key file (.key/.pem)",
|
||||||
|
"dialogTitle": "Select HTTPS private key file"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"name": "API Server [Beta]",
|
"name": "API Server [Beta]",
|
||||||
|
|||||||
@ -1,3 +1,7 @@
|
|||||||
|
import { createServer as createHttpServer } from 'node:http';
|
||||||
|
import { createServer as createHttpsServer } from 'node:https';
|
||||||
|
import { readFileSync } from 'node:fs';
|
||||||
|
|
||||||
import { jwt } from 'hono/jwt';
|
import { jwt } from 'hono/jwt';
|
||||||
import { OpenAPIHono as Hono } from '@hono/zod-openapi';
|
import { OpenAPIHono as Hono } from '@hono/zod-openapi';
|
||||||
import { cors } from 'hono/cors';
|
import { cors } from 'hono/cors';
|
||||||
@ -48,22 +52,26 @@ export const backend = createBackend<BackendType, APIServerConfig>({
|
|||||||
(newVolumeState: VolumeState) => (this.volumeState = newVolumeState),
|
(newVolumeState: VolumeState) => (this.volumeState = newVolumeState),
|
||||||
);
|
);
|
||||||
|
|
||||||
this.run(config.hostname, config.port);
|
this.run(config);
|
||||||
},
|
},
|
||||||
stop() {
|
stop() {
|
||||||
this.end();
|
this.end();
|
||||||
},
|
},
|
||||||
onConfigChange(config) {
|
onConfigChange(config) {
|
||||||
|
const old = this.oldConfig;
|
||||||
if (
|
if (
|
||||||
this.oldConfig?.hostname === config.hostname &&
|
old?.hostname === config.hostname &&
|
||||||
this.oldConfig?.port === config.port
|
old?.port === config.port &&
|
||||||
|
old?.useHttps === config.useHttps &&
|
||||||
|
old?.certPath === config.certPath &&
|
||||||
|
old?.keyPath === config.keyPath
|
||||||
) {
|
) {
|
||||||
this.oldConfig = config;
|
this.oldConfig = config;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.end();
|
this.end();
|
||||||
this.run(config.hostname, config.port);
|
this.run(config);
|
||||||
this.oldConfig = config;
|
this.oldConfig = config;
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -153,15 +161,30 @@ export const backend = createBackend<BackendType, APIServerConfig>({
|
|||||||
|
|
||||||
this.injectWebSocket = ws.injectWebSocket.bind(this);
|
this.injectWebSocket = ws.injectWebSocket.bind(this);
|
||||||
},
|
},
|
||||||
run(hostname, port) {
|
run(config) {
|
||||||
if (!this.app) return;
|
if (!this.app) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.server = serve({
|
const serveOptions =
|
||||||
fetch: this.app.fetch.bind(this.app),
|
config.useHttps && config.certPath && config.keyPath
|
||||||
port,
|
? {
|
||||||
hostname,
|
fetch: this.app.fetch.bind(this.app),
|
||||||
});
|
port: config.port,
|
||||||
|
hostname: config.hostname,
|
||||||
|
createServer: createHttpsServer,
|
||||||
|
serverOptions: {
|
||||||
|
key: readFileSync(config.keyPath),
|
||||||
|
cert: readFileSync(config.certPath),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
fetch: this.app.fetch.bind(this.app),
|
||||||
|
port: config.port,
|
||||||
|
hostname: config.hostname,
|
||||||
|
createServer: createHttpServer,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.server = serve(serveOptions);
|
||||||
|
|
||||||
if (this.injectWebSocket && this.server) {
|
if (this.injectWebSocket && this.server) {
|
||||||
this.injectWebSocket(this.server);
|
this.injectWebSocket(this.server);
|
||||||
|
|||||||
@ -17,6 +17,6 @@ export type BackendType = {
|
|||||||
injectWebSocket?: (server: ReturnType<typeof serve>) => void;
|
injectWebSocket?: (server: ReturnType<typeof serve>) => void;
|
||||||
|
|
||||||
init: (ctx: BackendContext<APIServerConfig>) => void;
|
init: (ctx: BackendContext<APIServerConfig>) => void;
|
||||||
run: (hostname: string, port: number) => void;
|
run: (config: APIServerConfig) => void;
|
||||||
end: () => void;
|
end: () => void;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -11,6 +11,9 @@ export interface APIServerConfig {
|
|||||||
secret: string;
|
secret: string;
|
||||||
|
|
||||||
authorizedClients: string[];
|
authorizedClients: string[];
|
||||||
|
useHttps: boolean;
|
||||||
|
certPath: string;
|
||||||
|
keyPath: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const defaultAPIServerConfig: APIServerConfig = {
|
export const defaultAPIServerConfig: APIServerConfig = {
|
||||||
@ -21,4 +24,7 @@ export const defaultAPIServerConfig: APIServerConfig = {
|
|||||||
secret: Date.now().toString(36),
|
secret: Date.now().toString(36),
|
||||||
|
|
||||||
authorizedClients: [],
|
authorizedClients: [],
|
||||||
|
useHttps: false,
|
||||||
|
certPath: '',
|
||||||
|
keyPath: '',
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { dialog } from 'electron';
|
||||||
import prompt from 'custom-electron-prompt';
|
import prompt from 'custom-electron-prompt';
|
||||||
|
|
||||||
import { t } from '@/i18n';
|
import { t } from '@/i18n';
|
||||||
@ -93,5 +94,51 @@ export const onMenu = async ({
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: t('plugins.api-server.menu.https.label'),
|
||||||
|
type: 'submenu',
|
||||||
|
submenu: [
|
||||||
|
{
|
||||||
|
label: t('plugins.api-server.menu.https.submenu.enable-https.label'),
|
||||||
|
type: 'checkbox',
|
||||||
|
checked: config.useHttps,
|
||||||
|
click(menuItem) {
|
||||||
|
setConfig({ ...config, useHttps: menuItem.checked });
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('plugins.api-server.menu.https.submenu.cert.label'),
|
||||||
|
type: 'normal',
|
||||||
|
async click() {
|
||||||
|
const config = await getConfig();
|
||||||
|
const result = await dialog.showOpenDialog(window, {
|
||||||
|
title: t(
|
||||||
|
'plugins.api-server.menu.https.submenu.cert.dialogTitle',
|
||||||
|
),
|
||||||
|
filters: [{ name: 'Certificate', extensions: ['crt', 'pem'] }],
|
||||||
|
properties: ['openFile'],
|
||||||
|
});
|
||||||
|
if (!result.canceled && result.filePaths.length > 0) {
|
||||||
|
setConfig({ ...config, certPath: result.filePaths[0] });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('plugins.api-server.menu.https.submenu.key.label'),
|
||||||
|
type: 'normal',
|
||||||
|
async click() {
|
||||||
|
const config = await getConfig();
|
||||||
|
const result = await dialog.showOpenDialog(window, {
|
||||||
|
title: t('plugins.api-server.menu.https.submenu.key.dialogTitle'),
|
||||||
|
filters: [{ name: 'Private Key', extensions: ['key', 'pem'] }],
|
||||||
|
properties: ['openFile'],
|
||||||
|
});
|
||||||
|
if (!result.canceled && result.filePaths.length > 0) {
|
||||||
|
setConfig({ ...config, keyPath: result.filePaths[0] });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user