From 1c159f59bba4e54959898d0aea818cf6b33853b7 Mon Sep 17 00:00:00 2001 From: lyswhut Date: Mon, 14 Aug 2023 15:33:59 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=90=8C=E6=AD=A5=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 25 ++- package.json | 5 +- publish/changeLog.md | 6 + src/common/constants.ts | 2 +- src/common/types/sync_common.d.ts | 30 ++++ src/common/types/utils.d.ts | 10 ++ src/main/modules/sync/client/auth.ts | 3 +- src/main/modules/sync/client/client.ts | 138 +++++++++------- src/main/modules/sync/client/modules/index.ts | 11 +- .../sync/client/modules/list/handler.ts | 53 ++++++ .../modules/sync/client/modules/list/index.ts | 9 +- .../sync/client/modules/list/localEvent.ts | 20 +++ .../modules/sync/client/modules/list/on.ts | 9 -- .../modules/sync/client/modules/list/send.ts | 21 --- src/main/modules/sync/client/syncList.ts | 73 --------- src/main/modules/sync/client/utils.ts | 10 +- src/main/modules/sync/server/auth.ts | 3 +- src/main/modules/sync/server/modules/index.ts | 11 +- src/main/modules/sync/server/modules/list.ts | 68 -------- .../sync/server/modules/list/handler.ts | 86 ++++++++++ .../modules/sync/server/modules/list/index.ts | 4 + .../sync/server/modules/list/localEvent.ts | 34 ++++ .../{syncList.ts => modules/list/sync.ts} | 153 ++++++++---------- src/main/modules/sync/server/server.ts | 101 ++++++++---- src/main/modules/sync/server/utils.ts | 14 +- src/main/modules/sync/utils.ts | 33 ++++ .../modules/winMain/rendererEvent/sync.ts | 10 +- src/main/types/common.d.ts | 1 + src/main/types/sync.d.ts | 29 +--- 29 files changed, 567 insertions(+), 405 deletions(-) create mode 100644 src/common/types/sync_common.d.ts create mode 100644 src/main/modules/sync/client/modules/list/handler.ts create mode 100644 src/main/modules/sync/client/modules/list/localEvent.ts delete mode 100644 src/main/modules/sync/client/modules/list/on.ts delete mode 100644 src/main/modules/sync/client/modules/list/send.ts delete mode 100644 src/main/modules/sync/client/syncList.ts delete mode 100644 src/main/modules/sync/server/modules/list.ts create mode 100644 src/main/modules/sync/server/modules/list/handler.ts create mode 100644 src/main/modules/sync/server/modules/list/index.ts create mode 100644 src/main/modules/sync/server/modules/list/localEvent.ts rename src/main/modules/sync/server/{syncList.ts => modules/list/sync.ts} (82%) diff --git a/package-lock.json b/package-lock.json index 5a6b2c5d70..07668ca2fc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "image-size": "^1.0.2", "jschardet": "^3.0.0", "long": "^5.2.3", + "message2call": "^0.1.0", "music-metadata": "^8.1.4", "needle": "github:lyswhut/needle#93299ac841b7e9a9f82ca7279b88aaaeda404060", "node-id3": "^0.2.6", @@ -45,7 +46,7 @@ "@tsconfig/recommended": "^1.0.2", "@types/better-sqlite3": "^7.6.4", "@types/needle": "^3.2.0", - "@types/node": "^20.4.9", + "@types/node": "^20.4.10", "@types/spinnies": "^0.5.0", "@types/tunnel": "^0.0.3", "@types/ws": "8.5.4", @@ -3088,9 +3089,9 @@ } }, "node_modules/@types/node": { - "version": "20.4.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.9.tgz", - "integrity": "sha512-8e2HYcg7ohnTUbHk8focoklEQYvemQmu9M/f43DZVx43kHn0tE3BY/6gSDxS7k0SprtS0NHvj+L80cGLnoOUcQ==", + "version": "20.4.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.10.tgz", + "integrity": "sha512-vwzFiiy8Rn6E0MtA13/Cxxgpan/N6UeNYR9oUu6kuJWxu6zCk98trcDp8CBhbtaeuq9SykCmXkFr2lWLoPcvLg==", "dev": true }, "node_modules/@types/plist": { @@ -8749,6 +8750,11 @@ "node": ">= 8" } }, + "node_modules/message2call": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/message2call/-/message2call-0.1.0.tgz", + "integrity": "sha512-10zubfIMTkxevtenynScyYN6GdWUgROrGqi9rU06tg+WY8SJIPwvKaDFuoKF8d2pbaFXD4hlOON2vf0ptCIN/A==" + }, "node_modules/micromatch": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", @@ -15018,9 +15024,9 @@ } }, "@types/node": { - "version": "20.4.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.9.tgz", - "integrity": "sha512-8e2HYcg7ohnTUbHk8focoklEQYvemQmu9M/f43DZVx43kHn0tE3BY/6gSDxS7k0SprtS0NHvj+L80cGLnoOUcQ==", + "version": "20.4.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.10.tgz", + "integrity": "sha512-vwzFiiy8Rn6E0MtA13/Cxxgpan/N6UeNYR9oUu6kuJWxu6zCk98trcDp8CBhbtaeuq9SykCmXkFr2lWLoPcvLg==", "dev": true }, "@types/plist": { @@ -19282,6 +19288,11 @@ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true }, + "message2call": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/message2call/-/message2call-0.1.0.tgz", + "integrity": "sha512-10zubfIMTkxevtenynScyYN6GdWUgROrGqi9rU06tg+WY8SJIPwvKaDFuoKF8d2pbaFXD4hlOON2vf0ptCIN/A==" + }, "micromatch": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", diff --git a/package.json b/package.json index bea1b6d1fe..d420e79ecc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "lx-music-desktop", - "version": "2.4.0-beta.6", + "version": "2.4.0-beta.7", "description": "一个免费的音乐查找助手", "main": "./dist/main/main.js", "productName": "lx-music-desktop", @@ -212,7 +212,7 @@ "@tsconfig/recommended": "^1.0.2", "@types/better-sqlite3": "^7.6.4", "@types/needle": "^3.2.0", - "@types/node": "^20.4.9", + "@types/node": "^20.4.10", "@types/spinnies": "^0.5.0", "@types/tunnel": "^0.0.3", "@types/ws": "8.5.4", @@ -262,6 +262,7 @@ "image-size": "^1.0.2", "jschardet": "^3.0.0", "long": "^5.2.3", + "message2call": "^0.1.0", "music-metadata": "^8.1.4", "needle": "github:lyswhut/needle#93299ac841b7e9a9f82ca7279b88aaaeda404060", "node-id3": "^0.2.6", diff --git a/publish/changeLog.md b/publish/changeLog.md index 7c2014150f..c5a2f59f1b 100644 --- a/publish/changeLog.md +++ b/publish/changeLog.md @@ -1,6 +1,10 @@ 目前本项目的原始发布地址只有 **GitHub** 及 **蓝奏网盘** ,其他渠道均为第三方转载发布,可信度请自行鉴别。 本项目无微信公众号之类的官方账号,谨防被骗。 +### 不兼容性变更 + +该版本修改了同步协议逻辑,需要PC端v2.4.0或移动端v1.7.0版本才能连接使用。 + ### 新增 - 新增我的列表名右键菜单-排序歌曲-随机乱序功能,使用它可以对选中列表内歌曲进行随机重排(#1440) @@ -10,6 +14,7 @@ - 优化音效设置-环境音效启用、禁用时的操作效果显示,修复禁用环境音效时仍然可以调整增益、新增预设的问题 - 过滤翻译歌词或罗马音歌词中只有“//”的行(#1499) - 点击打开歌单弹窗背景将不再自动关闭弹窗,防止选择输入框里的内容时意外关闭弹窗 +- 优化数据传输逻辑,列表同步指令使用队列机制,保证列表同步操作的顺序 ### 修复 @@ -21,6 +26,7 @@ - 修复某些tx源歌词因数据异常解析失败的问题 - 修复windows平台下隐藏窗口后再显示时任务栏按钮丢失的问题 - 修复首句歌词被提前播放的问题 +- 修复潜在导致列表数据不同步的问题 ### 其他 diff --git a/src/common/constants.ts b/src/common/constants.ts index 585372d1a5..3a83f8a231 100644 --- a/src/common/constants.ts +++ b/src/common/constants.ts @@ -78,7 +78,7 @@ export const DOWNLOAD_STATUS = { export const QUALITYS = ['flac24bit', 'flac', 'wav', 'ape', '320k', '192k', '128k'] as const export const SYNC_CODE = { - helloMsg: 'Hello~::^-^::~v3~', + helloMsg: 'Hello~::^-^::~v4~', idPrefix: 'OjppZDo6', authMsg: 'lx-music auth::', authFailed: 'Auth failed', diff --git a/src/common/types/sync_common.d.ts b/src/common/types/sync_common.d.ts new file mode 100644 index 0000000000..ad82a458b0 --- /dev/null +++ b/src/common/types/sync_common.d.ts @@ -0,0 +1,30 @@ +declare namespace LX { + namespace Sync { + type ActionList = SyncAction<'list_data_overwrite', LX.List.ListActionDataOverwrite> + | SyncAction<'list_create', LX.List.ListActionAdd> + | SyncAction<'list_remove', LX.List.ListActionRemove> + | SyncAction<'list_update', LX.List.ListActionUpdate> + | SyncAction<'list_update_position', LX.List.ListActionUpdatePosition> + | SyncAction<'list_music_add', LX.List.ListActionMusicAdd> + | SyncAction<'list_music_move', LX.List.ListActionMusicMove> + | SyncAction<'list_music_remove', LX.List.ListActionMusicRemove> + | SyncAction<'list_music_update', LX.List.ListActionMusicUpdate> + | SyncAction<'list_music_update_position', LX.List.ListActionMusicUpdatePosition> + | SyncAction<'list_music_overwrite', LX.List.ListActionMusicOverwrite> + | SyncAction<'list_music_clear', LX.List.ListActionMusicClear> + + type ServerActions = WarpPromiseRecord<{ + onListSyncAction: (action: LX.Sync.ActionList) => void + }> + + type ClientActions = WarpPromiseRecord<{ + onListSyncAction: (action: LX.Sync.ActionList) => void + list_sync_get_md5: () => string + list_sync_get_sync_mode: () => Mode + list_sync_get_list_data: () => ListData + list_sync_set_list_data: (data: ListData) => void + list_sync_finished: () => void + }> + } +} + diff --git a/src/common/types/utils.d.ts b/src/common/types/utils.d.ts index 9246a53d33..e0afc0eecb 100644 --- a/src/common/types/utils.d.ts +++ b/src/common/types/utils.d.ts @@ -10,3 +10,13 @@ type Modify = Omit & R type Actions = { [U in T as U['action']]: 'data' extends keyof U ? U['data'] : undefined } + +type WarpPromiseValue = T extends ((...args: infer P) => Promise) + ? ((...args: P) => Promise) + : T extends ((...args: infer P2) => infer R2) + ? ((...args: P2) => Promise) + : Promise + +type WarpPromiseRecord> = { + [K in keyof T]: WarpPromiseValue +} diff --git a/src/main/modules/sync/client/auth.ts b/src/main/modules/sync/client/auth.ts index 79afb0fbe7..c17b448ca6 100644 --- a/src/main/modules/sync/client/auth.ts +++ b/src/main/modules/sync/client/auth.ts @@ -3,6 +3,7 @@ import { getSyncAuthKey, setSyncAuthKey } from '../data' import { SYNC_CODE } from '@common/constants' import log from '../log' import { aesDecrypt, aesEncrypt, getComputerName, rsaDecrypt } from '../utils' +import { toMD5 } from '@common/utils/nodejs' const hello = async(urlInfo: LX.Sync.Client.UrlInfo) => request(`${urlInfo.httpProtocol}//${urlInfo.hostPath}/hello`) @@ -25,7 +26,7 @@ const getServerId = async(urlInfo: LX.Sync.Client.UrlInfo) => request(`${urlInfo }) const codeAuth = async(urlInfo: LX.Sync.Client.UrlInfo, serverId: string, authCode: string) => { - let key = ''.padStart(16, Buffer.from(authCode).toString('hex')) + let key = toMD5(authCode).substring(0, 16) // const iv = Buffer.from(key.split('').reverse().join('')).toString('base64') key = Buffer.from(key).toString('base64') let { publicKey, privateKey } = await generateRsaKey() diff --git a/src/main/modules/sync/client/client.ts b/src/main/modules/sync/client/client.ts index cf9707e0d8..721c234488 100644 --- a/src/main/modules/sync/client/client.ts +++ b/src/main/modules/sync/client/client.ts @@ -1,14 +1,15 @@ import WebSocket from 'ws' import { encryptMsg, decryptMsg } from './utils' -import * as modules from './modules' +import { modules, callObj } from './modules' // import { action as commonAction } from '@root/store/modules/common' // import { getStore } from '@root/store' -import registerSyncListHandler from './syncList' +// import registerSyncListHandler from './syncList' import log from '../log' import { SYNC_CLOSE_CODE, SYNC_CODE } from '@common/constants' import { dateFormat } from '@common/utils/common' import { aesEncrypt, getAddress } from '../utils' import { sendClientStatus } from '@main/modules/winMain' +import { createMsg2call } from 'message2call' let status: LX.Sync.ClientStatus = { status: false, @@ -31,8 +32,14 @@ export const sendSyncMessage = (message: string) => { } const handleConnection = (socket: LX.Sync.Client.Socket) => { - for (const moduleInit of Object.values(modules)) { - moduleInit(socket) + for (const { registerEvent } of Object.values(modules)) { + registerEvent(socket) + } +} + +const handleDisconnection = () => { + for (const { unregisterEvent } of Object.values(modules)) { + unregisterEvent() } } @@ -123,7 +130,7 @@ const heartbeatTools = { let client: LX.Sync.Client.Socket | null // let listSyncPromise: Promise export const connect = (urlInfo: LX.Sync.Client.UrlInfo, keyInfo: LX.Sync.ClientKeyInfo) => { - client = new WebSocket(`${urlInfo.wsProtocol}//${urlInfo.hostPath}?i=${encodeURIComponent(keyInfo.clientId)}&t=${encodeURIComponent(aesEncrypt(SYNC_CODE.msgConnect, keyInfo.key))}`, { + client = new WebSocket(`${urlInfo.wsProtocol}//${urlInfo.hostPath}/socket?i=${encodeURIComponent(keyInfo.clientId)}&t=${encodeURIComponent(aesEncrypt(SYNC_CODE.msgConnect, keyInfo.key))}`, { }) as LX.Sync.Client.Socket client.data = { keyInfo, @@ -131,39 +138,67 @@ export const connect = (urlInfo: LX.Sync.Client.UrlInfo, keyInfo: LX.Sync.Client } heartbeatTools.connect(client) - // listSyncPromise = registerSyncListHandler(socket) - let events: Partial<{ [K in keyof LX.Sync.ActionSyncSendType]: Array<(data: LX.Sync.ActionSyncSendType[K]) => (void | Promise)> }> = {} let closeEvents: Array<(err: Error) => (void | Promise)> = [] + + const message2read = createMsg2call({ + funcsObj: { + ...callObj, + list_sync_finished() { + log.info('sync list success') + client!.isReady = true + handleConnection(client as LX.Sync.Client.Socket) + sendSyncStatus({ + status: true, + message: '', + }) + heartbeatTools.failedNum = 0 + }, + }, + timeout: 120 * 1000, + sendMessage(data) { + void encryptMsg(keyInfo, JSON.stringify(data)).then((data) => { + client?.send(data) + }).catch((err) => { + log.error('encrypt msg error: ', err) + client?.close(SYNC_CLOSE_CODE.failed) + }) + }, + onCallBeforeParams(rawArgs) { + return [client, ...rawArgs] + }, + onError(error, path, groupName) { + const name = groupName ?? '' + log.error(`sync call ${name} ${path.join('.')} error:`, error) + if (groupName == null) return + client?.close(SYNC_CLOSE_CODE.failed) + sendSyncStatus({ + status: false, + message: error.message, + }) + }, + }) + + client.remoteSyncList = message2read.createSyncRemote('list') + client.addEventListener('message', ({ data }) => { if (data == 'ping') return if (typeof data === 'string') { - let syncData: LX.Sync.ActionSync - try { - syncData = JSON.parse(decryptMsg(keyInfo, data)) - } catch { - return - } - const handlers = events[syncData.action] - if (handlers) { - // @ts-expect-error - for (const handler of handlers) void handler(syncData.data) - } + void decryptMsg(keyInfo, data).then((data) => { + let syncData: LX.Sync.ServerActions + try { + syncData = JSON.parse(data) + } catch (err) { + log.error('parse msg error: ', err) + client?.close(SYNC_CLOSE_CODE.failed) + return + } + message2read.onMessage(syncData) + }).catch((error) => { + log.error('decrypt msg error: ', error) + client?.close(SYNC_CLOSE_CODE.failed) + }) } }) - client.onRemoteEvent = function(eventName, handler) { - let eventArr = events[eventName] - if (!eventArr) events[eventName] = eventArr = [] - // let eventArr = events.get(eventName) - // if (!eventArr) events.set(eventName, eventArr = []) - eventArr.push(handler) - - return () => { - eventArr!.splice(eventArr!.indexOf(handler), 1) - } - } - client.sendData = function(eventName, data, callback) { - client?.send(encryptMsg(keyInfo, JSON.stringify({ action: eventName, data })), callback) - } client.onClose = function(handler: typeof closeEvents[number]) { closeEvents.push(handler) return () => { @@ -171,6 +206,7 @@ export const connect = (urlInfo: LX.Sync.Client.UrlInfo, keyInfo: LX.Sync.Client } } + const initMessage = 'Wait syncing...' client.addEventListener('open', () => { log.info('connect') // const store = getStore() @@ -178,39 +214,14 @@ export const connect = (urlInfo: LX.Sync.Client.UrlInfo, keyInfo: LX.Sync.Client client!.isReady = false sendSyncStatus({ status: false, - message: 'Wait syncing...', - }) - void registerSyncListHandler(client as LX.Sync.Client.Socket).then(() => { - log.info('sync list success') - handleConnection(client as LX.Sync.Client.Socket) - log.info('register list sync service success') - client!.isReady = true - heartbeatTools.failedNum = 0 - sendSyncStatus({ - status: true, - message: '', - }) - }).catch(err => { - if (err.message == 'closed') { - sendSyncStatus({ - status: false, - message: '', - }) - } else { - console.log(err) - log.r_error(err.stack) - sendSyncStatus({ - status: false, - message: err.message, - }) - } + message: initMessage, }) }) client.addEventListener('close', ({ code }) => { const err = new Error('closed') for (const handler of closeEvents) void handler(err) + handleDisconnection() closeEvents = [] - events = {} switch (code) { case SYNC_CLOSE_CODE.normal: // case SYNC_CLOSE_CODE.failed: @@ -218,6 +229,15 @@ export const connect = (urlInfo: LX.Sync.Client.UrlInfo, keyInfo: LX.Sync.Client status: false, message: '', }) + break + case SYNC_CLOSE_CODE.failed: + if (!status.message || status.message == initMessage) { + sendSyncStatus({ + status: false, + message: 'failed', + }) + } + break } }) client.addEventListener('error', ({ message }) => { diff --git a/src/main/modules/sync/client/modules/index.ts b/src/main/modules/sync/client/modules/index.ts index 41f2522438..e484eb633a 100644 --- a/src/main/modules/sync/client/modules/index.ts +++ b/src/main/modules/sync/client/modules/index.ts @@ -1 +1,10 @@ -export { default as list } from './list' +import * as list from './list' +// export * as theme from './theme' + + +export const callObj = Object.assign({}, list.handler) + + +export const modules = { + list, +} diff --git a/src/main/modules/sync/client/modules/list/handler.ts b/src/main/modules/sync/client/modules/list/handler.ts new file mode 100644 index 0000000000..72db2ac32c --- /dev/null +++ b/src/main/modules/sync/client/modules/list/handler.ts @@ -0,0 +1,53 @@ +import { handleRemoteListAction } from '@main/modules/sync/utils' +import { getLocalListData, setLocalListData } from '../../../utils' +import { toMD5 } from '@common/utils/nodejs' +import { removeSelectModeListener, sendCloseSelectMode, sendSelectMode } from '@main/modules/winMain' +import log from '@main/modules/sync/log' + +const logInfo = (eventName: string, success = false) => { + log.info(`[${eventName}]${eventName.replace('list:sync:list_sync_', '').replaceAll('_', ' ')}${success ? ' success' : ''}`) +} +// const logError = (eventName: string, err: Error) => { +// log.error(`[${eventName}]${eventName.replace('list:sync:list_sync_', '').replaceAll('_', ' ')} error: ${err.message}`) +// } + +export const onListSyncAction = async(socket: LX.Sync.Client.Socket, action: LX.Sync.ActionList) => { + if (!socket.isReady) return + await handleRemoteListAction(action) +} + +export const list_sync_get_md5 = async(socket: LX.Sync.Client.Socket) => { + logInfo('list:sync:list_sync_get_md5') + return toMD5(JSON.stringify(await getLocalListData())) +} + +const getSyncMode = async(socket: LX.Sync.Client.Socket): Promise => new Promise((resolve, reject) => { + const handleDisconnect = (err: Error) => { + sendCloseSelectMode() + removeSelectModeListener() + reject(err) + } + let removeEventClose = socket.onClose(handleDisconnect) + sendSelectMode(socket.data.keyInfo.serverName, (mode) => { + if (mode == null) { + reject(new Error('cancel')) + return + } + resolve(mode) + removeSelectModeListener() + removeEventClose() + }) +}) +export const list_sync_get_sync_mode = async(socket: LX.Sync.Client.Socket) => { + return getSyncMode(socket) +} + +export const list_sync_get_list_data = async(socket: LX.Sync.Client.Socket) => { + logInfo('list:sync:list_sync_get_list_data') + return getLocalListData() +} + +export const list_sync_set_list_data = async(socket: LX.Sync.Client.Socket, data: LX.Sync.ListData) => { + logInfo('list:sync:list_sync_set_list_data') + await setLocalListData(data) +} diff --git a/src/main/modules/sync/client/modules/list/index.ts b/src/main/modules/sync/client/modules/list/index.ts index ccbd23a381..273dfea278 100644 --- a/src/main/modules/sync/client/modules/list/index.ts +++ b/src/main/modules/sync/client/modules/list/index.ts @@ -1,7 +1,4 @@ -import initOn from './on' -import initSend from './send' -export default (socket: LX.Sync.Client.Socket) => { - initOn(socket) - initSend(socket) -} +export * as handler from './handler' + +export * from './localEvent' diff --git a/src/main/modules/sync/client/modules/list/localEvent.ts b/src/main/modules/sync/client/modules/list/localEvent.ts new file mode 100644 index 0000000000..bcc3978316 --- /dev/null +++ b/src/main/modules/sync/client/modules/list/localEvent.ts @@ -0,0 +1,20 @@ +import { registerListActionEvent } from '@main/modules/sync/utils' + +let unregisterLocalListAction: (() => void) | null + +export const registerEvent = (socket: LX.Sync.Client.Socket) => { + // socket = _socket + // socket.onClose(() => { + // unregisterLocalListAction?.() + // unregisterLocalListAction = null + // }) + unregisterLocalListAction = registerListActionEvent((action) => { + if (!socket?.isReady) return + void socket.remoteSyncList.onListSyncAction(action) + }) +} + +export const unregisterEvent = () => { + unregisterLocalListAction?.() + unregisterLocalListAction = null +} diff --git a/src/main/modules/sync/client/modules/list/on.ts b/src/main/modules/sync/client/modules/list/on.ts deleted file mode 100644 index dcd248a4cc..0000000000 --- a/src/main/modules/sync/client/modules/list/on.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { handleRemoteListAction } from '@main/modules/sync/utils' - -export default (socket: LX.Sync.Client.Socket) => { - socket.onRemoteEvent('list:sync:action', (action) => { - if (!socket.isReady) return - void handleRemoteListAction(action) - }) -} - diff --git a/src/main/modules/sync/client/modules/list/send.ts b/src/main/modules/sync/client/modules/list/send.ts deleted file mode 100644 index f20f6b4e28..0000000000 --- a/src/main/modules/sync/client/modules/list/send.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { registerListActionEvent } from '@main/modules/sync/utils' - -let socket: LX.Sync.Client.Socket | null -let unregisterLocalListAction: (() => void) | null - - -const sendListAction = (action: LX.Sync.ActionList) => { - // console.log('sendListAction', action.action) - if (!socket?.isReady) return - socket.sendData('list:sync:action', action) -} - -export default (_socket: LX.Sync.Client.Socket) => { - socket = _socket - socket.onClose(() => { - socket = null - unregisterLocalListAction?.() - unregisterLocalListAction = null - }) - unregisterLocalListAction = registerListActionEvent(sendListAction) -} diff --git a/src/main/modules/sync/client/syncList.ts b/src/main/modules/sync/client/syncList.ts deleted file mode 100644 index 3979ffd375..0000000000 --- a/src/main/modules/sync/client/syncList.ts +++ /dev/null @@ -1,73 +0,0 @@ -import log from '../log' -import { getLocalListData, setLocalListData } from '../utils' -import { toMD5 } from '@common/utils/nodejs' -import { removeSelectModeListener, sendCloseSelectMode, sendSelectMode } from '@main/modules/winMain' -import { SYNC_CLOSE_CODE } from '@common/constants' - -const logInfo = (eventName: keyof LX.Sync.ActionSyncSendType, success = false) => { - log.info(`[${eventName as string}]${eventName.replace('list:sync:list_sync_', '').replaceAll('_', ' ')}${success ? ' success' : ''}`) -} -const logError = (eventName: keyof LX.Sync.ActionSyncSendType, err: Error) => { - log.error(`[${eventName as string}]${eventName.replace('list:sync:list_sync_', '').replaceAll('_', ' ')} error: ${err.message}`) -} - -export default async(socket: LX.Sync.Client.Socket) => new Promise((resolve, reject) => { - let listenEvents: Array<() => void> = [] - const unregisterEvents = () => { - while (listenEvents.length) listenEvents.shift()?.() - } - - socket.onClose(() => { - unregisterEvents() - sendCloseSelectMode() - removeSelectModeListener() - reject(new Error('closed')) - }) - listenEvents.push(socket.onRemoteEvent('list:sync:list_sync_get_md5', async() => { - logInfo('list:sync:list_sync_get_md5') - const md5 = toMD5(JSON.stringify(await getLocalListData())) - socket?.sendData('list:sync:list_sync_get_md5', md5, (err) => { - if (err) { - logError('list:sync:list_sync_get_md5', err) - socket.close(SYNC_CLOSE_CODE.failed) - return - } - logInfo('list:sync:list_sync_get_md5', true) - }) - })) - listenEvents.push(socket.onRemoteEvent('list:sync:list_sync_get_list_data', async() => { - logInfo('list:sync:list_sync_get_list_data') - socket?.sendData('list:sync:list_sync_get_list_data', await getLocalListData(), (err) => { - if (err) { - logError('list:sync:list_sync_get_list_data', err) - socket.close(SYNC_CLOSE_CODE.failed) - return - } - logInfo('list:sync:list_sync_get_list_data', true) - }) - })) - listenEvents.push(socket.onRemoteEvent('list:sync:list_sync_get_sync_mode', async() => { - logInfo('list:sync:list_sync_get_sync_mode') - sendSelectMode(socket.data.keyInfo.serverName, (mode) => { - removeSelectModeListener() - socket?.sendData('list:sync:list_sync_get_sync_mode', mode, (err) => { - if (err) { - logError('list:sync:list_sync_get_sync_mode', err) - socket.close(SYNC_CLOSE_CODE.failed) - return - } - logInfo('list:sync:list_sync_get_sync_mode', true) - }) - }) - })) - listenEvents.push(socket.onRemoteEvent('list:sync:list_sync_set_data', async(data) => { - logInfo('list:sync:list_sync_set_data') - await setLocalListData(data) - logInfo('list:sync:list_sync_set_data', true) - })) - listenEvents.push(socket.onRemoteEvent('list:sync:finished', async() => { - unregisterEvents() - resolve() - logInfo('list:sync:finished', true) - })) -}) diff --git a/src/main/modules/sync/client/utils.ts b/src/main/modules/sync/client/utils.ts index 53279ae7a1..d169ce6875 100644 --- a/src/main/modules/sync/client/utils.ts +++ b/src/main/modules/sync/client/utils.ts @@ -1,5 +1,6 @@ import { generateKeyPair } from 'node:crypto' import { httpFetch, type RequestOptions } from '@main/utils/request' +import { decodeData, encodeData } from '../utils' export const request = async(url: string, options: RequestOptions = { }) => { return httpFetch(url, { @@ -74,15 +75,14 @@ export const generateRsaKey = async() => new Promise<{ publicKey: string, privat ) }) - -export const encryptMsg = (keyInfo: LX.Sync.ClientKeyInfo, msg: string): string => { - return msg +export const encryptMsg = async(keyInfo: LX.Sync.ClientKeyInfo, msg: string): Promise => { + return encodeData(msg) // if (!keyInfo) return '' // return aesEncrypt(msg, keyInfo.key, keyInfo.iv) } -export const decryptMsg = (keyInfo: LX.Sync.ClientKeyInfo, enMsg: string): string => { - return enMsg +export const decryptMsg = async(keyInfo: LX.Sync.ClientKeyInfo, enMsg: string): Promise => { + return decodeData(enMsg) // if (!keyInfo) return '' // let msg = '' // try { diff --git a/src/main/modules/sync/server/auth.ts b/src/main/modules/sync/server/auth.ts index 7e15350167..7a21fd73de 100644 --- a/src/main/modules/sync/server/auth.ts +++ b/src/main/modules/sync/server/auth.ts @@ -4,6 +4,7 @@ import querystring from 'node:querystring' import { getIP } from './utils' import { createClientKeyInfo, getClientKeyInfo, saveClientKeyInfo } from '../data' import { aesDecrypt, aesEncrypt, getComputerName, rsaEncrypt } from '../utils' +import { toMD5 } from '@common/utils/nodejs' const requestIps = new Map() @@ -35,7 +36,7 @@ const verifyByKey = (encryptMsg: string, userId: string) => { } const verifyByCode = (encryptMsg: string, password: string) => { - let key = ''.padStart(16, Buffer.from(password).toString('hex')) + let key = toMD5(password).substring(0, 16) // const iv = Buffer.from(key.split('').reverse().join('')).toString('base64') key = Buffer.from(key).toString('base64') // console.log(req.headers.m, authCode, key) diff --git a/src/main/modules/sync/server/modules/index.ts b/src/main/modules/sync/server/modules/index.ts index cd9e351136..e484eb633a 100644 --- a/src/main/modules/sync/server/modules/index.ts +++ b/src/main/modules/sync/server/modules/index.ts @@ -1 +1,10 @@ -export * as list from './list' +import * as list from './list' +// export * as theme from './theme' + + +export const callObj = Object.assign({}, list.handler) + + +export const modules = { + list, +} diff --git a/src/main/modules/sync/server/modules/list.ts b/src/main/modules/sync/server/modules/list.ts deleted file mode 100644 index 2a01925b31..0000000000 --- a/src/main/modules/sync/server/modules/list.ts +++ /dev/null @@ -1,68 +0,0 @@ -// import { throttle } from '@common/utils/common' -// import { sendSyncActionList } from '@main/modules/winMain' -import { SYNC_CLOSE_CODE } from '@common/constants' -import { updateDeviceSnapshotKey } from '../../data' -import { handleRemoteListAction, registerListActionEvent } from '../../utils' -import { createSnapshot, encryptMsg, getCurrentListInfoKey } from '../utils' - -let wss: LX.Sync.Server.SocketServer | null -let removeListener: (() => void) | null - -// type listAction = 'list:action' - -// const addMusic = (orderId, callback) => { -// // ... -// } - -const broadcast = async(key: string, data: any, excludeIds: string[] = []) => { - if (!wss) return - const dataStr = JSON.stringify({ action: 'list:sync:action', data }) - for (const socket of wss.clients) { - if (excludeIds.includes(socket.keyInfo.clientId) || !socket.isReady) continue - socket.send(encryptMsg(socket.keyInfo, dataStr), (err) => { - if (err) { - socket.close(SYNC_CLOSE_CODE.failed) - return - } - updateDeviceSnapshotKey(socket.keyInfo, key) - }) - } -} - -const sendListAction = async(action: LX.Sync.ActionList) => { - console.log('sendListAction', action.action) - // io.sockets - await broadcast(await getCurrentListInfoKey(), action) -} - -export const registerListHandler = (_wss: LX.Sync.Server.SocketServer, socket: LX.Sync.Server.Socket) => { - if (!wss) { - wss = _wss - removeListener = registerListActionEvent(sendListAction) - } - - socket.onRemoteEvent('list:sync:action', (action) => { - if (!socket.isReady) return - // console.log(msg) - void handleRemoteListAction(action).then(updated => { - if (!updated) return - void createSnapshot().then(key => { - if (!key) return - updateDeviceSnapshotKey(socket.keyInfo, key) - void broadcast(key, action, [socket.keyInfo.clientId]) - }) - }) - // socket.broadcast.emit('list:action', { action: 'list_remove', data: { id: 'default', index: 0 } }) - }) - - // socket.on('list:add', addMusic) -} -export const unregisterListHandler = () => { - wss = null - - if (removeListener) { - removeListener() - removeListener = null - } -} - diff --git a/src/main/modules/sync/server/modules/list/handler.ts b/src/main/modules/sync/server/modules/list/handler.ts new file mode 100644 index 0000000000..6376b9b158 --- /dev/null +++ b/src/main/modules/sync/server/modules/list/handler.ts @@ -0,0 +1,86 @@ +// import { throttle } from '@common/utils/common' +// import { sendSyncActionList } from '@main/modules/winMain' +// import { SYNC_CLOSE_CODE } from '@common/constants' +import { updateDeviceSnapshotKey } from '@main/modules/sync/data' +import { handleRemoteListAction } from '../../../utils' +import { createSnapshot } from '../../utils' + +// let wss: LX.Sync.Server.SocketServer | null +// let removeListener: (() => void) | null + +// type listAction = 'list:action' + +// const addMusic = (orderId, callback) => { +// // ... +// } + +// const broadcast = async(key: string, data: any, excludeIds: string[] = []) => { +// if (!wss) return +// const dataStr = JSON.stringify({ action: 'list:sync:action', data }) +// const clients = Array.from(wss.clients).filter(socket => !excludeIds.includes(socket.keyInfo.clientId) && socket.isReady) +// if (!clients.length) return +// const enData = await encryptMsg(null, dataStr) +// for (const socket of clients) { +// if (excludeIds.includes(socket.keyInfo.clientId) || !socket.isReady) continue +// socket.send(enData, (err) => { +// if (err) { +// socket.close(SYNC_CLOSE_CODE.failed) +// return +// } +// updateDeviceSnapshotKey(socket.keyInfo, key) +// }) +// } +// } + +// const sendListAction = async(action: LX.Sync.ActionList) => { +// console.log('sendListAction', action.action) +// // io.sockets +// await broadcast(await getCurrentListInfoKey(), action) +// } + +// export const registerListHandler = (_wss: LX.Sync.Server.SocketServer, socket: LX.Sync.Server.Socket) => { +// if (!wss) { +// wss = _wss +// removeListener = registerListActionEvent(sendListAction) +// } + +// socket.onRemoteEvent('list:sync:action', (action) => { +// if (!socket.isReady) return +// // console.log(msg) +// void handleRemoteListAction(action).then(updated => { +// if (!updated) return +// void createSnapshot().then(key => { +// if (!key) return +// updateDeviceSnapshotKey(socket.keyInfo, key) +// void broadcast(key, action, [socket.keyInfo.clientId]) +// }) +// }) +// // socket.broadcast.emit('list:action', { action: 'list_remove', data: { id: 'default', index: 0 } }) +// }) + +// // socket.on('list:add', addMusic) +// } +// export const unregisterListHandler = () => { +// wss = null + +// if (removeListener) { +// removeListener() +// removeListener = null +// } +// } + +export const onListSyncAction = async(socket: LX.Sync.Server.Socket, action: LX.Sync.ActionList) => { + await handleRemoteListAction(action).then(updated => { + if (!updated) return + console.log(updated) + void createSnapshot().then(key => { + if (!key) return + updateDeviceSnapshotKey(socket.keyInfo, key) + const currentId = socket.keyInfo.clientId + socket.broadcast((client) => { + if (client.keyInfo.clientId == currentId || !client.isReady) return + void client.remoteSyncList.onListSyncAction(action) + }) + }) + }) +} diff --git a/src/main/modules/sync/server/modules/list/index.ts b/src/main/modules/sync/server/modules/list/index.ts new file mode 100644 index 0000000000..59c43c8e61 --- /dev/null +++ b/src/main/modules/sync/server/modules/list/index.ts @@ -0,0 +1,4 @@ +export * as handler from './handler' +export { default as sync } from './sync' + +export * from './localEvent' diff --git a/src/main/modules/sync/server/modules/list/localEvent.ts b/src/main/modules/sync/server/modules/list/localEvent.ts new file mode 100644 index 0000000000..4a14cb3eae --- /dev/null +++ b/src/main/modules/sync/server/modules/list/localEvent.ts @@ -0,0 +1,34 @@ +import { updateDeviceSnapshotKey } from '@main/modules/sync/data' +import { registerListActionEvent } from '../../../utils' +import { getCurrentListInfoKey } from '../../utils' + +// let socket: LX.Sync.Server.Socket | null +let unregisterLocalListAction: (() => void) | null + + +const sendListAction = async(wss: LX.Sync.Server.SocketServer, action: LX.Sync.ActionList) => { + // console.log('sendListAction', action.action) + const key = await getCurrentListInfoKey() + for (const client of wss.clients) { + if (!client.isReady) return + void client.remoteSyncList.onListSyncAction(action).then(() => { + updateDeviceSnapshotKey(client.keyInfo, key) + }) + } +} + +export const registerEvent = (wss: LX.Sync.Server.SocketServer) => { + // socket = _socket + // socket.onClose(() => { + // unregisterLocalListAction?.() + // unregisterLocalListAction = null + // }) + unregisterLocalListAction = registerListActionEvent((action) => { + void sendListAction(wss, action) + }) +} + +export const unregisterEvent = () => { + unregisterLocalListAction?.() + unregisterLocalListAction = null +} diff --git a/src/main/modules/sync/server/syncList.ts b/src/main/modules/sync/server/modules/list/sync.ts similarity index 82% rename from src/main/modules/sync/server/syncList.ts rename to src/main/modules/sync/server/modules/list/sync.ts index 82248c8b3c..27b26d396d 100644 --- a/src/main/modules/sync/server/syncList.ts +++ b/src/main/modules/sync/server/modules/list/sync.ts @@ -1,8 +1,8 @@ import { SYNC_CLOSE_CODE } from '@common/constants' +import { getLocalListData, setLocalListData } from '@main/modules/sync/utils' import { removeSelectModeListener, sendCloseSelectMode, sendSelectMode } from '@main/modules/winMain' -import { getSnapshot, updateDeviceSnapshotKey } from '../data' -import { getLocalListData, setLocalListData } from '../utils' -import { createSnapshot, encryptMsg, getCurrentListInfoKey } from './utils' +import { createSnapshot, getCurrentListInfoKey } from '../../utils' +import { getSnapshot, updateDeviceSnapshotKey } from '@main/modules/sync/data' const handleSetLocalListData = async(listData: LX.Sync.ListData) => { @@ -12,7 +12,7 @@ const handleSetLocalListData = async(listData: LX.Sync.ListData) => { // type ListInfoType = LX.List.UserListInfoFull | LX.List.MyDefaultListInfoFull | LX.List.MyLoveListInfoFull -let wss: LX.Sync.Server.SocketServer | null +// let wss: LX.Sync.Server.SocketServer | null let syncingId: string | null = null const wait = async(time = 1000) => await new Promise((resolve, reject) => setTimeout(resolve, time)) @@ -24,37 +24,14 @@ const patchListData = (listData: Partial): LX.Sync.ListData => }, listData) } -const getRemoteListData = async(socket: LX.Sync.Server.Socket): Promise => await new Promise((resolve, reject) => { +const getRemoteListData = async(socket: LX.Sync.Server.Socket): Promise => { console.log('getRemoteListData') - let removeEventClose = socket.onClose(reject) - let removeEvent = socket.onRemoteEvent('list:sync:list_sync_get_list_data', (listData) => { - resolve(patchListData(listData)) - removeEventClose() - removeEvent() - }) - socket.sendData('list:sync:list_sync_get_list_data', undefined, (err) => { - if (!err) return - reject(err) - removeEventClose() - removeEvent() - }) -}) - -const getRemoteListMD5 = async(socket: LX.Sync.Server.Socket): Promise => await new Promise((resolve, reject) => { - let removeEventClose = socket.onClose(reject) - let removeEvent = socket.onRemoteEvent('list:sync:list_sync_get_md5', (md5) => { - resolve(md5) - removeEventClose() - removeEvent() - }) - socket.sendData('list:sync:list_sync_get_md5', undefined, (err) => { - if (!err) return - reject(err) - removeEventClose() - removeEvent() - }) -}) + return patchListData(await socket.remoteSyncList.list_sync_get_list_data()) +} +const getRemoteListMD5 = async(socket: LX.Sync.Server.Socket): Promise => { + return socket.remoteSyncList.list_sync_get_md5() +} const getSyncMode = async(socket: LX.Sync.Server.Socket): Promise => new Promise((resolve, reject) => { const handleDisconnect = (err: Error) => { @@ -62,53 +39,38 @@ const getSyncMode = async(socket: LX.Sync.Server.Socket): Promise removeSelectModeListener() reject(err) } + let removeEventClose = socket.onClose(handleDisconnect) sendSelectMode(socket.keyInfo.deviceName, (mode) => { + if (mode == null) { + reject(new Error('cancel')) + return + } + resolve(mode) removeSelectModeListener() removeEventClose() - resolve(mode) }) - let removeEventClose = socket.onClose(handleDisconnect) }) -const finishedSync = async(socket: LX.Sync.Server.Socket) => new Promise((resolve, reject) => { - socket.sendData('list:sync:finished', undefined, (err) => { - if (err) reject(err) - else resolve() - }) -}) +const finishedSync = async(socket: LX.Sync.Server.Socket) => { + await socket.remoteSyncList.list_sync_finished() +} -const sendDataPromise = async(socket: LX.Sync.Server.Socket, dataStr: string, key: string) => new Promise((resolve, reject) => { - socket.send(encryptMsg(socket.keyInfo, dataStr), (err) => { - if (err) { - socket.close(SYNC_CLOSE_CODE.failed) - resolve() - return - } - updateDeviceSnapshotKey(socket.keyInfo, key) - resolve() - }) -}) -const overwriteRemoteListData = async(listData: LX.Sync.ListData, key: string, excludeIds: string[] = []) => { - if (!wss) return - const dataStr = JSON.stringify({ action: 'list:sync:action', data: { action: 'list_data_overwrite', data: listData } }) +const overwriteRemoteListData = async(socket: LX.Sync.Server.Socket, listData: LX.Sync.ListData, key: string, excludeIds: string[] = []) => { + const action = { action: 'list_data_overwrite', data: listData } as const const tasks: Array> = [] - for (const socket of wss.clients) { - if (excludeIds.includes(socket.keyInfo.clientId) || !socket.isReady) continue - tasks.push(sendDataPromise(socket, dataStr, key)) - } + socket.broadcast((client) => { + if (excludeIds.includes(client.keyInfo.clientId) || !client.isReady) return + tasks.push(client.remoteSyncList.onListSyncAction(action).then(() => { + updateDeviceSnapshotKey(socket.keyInfo, key) + })) + }) if (!tasks.length) return await Promise.all(tasks) } -const setRemotelList = async(socket: LX.Sync.Server.Socket, listData: LX.Sync.ListData, key: string): Promise => new Promise((resolve, reject) => { - socket.sendData('list:sync:list_sync_set_data', listData, (err) => { - if (err) { - reject(err) - return - } - updateDeviceSnapshotKey(socket.keyInfo, key) - resolve() - }) -}) +const setRemotelList = async(socket: LX.Sync.Server.Socket, listData: LX.Sync.ListData, key: string): Promise => { + await socket.remoteSyncList.list_sync_set_list_data(listData) + updateDeviceSnapshotKey(socket.keyInfo, key) +} type UserDataObj = Map const createUserListDataObj = (listData: LX.Sync.ListData): UserDataObj => { @@ -268,7 +230,7 @@ const handleSyncList = async(socket: LX.Sync.Server.Socket) => { let key if (requiredUpdateLocalListData) { key = await handleSetLocalListData(mergedList) - await overwriteRemoteListData(mergedList, key, [socket.keyInfo.clientId]) + await overwriteRemoteListData(socket, mergedList, key, [socket.keyInfo.clientId]) if (!requiredUpdateRemoteListData) updateDeviceSnapshotKey(socket.keyInfo, key) } if (requiredUpdateRemoteListData) { @@ -282,7 +244,7 @@ const handleSyncList = async(socket: LX.Sync.Server.Socket) => { let key: string if (remoteListData.defaultList.length || remoteListData.loveList.length || remoteListData.userList.length) { key = await handleSetLocalListData(remoteListData) - await overwriteRemoteListData(remoteListData, key, [socket.keyInfo.clientId]) + await overwriteRemoteListData(socket, remoteListData, key, [socket.keyInfo.clientId]) } key ??= await getCurrentListInfoKey() updateDeviceSnapshotKey(socket.keyInfo, key) @@ -399,7 +361,7 @@ const handleMergeListDataFromSnapshot = async(socket: LX.Sync.Server.Socket, sna newListData.userList = newUserList const key = await handleSetLocalListData(newListData) await setRemotelList(socket, newListData, key) - await overwriteRemoteListData(newListData, key, [socket.keyInfo.clientId]) + await overwriteRemoteListData(socket, newListData, key, [socket.keyInfo.clientId]) } @@ -416,14 +378,41 @@ const syncList = async(socket: LX.Sync.Server.Socket) => { } -export default async(_wss: LX.Sync.Server.SocketServer, socket: LX.Sync.Server.Socket) => { - if (!wss) { - wss = _wss - _wss.addListener('close', () => { - wss = null - }) - } +// export default async(_wss: LX.Sync.Server.SocketServer, socket: LX.Sync.Server.Socket) => { +// if (!wss) { +// wss = _wss +// _wss.addListener('close', () => { +// wss = null +// }) +// } + +// let disconnected = false +// socket.onClose(() => { +// disconnected = true +// if (syncingId == socket.keyInfo.clientId) syncingId = null +// }) + +// while (true) { +// if (disconnected) throw new Error('disconnected') +// if (!syncingId) break +// await wait() +// } + +// syncingId = socket.keyInfo.clientId +// await syncList(socket).then(async() => { +// // if (newListData) registerUpdateSnapshotTask(socket, { ...newListData }) +// return finishedSync(socket) +// }).finally(() => { +// syncingId = null +// }) +// } + +// const removeSnapshot = async(keyInfo: LX.Sync.KeyInfo) => { +// const filePath = getSnapshotFilePath(keyInfo) +// await fsPromises.unlink(filePath) +// } +export default async(socket: LX.Sync.Server.Socket) => { let disconnected = false socket.onClose(() => { disconnected = true @@ -438,14 +427,8 @@ export default async(_wss: LX.Sync.Server.SocketServer, socket: LX.Sync.Server.S syncingId = socket.keyInfo.clientId await syncList(socket).then(async() => { - // if (newListData) registerUpdateSnapshotTask(socket, { ...newListData }) return finishedSync(socket) }).finally(() => { syncingId = null }) } - -// const removeSnapshot = async(keyInfo: LX.Sync.KeyInfo) => { -// const filePath = getSnapshotFilePath(keyInfo) -// await fsPromises.unlink(filePath) -// } diff --git a/src/main/modules/sync/server/server.ts b/src/main/modules/sync/server/server.ts index 8a597713ff..dcc278c48b 100644 --- a/src/main/modules/sync/server/server.ts +++ b/src/main/modules/sync/server/server.ts @@ -1,15 +1,15 @@ import http, { type IncomingMessage } from 'node:http' import url from 'node:url' import { WebSocketServer } from 'ws' -import * as modules from './modules' +import { modules, callObj } from './modules' import { authCode, authConnect } from './auth' -import syncList from './syncList' import log from '../log' import { SYNC_CLOSE_CODE, SYNC_CODE } from '@common/constants' import { decryptMsg, encryptMsg, generateCode as handleGenerateCode } from './utils' import { getAddress } from '../utils' import { sendServerStatus } from '@main/modules/winMain/index' import { getClientKeyInfo, getServerId, saveClientKeyInfo } from '../data' +import { createMsg2call } from 'message2call' let status: LX.Sync.ServerStatus = { @@ -40,6 +40,24 @@ const codeTools: { }, } +const syncData = async(socket: LX.Sync.Server.Socket) => { + for (const module of Object.values(modules)) { + await module.sync(socket) + } +} + +const registerLocalSyncEvent = async(wss: LX.Sync.Server.SocketServer) => { + for (const module of Object.values(modules)) { + module.registerEvent(wss) + } +} + +const unregisterLocalSyncEvent = () => { + for (const module of Object.values(modules)) { + module.unregisterEvent() + } +} + const checkDuplicateClient = (newSocket: LX.Sync.Server.Socket) => { for (const client of [...wss!.clients]) { if (client === newSocket || client.keyInfo.clientId != newSocket.keyInfo.clientId) continue @@ -65,7 +83,7 @@ const handleConnection = async(socket: LX.Sync.Server.Socket, request: IncomingM checkDuplicateClient(socket) try { - await syncList(wss as LX.Sync.Server.SocketServer, socket) + await syncData(socket) } catch (err) { // console.log(err) log.warn(err) @@ -83,9 +101,6 @@ const handleConnection = async(socket: LX.Sync.Server.Socket, request: IncomingM // console.log('connection', keyInfo.deviceName) log.info('connection', keyInfo.deviceName) // console.log(socket.handshake.query) - for (const module of Object.values(modules)) { - module.registerListHandler(wss as LX.Sync.Server.SocketServer, socket) - } socket.isReady = true } @@ -93,9 +108,6 @@ const handleConnection = async(socket: LX.Sync.Server.Socket, request: IncomingM const handleUnconnection = () => { console.log('unconnection') // console.log(socket.handshake.query) - for (const module of Object.values(modules)) { - module.unregisterListHandler() - } } const authConnection = (req: http.IncomingMessage, callback: (err: string | null | undefined, success: boolean) => void) => { @@ -157,52 +169,73 @@ const handleStartServer = async(port = 9527, ip = '0.0.0.0') => await new Promis // const events = new Map void>>() // const events = new Map void>>() - let events: Partial<{ [K in keyof LX.Sync.ActionSyncType]: Array<(data: LX.Sync.ActionSyncType[K]) => void> }> = {} + // let events: Partial<{ [K in keyof LX.Sync.ActionSyncType]: Array<(data: LX.Sync.ActionSyncType[K]) => void> }> = {} let closeEvents: Array<(err: Error) => (void | Promise)> = [] + const msg2call = createMsg2call({ + funcsObj: callObj, + timeout: 120 * 1000, + sendMessage(data) { + void encryptMsg(socket.keyInfo, JSON.stringify(data)).then((data) => { + // console.log('sendData', eventName) + socket.send(data) + }).catch(err => { + log.error('encrypt message error:', err) + log.error(err.message) + socket.close(SYNC_CLOSE_CODE.failed) + }) + }, + onCallBeforeParams(rawArgs) { + return [socket, ...rawArgs] + }, + onError(error, path, groupName) { + const name = groupName ?? '' + log.error(`sync call ${name} ${path.join('.')} error:`, error) + if (groupName == null) return + socket.close(SYNC_CLOSE_CODE.failed) + }, + }) + socket.remote = msg2call.remote + socket.remoteSyncList = msg2call.createSyncRemote('list') socket.addEventListener('message', ({ data }) => { - if (typeof data === 'string') { - let syncData: LX.Sync.ActionSync + if (typeof data != 'string') return + void decryptMsg(socket.keyInfo, data).then((data) => { + let syncData: any try { - syncData = JSON.parse(decryptMsg(socket.keyInfo, data)) + syncData = JSON.parse(data) } catch (err) { log.error('parse message error:', err) socket.close(SYNC_CLOSE_CODE.failed) return } - const handlers = events[syncData.action] - if (handlers) { - // @ts-expect-error - for (const handler of handlers) handler(syncData.data) - } - } + msg2call.onMessage(syncData) + }).catch(err => { + log.error('decrypt message error:', err) + log.error(err.message) + socket.close(SYNC_CLOSE_CODE.failed) + }) }) socket.addEventListener('close', () => { + if (!socket.isReady) { + const queryData = url.parse(request.url as string, true).query as Record + log.info('deconnection', queryData.i) + return + } const err = new Error('closed') for (const handler of closeEvents) void handler(err) - events = {} + // events = {} closeEvents = [] if (!status.devices.length) handleUnconnection() log.info('deconnection', socket.keyInfo.deviceName) }) - socket.onRemoteEvent = function(eventName, handler) { - let eventArr = events[eventName] - if (!eventArr) events[eventName] = eventArr = [] - // let eventArr = events.get(eventName) - // if (!eventArr) events.set(eventName, eventArr = []) - eventArr.push(handler) - - return () => { - eventArr!.splice(eventArr!.indexOf(handler), 1) - } - } socket.onClose = function(handler: typeof closeEvents[number]) { closeEvents.push(handler) return () => { closeEvents.splice(closeEvents.indexOf(handler), 1) } } - socket.sendData = function(eventName, data, callback) { - socket.send(encryptMsg(socket.keyInfo, JSON.stringify({ action: eventName, data })), callback) + socket.broadcast = function(handler) { + if (!wss) return + for (const client of wss.clients) handler(client) } void handleConnection(socket, request) }) @@ -256,6 +289,7 @@ const handleStartServer = async(port = 9527, ip = '0.0.0.0') => await new Promis const bind = typeof addr == 'string' ? `pipe ${addr}` : `port ${addr.port}` log.info(`Listening on ${ip} ${bind}`) resolve(null) + void registerLocalSyncEvent(wss as LX.Sync.Server.SocketServer) }) httpServer.listen(port, ip) @@ -264,6 +298,7 @@ const handleStartServer = async(port = 9527, ip = '0.0.0.0') => await new Promis const handleStopServer = async() => new Promise((resolve, reject) => { if (!wss) return for (const client of wss.clients) client.close(SYNC_CLOSE_CODE.normal) + unregisterLocalSyncEvent() wss.close() wss = null httpServer.close((err) => { diff --git a/src/main/modules/sync/server/utils.ts b/src/main/modules/sync/server/utils.ts index 364299b207..66e9885e49 100644 --- a/src/main/modules/sync/server/utils.ts +++ b/src/main/modules/sync/server/utils.ts @@ -6,7 +6,7 @@ import { saveSnapshotInfo, type SnapshotInfo, } from '../data' -import { getLocalListData } from '../utils' +import { decodeData, encodeData, getLocalListData } from '../utils' export const generateCode = (): string => { return Math.random().toString().substring(2, 8) @@ -16,14 +16,18 @@ export const getIP = (request: http.IncomingMessage) => { return request.socket.remoteAddress } -export const encryptMsg = (keyInfo: LX.Sync.ServerKeyInfo, msg: string): string => { - return msg +export const encryptMsg = async(keyInfo: LX.Sync.ServerKeyInfo | null, msg: string): Promise => { + return encodeData(msg) + // console.log('enmsg raw: ', msg.length, 'en: ', len.length) + // return len // if (!keyInfo) return '' // return aesEncrypt(msg, keyInfo.key, keyInfo.iv) } -export const decryptMsg = (keyInfo: LX.Sync.ServerKeyInfo, enMsg: string): string => { - return enMsg +export const decryptMsg = async(keyInfo: LX.Sync.ServerKeyInfo | null, enMsg: string): Promise => { + return decodeData(enMsg) + // console.log('decmsg raw: ', len.length, 'en: ', enMsg.length) + // return len // if (!keyInfo) return '' // let msg = '' // try { diff --git a/src/main/modules/sync/utils.ts b/src/main/modules/sync/utils.ts index 2bae352c06..31006f261a 100644 --- a/src/main/modules/sync/utils.ts +++ b/src/main/modules/sync/utils.ts @@ -1,5 +1,6 @@ import { createCipheriv, createDecipheriv, publicEncrypt, privateDecrypt, constants } from 'node:crypto' import os, { networkInterfaces } from 'node:os' +import zlib from 'node:zlib' import cp from 'node:child_process' import { LIST_IDS } from '@common/constants' @@ -40,6 +41,38 @@ export const getComputerName = () => { return name } +const gzip = async(data: string) => new Promise((resolve, reject) => { + zlib.gzip(data, (err, buf) => { + if (err) { + reject(err) + return + } + resolve(buf.toString('base64')) + }) +}) +const unGzip = async(data: string) => new Promise((resolve, reject) => { + zlib.gunzip(Buffer.from(data, 'base64'), (err, buf) => { + if (err) { + reject(err) + return + } + resolve(buf.toString()) + }) +}) + +export const encodeData = async(data: string) => { + return data.length > 1024 + ? 'cg_' + await gzip(data) + : data +} + +export const decodeData = async(enData: string) => { + return enData.substring(0, 3) == 'cg_' + ? await unGzip(enData.replace('cg_', '')) + : enData +} + + export const aesEncrypt = (text: string, key: string) => { const cipher = createCipheriv('aes-128-ecb', Buffer.from(key, 'base64'), '') return Buffer.concat([cipher.update(Buffer.from(text)), cipher.final()]).toString('base64') diff --git a/src/main/modules/winMain/rendererEvent/sync.ts b/src/main/modules/winMain/rendererEvent/sync.ts index 43a4a1b743..5da172b6e2 100644 --- a/src/main/modules/winMain/rendererEvent/sync.ts +++ b/src/main/modules/winMain/rendererEvent/sync.ts @@ -3,7 +3,7 @@ import { WIN_MAIN_RENDERER_EVENT_NAME } from '@common/ipcNames' import { startServer, stopServer, getServerStatus, generateCode, connectServer, disconnectServer, getClientStatus } from '@main/modules/sync' import { sendEvent } from '../main' -let selectModeListenr: ((mode: LX.Sync.Mode) => void) | null = null +let selectModeListenr: ((mode: LX.Sync.Mode | null) => void) | null = null export default () => { mainHandle(WIN_MAIN_RENDERER_EVENT_NAME.sync_action, async({ params: data }) => { @@ -18,7 +18,10 @@ export default () => { case 'get_client_status': return getClientStatus() case 'generate_code': return generateCode() case 'select_mode': - if (selectModeListenr) selectModeListenr(data.data) + if (selectModeListenr) { + selectModeListenr(data.data) + selectModeListenr = null + } break default: break @@ -43,11 +46,12 @@ export const sendServerStatus = (status: LX.Sync.ServerStatus) => { data: status, }) } -export const sendSelectMode = (deviceName: string, listener: (mode: LX.Sync.Mode) => void) => { +export const sendSelectMode = (deviceName: string, listener: (mode: LX.Sync.Mode | null) => void) => { selectModeListenr = listener sendSyncAction({ action: 'select_mode', data: deviceName }) } export const removeSelectModeListener = () => { + if (selectModeListenr) selectModeListenr(null) selectModeListenr = null } export const sendCloseSelectMode = () => { diff --git a/src/main/types/common.d.ts b/src/main/types/common.d.ts index 28e03daaf2..310888266c 100644 --- a/src/main/types/common.d.ts +++ b/src/main/types/common.d.ts @@ -3,6 +3,7 @@ import '@common/types/app_setting' import '@common/types/common' import '@common/types/user_api' import '@common/types/sync' +import '@common/types/sync_common' import '@common/types/list' import '@common/types/download_list' import '@common/types/music' diff --git a/src/main/types/sync.d.ts b/src/main/types/sync.d.ts index 547ac43ca6..21440a992a 100644 --- a/src/main/types/sync.d.ts +++ b/src/main/types/sync.d.ts @@ -14,18 +14,8 @@ declare global { urlInfo: UrlInfo } - onRemoteEvent: ( - eventName: T, - handler: (data: LX.Sync.ActionSyncSendType[T]) => (void | Promise) - ) => () => void - onClose: (handler: (err: Error) => (void | Promise)) => () => void - - sendData: ( - eventName: T, - data?: LX.Sync.ActionSyncType[T], - callback?: (err?: Error) => void - ) => void + remoteSyncList: LX.Sync.ServerActions } interface UrlInfo { @@ -38,22 +28,13 @@ declare global { namespace Server { interface Socket extends WS.WebSocket { isAlive?: boolean - isMobile: boolean isReady: boolean - keyInfo: LX.Sync.ServerKeyInfo - - onRemoteEvent: ( - eventName: T, - handler: (data: LX.Sync.ActionSyncType[T]) => void - ) => () => void - + keyInfo: ServerKeyInfo onClose: (handler: (err: Error) => (void | Promise)) => () => void + broadcast: (handler: (client: Socket) => void) => void - sendData: ( - eventName: T, - data?: LX.Sync.ActionSyncSendType[T], - callback?: (err?: Error) => void - ) => void + remote: LX.Sync.ClientActions + remoteSyncList: LX.Sync.ClientActions } type SocketServer = WS.Server }