diff --git a/src/app.ts b/src/app.ts index ba1114a2..6bb8ff90 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,4 +1,4 @@ -import { app, BrowserWindow, Menu, Tray, powerMonitor } from 'electron' +import { protocol, app, BrowserWindow, Menu, Tray, powerMonitor } from 'electron' import * as path from 'path' import * as os from 'os' import { dialog } from 'electron' @@ -8,18 +8,28 @@ import { USER_DIR } from './server/constant' import * as updater from './updater' import { getAccelerator, registerShortcut } from './shortcut' import { mainMenus, inputMenu, selectionMenu } from './menus'; +import { SCHEME, transformProtocolRequest } from './protocol'; import { bus } from './bus' const opn = require('opn') const isMacos = os.platform() === 'darwin' -let isDev = false +let urlMode: 'scheme' | 'dev' | 'prod' = 'scheme' const showTray = !(yargs.argv['disable-tray']) const backendPort = Number(yargs.argv.port) || 3044 const devFrontendPort = 8066 -const getUrl = () => { +Menu.setApplicationMenu(mainMenus) + +// 主窗口 +let win: BrowserWindow | null = null +// 系统托盘 +let tray = null + +const getUrl = (mode?: typeof urlMode) => { + mode = mode ?? urlMode + const args = Object.entries(yargs.argv).filter(x => [ 'readonly', 'show-status-bar', @@ -29,15 +39,15 @@ const getUrl = () => { const query = (new URLSearchParams(args as any)).toString() - return `http://localhost:${isDev ? devFrontendPort : backendPort}` + (query ? `?${query}` : '') + const proto = mode === 'scheme' ? SCHEME : 'http' + const port = mode === 'dev' ? devFrontendPort : backendPort + + console.log(mode, `${proto}://localhost:${port}` + (query ? `?${query}` : '')) + + return `${proto}://localhost:${port}` + (query ? `?${query}` : '') } -Menu.setApplicationMenu(mainMenus) - -// 主窗口 -let win: BrowserWindow | null = null -// 系统托盘 -let tray = null +const openInBrowser = () => opn(getUrl('prod')) const hide = () => { if (win) { @@ -177,7 +187,18 @@ if (!gotTheLock) { app.on('ready', () => { // 打开后端服务器 try { - server(backendPort) + const handler = server(backendPort) + protocol.registerStreamProtocol('yank-note', async (request, callback) => { + // 自定义 protocol 协议转换为 koa 请求 + const { req, res, out } = await transformProtocolRequest(request) + + await handler(req, res) + callback({ + headers: res.getHeaders() as any, + statusCode: res.statusCode, + data: out, + }) + }) } catch (error) { app.exit(-1) } @@ -194,7 +215,7 @@ if (!gotTheLock) { // 注册快捷键 registerShortcut({ 'show-main-window': () => showWindow(), - 'open-in-browser': () => opn(getUrl()) + 'open-in-browser': () => openInBrowser() }) powerMonitor.on('shutdown', quit) @@ -213,9 +234,7 @@ if (!gotTheLock) { type: 'normal', label: '浏览器中打开', accelerator: getAccelerator('open-in-browser'), - click: () => { - opn(getUrl()) - } + click: openInBrowser }, { type: 'normal', @@ -245,19 +264,28 @@ if (!gotTheLock) { submenu: [ { type: 'radio', - checked: !isDev, - label: `正式端口(${backendPort})`, + checked: urlMode === 'scheme', + label: `正式端口(Schema)`, click: () => { - isDev = false + urlMode = 'scheme' reload() } }, { type: 'radio', - checked: isDev, + checked: urlMode === 'prod', + label: `正式端口(${backendPort})`, + click: () => { + urlMode = 'prod' + reload() + } + }, + { + type: 'radio', + checked: urlMode === 'dev', label: `开发端口(${devFrontendPort})`, click: () => { - isDev = true + urlMode = 'dev' reload() } }, diff --git a/src/protocol.ts b/src/protocol.ts new file mode 100644 index 00000000..efd9d9c8 --- /dev/null +++ b/src/protocol.ts @@ -0,0 +1,66 @@ +import { protocol, session, ProtocolRequest, UploadData } from 'electron' +import { IncomingMessage, ServerResponse } from 'http' +import { Readable } from 'stream' +import { Transform } from 'stream' + +export const SCHEME = 'yank-note' + +protocol.registerSchemesAsPrivileged([{ + scheme: SCHEME, + privileges: { + supportFetchAPI: true, + standard: true, + secure: true, + allowServiceWorkers: true, + bypassCSP: true, + corsEnabled: true, + } +}]) + +async function transformBody(data?: UploadData[]): Promise { + if (!data) { + return null + } + + let body = Buffer.from([]) + for (const payload of data) { + if (payload.bytes) { + body = Buffer.concat([body, payload.bytes]) + } else if (payload.blobUUID) { + const file = await session.defaultSession.getBlobData(payload.blobUUID) + body = Buffer.concat([body, file]) + } + } + + return body +} + +export async function transformProtocolRequest (request: ProtocolRequest) { + const req = new IncomingMessage(null) + + req.method = request.method + req.url = request.url.replace('yank-note', 'http') + + Object.keys(request.headers).forEach(key => { + req.headers[key.toLowerCase()] = request.headers[key] + }) + + const body = await transformBody(request.uploadData) + if (body) { + req.headers['content-length'] = body.length.toString() + req._read = Readable.from(body)._read + } + + const out = new Transform({ + transform(chunk, encoding, cb) { + this.push(chunk, encoding) + cb() + }, + }) + + const res = new ServerResponse(req) + res.write = out.write.bind(out) + res.end = out.end.bind(out) + + return { req, res, out } +} diff --git a/src/server/main.ts b/src/server/main.ts index 5a82dad3..1d60184d 100644 --- a/src/server/main.ts +++ b/src/server/main.ts @@ -302,7 +302,8 @@ const server = (port = 3000) => { await sendFile(path.resolve(STATIC_DIR, urlPath), true); }) - const server = require('http').createServer(app.callback()) + const callback = app.callback() + const server = require('http').createServer(callback) const io = require('socket.io')(server, {path: '/ws'}) io.on('connection', (socket: any) => { @@ -329,6 +330,8 @@ const server = (port = 3000) => { server.listen(port, 'localhost') console.log(`访问地址:http://localhost:${port}`) + + return callback } if (require.main === module) {