Skip to content

Commit

Permalink
再见 CORS
Browse files Browse the repository at this point in the history
✨ 后端连接支持,现在 electron 客户端将使用后端来连接
➕ 新增依赖 log4js
  • Loading branch information
Stapxs committed Aug 21, 2024
1 parent c64592d commit 7796eab
Show file tree
Hide file tree
Showing 8 changed files with 206 additions and 51 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "stapxs-qq-lite",
"version": "2.8.0",
"version": "2.8.2",
"private": false,
"author": "Stapx Steve [林槐]",
"description": "一个兼容 OneBot 的非官方网页版 QQ 客户端,使用 Vue 重制的全新版本。",
Expand Down Expand Up @@ -28,6 +28,7 @@
"js-file-downloader": "^1.1.24",
"js-yaml-loader": "^1.2.2",
"jsonpath": "^1.1.1",
"log4js": "^6.9.1",
"pinyin": "^3.0.0-alpha.4",
"prismjs": "^1.29.0",
"raw-loader": "^4.0.2",
Expand Down
1 change: 1 addition & 0 deletions src/assets/l10n/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,7 @@
"cq_code": "CQ 码",
"array_code": "Array 数组",
"chat_pic": "图片",
"log_con_backend": "使用后端连接模式",

"menu_about": "关于",
"menu_update": "检查更新…",
Expand Down
23 changes: 16 additions & 7 deletions src/background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,12 @@ import { noticeList, regIpcListener } from './function/electron/ipc'
import { Menu, session, app, protocol, BrowserWindow } from 'electron'
import { createProtocol } from 'vue-cli-plugin-electron-builder/lib'
import { touchBar } from './function/electron/touchbar'
import log4js from 'log4js'

const isDevelopment = process.env.NODE_ENV !== 'production'
const isPrimary = app.requestSingleInstanceLock()
const logger = log4js.getLogger('background')
export let logLevel = isDevelopment ? 'debug' : 'info'

protocol.registerSchemesAsPrivileged([
{ scheme: 'app', privileges: { secure: true, standard: true } }
Expand All @@ -21,19 +24,25 @@ protocol.registerSchemesAsPrivileged([
export let win = undefined as BrowserWindow | undefined
export let touchBarInstance = undefined as touchBar | undefined

/* eslint-disable no-console */
async function createWindow() {
if(new Store().get('opt_log_level')) {
logLevel = (String) (new Store().get('opt_log_level')) ?? 'info'
}
logger.level = logLevel

/* eslint-disable no-console */
console.log('')
console.log(' _____ _____ _____ _____ __ __ \n' +
'| __|_ _| _ | _ | | | \n' +
'|__ | | | | | __|- -| \n' +
'|_____| |_| |__|__|__| |__|__| CopyRight © Stapx Steve')
console.log('=======================================================')
console.log('Welcome to Stapxs QQ Lite, current version: ' + packageInfo.version)
console.log('The background language component will be initialized after the frontend is loaded.')
console.log('日志等级:', logLevel)
/* eslint-enable no-console */
logger.info('欢迎使用 Stapxs QQ Lite, 当前版本: ' + packageInfo.version)

console.log('Platform:' + process.platform)
console.log('Start creating main window ……')
logger.info('启动平台架构:' + process.platform)
logger.info('正在创建窗体 ……')
Menu.setApplicationMenu(null)
// 创建窗口
const mainWindowState = windowStateKeeper({
Expand Down Expand Up @@ -89,7 +98,7 @@ async function createWindow() {
win = new BrowserWindow(windowConfig)
win.once('focus', () => {if(win)win.flashFrame(false)})
mainWindowState.manage(win) // 窗口状态管理器
console.log('Create main window to complete.')
logger.info('创建窗体完成')
// 注册 IPC 事务
regIpcListener()
// macOS:创建 TouchBar
Expand Down Expand Up @@ -176,7 +185,7 @@ app.on('ready', async () => {
try {
await installExtension('nhdogjmejiglipccpnnnanhbledajbpd')
} catch (e: unknown) {
console.error('Vue Devtools failed to install:', (e as Error).toString())
logger.error('Vue Devtools 安装失败:', (e as Error).toString())
}
}
createWindow()
Expand Down
124 changes: 84 additions & 40 deletions src/function/connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,16 @@ export class Connector {
static create(address: string, token?: string, wss: boolean | undefined = undefined) {
const $t = app.config.globalProperties.$t

// Electron 默认使用后端连接模式
if(runtimeData.tags.isElectron) {
logger.add(LogType.WS, $t('log_con_backend'))
const reader = runtimeData.reader
if(reader) {
reader.send('onebot:connect', { address: address, token: token })
return
}
}

// PS:只有在未设定 wss 类型的情况下才认为是首次连接
if(wss == undefined) retry = 0; else retry ++
// 最多自动重试连接五次
Expand Down Expand Up @@ -63,34 +73,57 @@ export class Connector {
}

websocket.onopen = () => {
logger.add(LogType.WS, $t('log_con_success'))
// 保存登录信息
Option.save('address', address)
// 保存密钥
if(runtimeData.sysConfig.save_password && runtimeData.sysConfig.save_password != '') {
Option.save('save_password', token)
}
// 清空应用通知
popInfo.clear()
// 加载初始化数据
// PS:标记登陆成功在获取用户信息的回调位置,防止无法获取到内容
Connector.send('get_version_info', {}, 'getVersionInfo')
// 更新菜单
updateMenu({
id: 'logout',
action: 'visible',
value: true
})
this.onopen(address, token)
}
websocket.onmessage = (e) => {
// 心跳包输出到日志里太烦人了
if ((e.data as string).indexOf('"meta_event_type":"heartbeat"') < 0) {
logger.add(LogType.WS, 'GET:' + e.data)
}
parse(e.data)
this.onmessage(e.data)
}
websocket.onclose = (e) => {
websocket = undefined
this.onclose(e.code, e.reason, address, token)
}
websocket.onerror = (e) => {
popInfo.add(PopType.ERR, $t('pop_log_con_fail') + ': ' + e.type, false)
return
}
}

// 连接事件 =====================================================

static onopen(address: string, token: string | undefined) {
const $t = app.config.globalProperties.$t

logger.add(LogType.WS, $t('log_con_success'))
// 保存登录信息
Option.save('address', address)
// 保存密钥
if (runtimeData.sysConfig.save_password && runtimeData.sysConfig.save_password != '') {
Option.save('save_password', token)
}
// 清空应用通知
popInfo.clear()
// 加载初始化数据
// PS:标记登陆成功在获取用户信息的回调位置,防止无法获取到内容
Connector.send('get_version_info', {}, 'getVersionInfo')
// 更新菜单
updateMenu({
id: 'logout',
action: 'visible',
value: true
})
}

static onmessage(message: string) {
// 心跳包输出到日志里太烦人了
if ((message as string).indexOf('"meta_event_type":"heartbeat"') < 0) {
logger.add(LogType.WS, 'GET:' + message)
}
parse(message)
}

static onclose(code: number, message: string | undefined, address: string, token: string | undefined) {
const $t = app.config.globalProperties.$t

websocket = undefined
updateMenu({
id: 'logout',
action: 'visible',
Expand All @@ -102,7 +135,7 @@ export class Connector {
value: $t('menu_login')
})

switch(e.code) {
switch(code) {
case 1000: break; // 正常关闭
case 1006: { // 非正常关闭,尝试重连
if(login.status) {
Expand All @@ -119,31 +152,35 @@ export class Connector {
break;
}
default: {
popInfo.add(PopType.ERR, $t('pop_log_con_fail') + ': ' + e.code, false)
popInfo.add(PopType.ERR, $t('pop_log_con_fail') + ': ' + code, false)
// eslint-disable-next-line no-console
console.log(e)
console.log(message)
}
}
logger.error($t('pop_log_con_fail') + ': ' + e.code)
logger.error($t('pop_log_con_fail') + ': ' + code)
login.status = false

// 除了 1006 意外断开(可能要保留数据重连),其他情况都会清空
if(e.code != 1006) {
if(code != 1006) {
resetRimtime()
}
}
websocket.onerror = (e) => {
popInfo.add(PopType.ERR, $t('pop_log_con_fail') + ': ' + e.type, false)
return
}
}

// 连接器操作 =====================================================

/**
* 正常断开 Websocket 连接
*/
static close() {
popInfo.add(PopType.INFO, app.config.globalProperties.$t('pop_log_con_close'))
if(websocket) websocket.close(1000)
if(runtimeData.tags.isElectron) {
const reader = runtimeData.reader
if(reader) {
reader.send('onebot:close')
}
} else {
popInfo.add(PopType.INFO, app.config.globalProperties.$t('pop_log_con_close'))
if(websocket) websocket.close(1000)
}
}

/**
Expand All @@ -156,11 +193,18 @@ export class Connector {
// 构建 JSON
const json = JSON.stringify({ action: name, params: value, echo: echo } as BotActionElem)
// 发送
if(websocket) websocket.send(json)
if (Option.get('log_level') === 'debug') {
logger.debug('PUT:' + json)
if(runtimeData.tags.isElectron) {
const reader = runtimeData.reader
if(reader) {
reader.send('onebot:send', json)
}
} else {
logger.add(LogType.WS, 'PUT:' + json)
if(websocket) websocket.send(json)
if (Option.get('log_level') === 'debug') {
logger.debug('PUT:' + json)
} else {
logger.add(LogType.WS, 'PUT:' + json)
}
}
}
}
Expand Down
81 changes: 81 additions & 0 deletions src/function/electron/connector.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/**
* 后端和前端的简易通信器,封装一些常用的交互
*/

import WebSocket from 'ws'
import log4js from 'log4js'
import { BrowserWindow, ipcMain } from 'electron'
import { logLevel } from '@/background'

export class Connector {

private logger = log4js.getLogger('connector')

private win: BrowserWindow
private websocket: WebSocket | undefined

constructor(win: BrowserWindow) {
this.logger.level = logLevel
this.win = win
// 初始化 ipc
ipcMain.on('onebot:send', (event, json) => {
this.websocket?.send(json)
})
ipcMain.on('onebot:close', () => {
this.websocket?.close(1000)
this.websocket = undefined
})
this.logger.info('后端连接器已初始化')
}

connect(url: string, token: string) {
if(url.indexOf('ws://') < 0 && url.indexOf('wss://') < 0) {
url = 'wss://' + url
}

if(!this.websocket) {
this.logger.info('正在连接到:', url)
this.websocket = new WebSocket(url + '?access_token=' + token)
}

this.websocket.onopen = () => {
this.logger.info('已成功连接到', url)
this.win.webContents.send('onebot:onopen', { address: url, token: token })
}
this.websocket.onmessage = (e) => {
try {
const message = JSON.parse((e.data as string))
if(message.echo)
this.logger.debug('收到消息:', message.echo)
else if(message.post_type) {
if(message.notice_type)
this.logger.debug('收到消息:', message.post_type, message.notice_type)
else
this.logger.debug('收到消息:', message.post_type)
}
} catch(e) {
//
}
this.win.webContents.send('onebot:onmessage', e.data)
}
this.websocket.onclose = (e) => {
this.logger.info('连接已关闭,代码:', e.code)
if(e.code != 1006 && e.code != 1015) {
// 除了需要重连的情况,其他情况都直接常规处理
this.win.webContents.send('onebot:onclose', {
code: e.code,
message: e.reason,
address: url,
token: token
})
} else if(e.code == 1015) {
// TSL 连接失败,尝试使用非加密连接
this.logger.warn('连接失败,尝试使用非加密连接...')
this.connect(url.replace('wss://', 'ws://'), token)
}
}
this.websocket.onerror = (e) => {
this.logger.error('连接错误:', e)
}
}
}
9 changes: 9 additions & 0 deletions src/function/electron/ipc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,21 @@ import { ipcMain, shell, systemPreferences, app, Menu, MenuItemConstructorOption
import { GtkTheme, GtkData } from '@jakejarrett/gtk-theme'
import { runCommand } from './util'
import { win, touchBarInstance } from '@/background'
import { Connector } from '@/function/electron/connector'

let connector = undefined as Connector | undefined
const store = new Store()
let template = [] as any[]
export const noticeList = {} as {[key: string]: ELNotification[]}

export function regIpcListener() {
// 后端连接模式
ipcMain.on('onebot:connect', (event, args) => {
if(!connector && win) {
connector = new Connector(win)
}
connector?.connect(args.address, args.token)
})
// 获取系统平台
ipcMain.handle('sys:getPlatform', () => {
return process.platform
Expand Down
11 changes: 11 additions & 0 deletions src/function/utils/appUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,17 @@ export function createIpc() {
runtimeData.reader.on('app:jumpChat', (event, info) => {
jumpToChat(info.userId, info.msgId)
})

// 后端连接模式
runtimeData.reader.on('onebot:onopen', (event, data) => {
Connector.onopen(data.address, data.token)
})
runtimeData.reader.on('onebot:onmessage', (event, message) => {
Connector.onmessage(message)
})
runtimeData.reader.on('onebot:onclose', (event, data) => {
Connector.onclose(data.code, data.reason, data.address, data.token)
})
}
}

Expand Down
Loading

0 comments on commit 7796eab

Please sign in to comment.