修改同步逻辑

This commit is contained in:
lyswhut 2023-08-28 13:39:23 +08:00
parent 71b84fb10d
commit c6f4bfa875
45 changed files with 604 additions and 482 deletions

View File

@ -8,6 +8,7 @@ module.exports = {
'vue', 'vue',
'@types/ws', '@types/ws',
// 'eslint-config-standard-with-typescript', // 'eslint-config-standard-with-typescript',
'typescript', // https://github.com/microsoft/TypeScript/pull/54567
], ],
// target: 'newest', // target: 'newest',
@ -24,10 +25,6 @@ module.exports = {
// target: 'minor', // target: 'minor',
// filter: [ // filter: [
// 'eslint-plugin-n',
// 'electron', // 'electron',
// 'eslint-config-standard-with-typescript',
// '@typescript-eslint/eslint-plugin',
// '@typescript-eslint/parser',
// ], // ],
} }

503
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "lx-music-desktop", "name": "lx-music-desktop",
"version": "2.4.0-beta.9", "version": "2.4.0-beta.10",
"description": "一个免费的音乐查找助手", "description": "一个免费的音乐查找助手",
"main": "./dist/main.js", "main": "./dist/main.js",
"productName": "lx-music-desktop", "productName": "lx-music-desktop",
@ -205,14 +205,14 @@
}, },
"homepage": "https://github.com/lyswhut/lx-music-desktop#readme", "homepage": "https://github.com/lyswhut/lx-music-desktop#readme",
"devDependencies": { "devDependencies": {
"@babel/core": "^7.22.10", "@babel/core": "^7.22.11",
"@babel/eslint-parser": "^7.22.10", "@babel/eslint-parser": "^7.22.11",
"@babel/plugin-proposal-class-properties": "^7.18.6", "@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-transform-modules-umd": "^7.22.5", "@babel/plugin-transform-modules-umd": "^7.22.5",
"@babel/plugin-transform-runtime": "^7.22.10", "@babel/plugin-transform-runtime": "^7.22.10",
"@babel/preset-env": "^7.22.10", "@babel/preset-env": "^7.22.10",
"@babel/preset-typescript": "^7.22.5", "@babel/preset-typescript": "^7.22.11",
"@tsconfig/recommended": "^1.0.2", "@tsconfig/recommended": "^1.0.2",
"@types/better-sqlite3": "^7.6.4", "@types/better-sqlite3": "^7.6.4",
"@types/needle": "^3.2.0", "@types/needle": "^3.2.0",
@ -230,15 +230,15 @@
"css-loader": "^6.8.1", "css-loader": "^6.8.1",
"css-minimizer-webpack-plugin": "^5.0.1", "css-minimizer-webpack-plugin": "^5.0.1",
"del": "^6.1.1", "del": "^6.1.1",
"electron": "^22.3.21", "electron": "^22.3.22",
"electron-builder": "^24.6.3", "electron-builder": "^24.6.4",
"electron-debug": "^3.2.0", "electron-debug": "^3.2.0",
"electron-devtools-installer": "^3.2.0", "electron-devtools-installer": "^3.2.0",
"electron-to-chromium": "^1.4.496", "electron-to-chromium": "^1.4.503",
"electron-updater": "^6.1.4", "electron-updater": "^6.1.4",
"eslint": "^8.47.0", "eslint": "^8.48.0",
"eslint-config-standard": "^17.1.0", "eslint-config-standard": "^17.1.0",
"eslint-config-standard-with-typescript": "^38.1.0", "eslint-config-standard-with-typescript": "^39.0.0",
"eslint-formatter-friendly": "github:lyswhut/eslint-friendly-formatter#2170d1320e2fad13615a9dcf229669f0bb473a53", "eslint-formatter-friendly": "github:lyswhut/eslint-friendly-formatter#2170d1320e2fad13615a9dcf229669f0bb473a53",
"eslint-plugin-html": "^7.1.0", "eslint-plugin-html": "^7.1.0",
"eslint-plugin-vue": "^9.17.0", "eslint-plugin-vue": "^9.17.0",
@ -274,19 +274,19 @@
}, },
"dependencies": { "dependencies": {
"@simonwep/pickr": "^1.8.2", "@simonwep/pickr": "^1.8.2",
"better-sqlite3": "^8.5.1", "better-sqlite3": "^8.5.2",
"bufferutil": "^4.0.7", "bufferutil": "^4.0.7",
"comlink": "~4.3.1", "comlink": "~4.3.1",
"crypto-js": "^4.1.1", "crypto-js": "^4.1.1",
"electron-font-manager": "github:lyswhut/electron-font-manager#6d2f5ecf850c4fe34812b9394913680462ee0dae", "electron-font-manager": "github:lyswhut/electron-font-manager#6d2f5ecf850c4fe34812b9394913680462ee0dae",
"electron-log": "^5.0.0-beta.25", "electron-log": "^5.0.0-beta.28",
"electron-store": "^8.1.0", "electron-store": "^8.1.0",
"font-list": "^1.5.0", "font-list": "^1.5.1",
"iconv-lite": "^0.6.3", "iconv-lite": "^0.6.3",
"image-size": "^1.0.2", "image-size": "^1.0.2",
"jschardet": "^3.0.0", "jschardet": "^3.0.0",
"long": "^5.2.3", "long": "^5.2.3",
"message2call": "^0.1.0", "message2call": "^0.1.2",
"music-metadata": "^8.1.4", "music-metadata": "^8.1.4",
"needle": "github:lyswhut/needle#93299ac841b7e9a9f82ca7279b88aaaeda404060", "needle": "github:lyswhut/needle#93299ac841b7e9a9f82ca7279b88aaaeda404060",
"node-id3": "^0.2.6", "node-id3": "^0.2.6",

View File

@ -30,5 +30,5 @@
### 其他 ### 其他
- 更新 electron 到 v22.3.21 - 更新 electron 到 v22.3.22
- 重构同步服务端功能部分代码,使其更易扩展新功能 - 重构同步服务端功能部分代码,使其更易扩展新功能

View File

@ -76,24 +76,3 @@ export const DOWNLOAD_STATUS = {
} as const } as const
export const QUALITYS = ['flac24bit', 'flac', 'wav', 'ape', '320k', '192k', '128k'] as const export const QUALITYS = ['flac24bit', 'flac', 'wav', 'ape', '320k', '192k', '128k'] as const
export const SYNC_CODE = {
helloMsg: 'Hello~::^-^::~v4~',
idPrefix: 'OjppZDo6',
authMsg: 'lx-music auth::',
authFailed: 'Auth failed',
missingAuthCode: 'Missing auth code',
getServiceIdFailed: 'Get service id failed',
connectServiceFailed: 'Connect service failed',
connecting: 'Connecting...',
unknownServiceAddress: 'Unknown service address',
msgBlockedIp: 'Blocked IP',
msgConnect: 'lx-music connect',
msgAuthFailed: 'Auth failed',
} as const
export const SYNC_CLOSE_CODE = {
normal: 1000,
failed: 4100,
} as const

View File

@ -41,7 +41,7 @@ export const SYNC_CLOSE_CODE = {
failed: 4100, failed: 4100,
} as const } as const
export const TRANS_MODE: Readonly<Record<LX.Sync.ListSyncMode, LX.Sync.ListSyncMode>> = { export const TRANS_MODE: Readonly<Record<LX.Sync.List.SyncMode, LX.Sync.List.SyncMode>> = {
merge_local_remote: 'merge_remote_local', merge_local_remote: 'merge_remote_local',
merge_remote_local: 'merge_local_remote', merge_remote_local: 'merge_local_remote',
overwrite_local_remote: 'overwrite_remote_local', overwrite_local_remote: 'overwrite_remote_local',
@ -64,3 +64,7 @@ export const File = {
syncAuthKeysJSON: 'syncAuthKey.json', syncAuthKeysJSON: 'syncAuthKey.json',
} as const } as const
export const FeaturesList = [
'list',
] as const

View File

@ -139,19 +139,5 @@ declare namespace LX {
userList: UserListInfoFull[] userList: UserListInfoFull[]
tempList: LX.Music.MusicInfo[] tempList: LX.Music.MusicInfo[]
} }
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>
} }
} }

34
src/common/types/list_sync.d.ts vendored Normal file
View File

@ -0,0 +1,34 @@
declare namespace LX {
namespace Sync {
namespace List {
interface ListInfo {
lastSyncDate?: number
snapshotKey: string
}
type ActionList = LX.Sync.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 ListData = Omit<LX.List.ListDataFull, 'tempList'>
type SyncMode = 'merge_local_remote'
| 'merge_remote_local'
| 'overwrite_local_remote'
| 'overwrite_remote_local'
| 'overwrite_local_remote_full'
| 'overwrite_remote_local_full'
// | 'none'
| 'cancel'
}
}
}

View File

@ -24,48 +24,13 @@ declare namespace LX {
| SyncAction<'client_status', ClientStatus> | SyncAction<'client_status', ClientStatus>
| SyncAction<'server_status', ServerStatus> | SyncAction<'server_status', ServerStatus>
type SyncServiceActions = SyncAction<'select_mode', ListSyncMode> type SyncServiceActions = SyncAction<'select_mode', LX.Sync.List.SyncMode>
| SyncAction<'get_server_status'> | SyncAction<'get_server_status'>
| SyncAction<'get_client_status'> | SyncAction<'get_client_status'>
| SyncAction<'generate_code'> | SyncAction<'generate_code'>
| SyncAction<'enable_server', EnableServer> | SyncAction<'enable_server', EnableServer>
| SyncAction<'enable_client', EnableClient> | SyncAction<'enable_client', EnableClient>
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 ActionSync = SyncAction<'list:sync:list_sync_get_md5', string>
| SyncAction<'list:sync:list_sync_get_list_data', ListData>
| SyncAction<'list:sync:list_sync_get_sync_mode', Mode>
| SyncAction<'list:sync:action', ActionList>
// | SyncAction<'finished'>
type ActionSyncType = Actions<ActionSync>
type ActionSyncSend = SyncAction<'list:sync:list_sync_get_md5'>
| SyncAction<'list:sync:list_sync_get_list_data'>
| SyncAction<'list:sync:list_sync_get_sync_mode'>
| SyncAction<'list:sync:list_sync_set_data', LX.Sync.ListData>
| SyncAction<'list:sync:action', ActionList>
| SyncAction<'list:sync:finished'>
type ActionSyncSendType = Actions<ActionSyncSend>
interface List {
action: string
data: any
}
interface ServerStatus { interface ServerStatus {
status: boolean status: boolean
message: string message: string
@ -94,20 +59,10 @@ declare namespace LX {
isMobile: boolean isMobile: boolean
} }
interface ListInfo { type ServerType = 'desktop-app' | 'server'
lastSyncDate?: number interface EnabledFeatures {
snapshotKey: string list: boolean
} }
type ListData = Omit<LX.List.ListDataFull, 'tempList'> type SupportedFeatures = Partial<{ [k in keyof EnabledFeatures]: number }>
type ListSyncMode = 'merge_local_remote'
| 'merge_remote_local'
| 'overwrite_local_remote'
| 'overwrite_remote_local'
| 'overwrite_local_remote_full'
| 'overwrite_remote_local_full'
// | 'none'
| 'cancel'
} }
} }

View File

@ -1,17 +0,0 @@
declare namespace LX {
namespace Sync {
type ServerActions = WarpPromiseRecord<{
onListSyncAction: (action: LX.List.ActionList) => void
}>
type ClientActions = WarpPromiseRecord<{
onListSyncAction: (action: LX.List.ActionList) => void
list_sync_get_md5: () => string
list_sync_get_sync_mode: () => ListSyncMode
list_sync_get_list_data: () => ListData
list_sync_set_list_data: (data: ListData) => void
list_sync_finished: () => void
}>
}
}

View File

@ -1,9 +1,9 @@
import { request, generateRsaKey } from './utils' import { request, generateRsaKey } from './utils'
import { getSyncAuthKey, setSyncAuthKey } from './data' import { getSyncAuthKey, setSyncAuthKey } from './data'
import { SYNC_CODE } from '@common/constants'
import log from '../log' import log from '../log'
import { aesDecrypt, aesEncrypt, getComputerName, rsaDecrypt } from '../utils' import { aesDecrypt, aesEncrypt, getComputerName, rsaDecrypt } from '../utils'
import { toMD5 } from '@common/utils/nodejs' import { toMD5 } from '@common/utils/nodejs'
import { SYNC_CODE } from '@common/constants_sync'
const hello = async(urlInfo: LX.Sync.Client.UrlInfo) => request(`${urlInfo.httpProtocol}//${urlInfo.hostPath}/hello`) const hello = async(urlInfo: LX.Sync.Client.UrlInfo) => request(`${urlInfo.httpProtocol}//${urlInfo.hostPath}/hello`)

View File

@ -1,15 +1,15 @@
import WebSocket from 'ws' import WebSocket from 'ws'
import { encryptMsg, decryptMsg } from './utils' import { encryptMsg, decryptMsg } from './utils'
import { modules, callObj } from './modules' import { callObj } from './sync'
// import { action as commonAction } from '@root/store/modules/common' // import { action as commonAction } from '@root/store/modules/common'
// import { getStore } from '@root/store' // import { getStore } from '@root/store'
// import registerSyncListHandler from './syncList' // import registerSyncListHandler from './syncList'
import log from '../log' import log from '../log'
import { SYNC_CLOSE_CODE, SYNC_CODE } from '@common/constants'
import { dateFormat } from '@common/utils/common' import { dateFormat } from '@common/utils/common'
import { aesEncrypt, getAddress } from '../utils' import { aesEncrypt, getAddress } from '../utils'
import { sendClientStatus } from '@main/modules/winMain' import { sendClientStatus } from '@main/modules/winMain'
import { createMsg2call } from 'message2call' import { createMsg2call } from 'message2call'
import { SYNC_CLOSE_CODE, SYNC_CODE } from '@common/constants_sync'
let status: LX.Sync.ClientStatus = { let status: LX.Sync.ClientStatus = {
status: false, status: false,
@ -31,18 +31,6 @@ export const sendSyncMessage = (message: string) => {
sendClientStatus(status) sendClientStatus(status)
} }
const handleConnection = (socket: LX.Sync.Client.Socket) => {
for (const { registerEvent } of Object.values(modules)) {
registerEvent(socket)
}
}
const handleDisconnection = () => {
for (const { unregisterEvent } of Object.values(modules)) {
unregisterEvent()
}
}
const heartbeatTools = { const heartbeatTools = {
failedNum: 0, failedNum: 0,
maxTryNum: 100000, maxTryNum: 100000,
@ -139,14 +127,14 @@ export const connect = (urlInfo: LX.Sync.Client.UrlInfo, keyInfo: LX.Sync.Client
heartbeatTools.connect(client) heartbeatTools.connect(client)
let closeEvents: Array<(err: Error) => (void | Promise<void>)> = [] let closeEvents: Array<(err: Error) => (void | Promise<void>)> = []
let disconnected = true
const message2read = createMsg2call({ const message2read = createMsg2call<LX.Sync.ServerSyncActions>({
funcsObj: { funcsObj: {
...callObj, ...callObj,
list_sync_finished() { finished() {
log.info('sync list success') log.info('sync list success')
client!.isReady = true client!.isReady = true
handleConnection(client as LX.Sync.Client.Socket)
sendSyncStatus({ sendSyncStatus({
status: true, status: true,
message: '', message: '',
@ -156,6 +144,7 @@ export const connect = (urlInfo: LX.Sync.Client.UrlInfo, keyInfo: LX.Sync.Client
}, },
timeout: 120 * 1000, timeout: 120 * 1000,
sendMessage(data) { sendMessage(data) {
if (disconnected) throw new Error('disconnected')
void encryptMsg(keyInfo, JSON.stringify(data)).then((data) => { void encryptMsg(keyInfo, JSON.stringify(data)).then((data) => {
client?.send(data) client?.send(data)
}).catch((err) => { }).catch((err) => {
@ -178,13 +167,14 @@ export const connect = (urlInfo: LX.Sync.Client.UrlInfo, keyInfo: LX.Sync.Client
}, },
}) })
client.remoteSyncList = message2read.createSyncRemote('list') client.remote = message2read.remote
client.remoteQueueList = message2read.createQueueRemote('list')
client.addEventListener('message', ({ data }) => { client.addEventListener('message', ({ data }) => {
if (data == 'ping') return if (data == 'ping') return
if (typeof data === 'string') { if (typeof data === 'string') {
void decryptMsg(keyInfo, data).then((data) => { void decryptMsg(keyInfo, data).then((data) => {
let syncData: LX.Sync.ServerActions let syncData: LX.Sync.ServerSyncActions
try { try {
syncData = JSON.parse(data) syncData = JSON.parse(data)
} catch (err) { } catch (err) {
@ -212,6 +202,10 @@ export const connect = (urlInfo: LX.Sync.Client.UrlInfo, keyInfo: LX.Sync.Client
// const store = getStore() // const store = getStore()
// global.lx.syncKeyInfo = keyInfo // global.lx.syncKeyInfo = keyInfo
client!.isReady = false client!.isReady = false
client!.moduleReadys = {
list: false,
}
disconnected = false
sendSyncStatus({ sendSyncStatus({
status: false, status: false,
message: initMessage, message: initMessage,
@ -225,7 +219,7 @@ export const connect = (urlInfo: LX.Sync.Client.UrlInfo, keyInfo: LX.Sync.Client
log.error(err?.message) log.error(err?.message)
} }
closeEvents = [] closeEvents = []
handleDisconnection() disconnected = true
message2read.onDestroy() message2read.onDestroy()
switch (code) { switch (code) {
case SYNC_CLOSE_CODE.normal: case SYNC_CLOSE_CODE.normal:

View File

@ -1,6 +1,6 @@
import fs from 'node:fs' import fs from 'node:fs'
import path from 'node:path' import path from 'node:path'
import { File } from '../constants' import { File } from '../../../../common/constants_sync'
let syncAuthKeys: Record<string, LX.Sync.ClientKeyInfo> let syncAuthKeys: Record<string, LX.Sync.ClientKeyInfo>

View File

@ -1,10 +1,10 @@
import handleAuth from './auth' import handleAuth from './auth'
import { connect as socketConnect, disconnect as socketDisconnect, sendSyncStatus, sendSyncMessage } from './client' import { connect as socketConnect, disconnect as socketDisconnect, sendSyncStatus, sendSyncMessage } from './client'
// import { getSyncHost } from '@root/utils/data' // import { getSyncHost } from '@root/utils/data'
import { SYNC_CODE } from '@common/constants'
import log from '../log' import log from '../log'
import { parseUrl } from './utils' import { parseUrl } from './utils'
import migrateData from '../migrate' import migrateData from '../migrate'
import { SYNC_CODE } from '@common/constants_sync'
let connectId = 0 let connectId = 0

View File

@ -8,3 +8,7 @@ export const callObj = Object.assign({}, list.handler)
export const modules = { export const modules = {
list, list,
} }
export const featureVersion = {
list: 1,
} as const

View File

@ -1,8 +1,10 @@
// 这个文件导出的方法将暴露给服务端调用,第一个参数固定为当前 socket 对象
import { handleRemoteListAction } from '@main/modules/sync/utils' import { handleRemoteListAction } from '@main/modules/sync/utils'
import { getLocalListData, setLocalListData } from '../../../utils' import { getLocalListData, setLocalListData } from '../../../utils'
import { toMD5 } from '@common/utils/nodejs' import { toMD5 } from '@common/utils/nodejs'
import { removeSelectModeListener, sendCloseSelectMode, sendSelectMode } from '@main/modules/winMain' import { removeSelectModeListener, sendCloseSelectMode, sendSelectMode } from '@main/modules/winMain'
import log from '@main/modules/sync/log' import log from '@main/modules/sync/log'
import { registerEvent, unregisterEvent } from './localEvent'
const logInfo = (eventName: string, success = false) => { const logInfo = (eventName: string, success = false) => {
log.info(`[${eventName}]${eventName.replace('list:sync:list_sync_', '').replaceAll('_', ' ')}${success ? ' success' : ''}`) log.info(`[${eventName}]${eventName.replace('list:sync:list_sync_', '').replaceAll('_', ' ')}${success ? ' success' : ''}`)
@ -11,8 +13,8 @@ const logInfo = (eventName: string, success = false) => {
// log.error(`[${eventName}]${eventName.replace('list:sync:list_sync_', '').replaceAll('_', ' ')} error: ${err.message}`) // 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) => { export const onListSyncAction = async(socket: LX.Sync.Client.Socket, action: LX.Sync.List.ActionList) => {
if (!socket.isReady) return if (!socket.moduleReadys?.list) return
await handleRemoteListAction(action) await handleRemoteListAction(action)
} }
@ -21,7 +23,7 @@ export const list_sync_get_md5 = async(socket: LX.Sync.Client.Socket) => {
return toMD5(JSON.stringify(await getLocalListData())) return toMD5(JSON.stringify(await getLocalListData()))
} }
const getSyncMode = async(socket: LX.Sync.Client.Socket): Promise<LX.Sync.ListSyncMode> => new Promise((resolve, reject) => { const getSyncMode = async(socket: LX.Sync.Client.Socket): Promise<LX.Sync.List.SyncMode> => new Promise((resolve, reject) => {
const handleDisconnect = (err: Error) => { const handleDisconnect = (err: Error) => {
sendCloseSelectMode() sendCloseSelectMode()
removeSelectModeListener() removeSelectModeListener()
@ -47,7 +49,17 @@ export const list_sync_get_list_data = async(socket: LX.Sync.Client.Socket) => {
return getLocalListData() return getLocalListData()
} }
export const list_sync_set_list_data = async(socket: LX.Sync.Client.Socket, data: LX.Sync.ListData) => { export const list_sync_set_list_data = async(socket: LX.Sync.Client.Socket, data: LX.Sync.List.ListData) => {
logInfo('list:sync:list_sync_set_list_data') logInfo('list:sync:list_sync_set_list_data')
await setLocalListData(data) await setLocalListData(data)
} }
export const list_sync_finished = async(socket: LX.Sync.Client.Socket) => {
logInfo('list:sync:finished')
socket.moduleReadys.list = true
registerEvent(socket)
socket.onClose(() => {
unregisterEvent()
})
}

View File

@ -8,9 +8,10 @@ export const registerEvent = (socket: LX.Sync.Client.Socket) => {
// unregisterLocalListAction?.() // unregisterLocalListAction?.()
// unregisterLocalListAction = null // unregisterLocalListAction = null
// }) // })
unregisterEvent()
unregisterLocalListAction = registerListActionEvent((action) => { unregisterLocalListAction = registerListActionEvent((action) => {
if (!socket?.isReady) return if (!socket.moduleReadys?.list) return
void socket.remoteSyncList.onListSyncAction(action) void socket.remoteQueueList.onListSyncAction(action)
}) })
} }

View File

@ -0,0 +1,22 @@
// 这个文件导出的方法将暴露给服务端调用,第一个参数固定为当前 socket 对象
// import { getUserSpace } from '@/user'
// import { modules } from '../modules'
import { featureVersion } from '../modules'
export const getEnabledFeatures = async(socket: LX.Sync.Client.Socket, serverType: LX.Sync.ServerType, supportedFeatures: LX.Sync.SupportedFeatures): Promise<LX.Sync.EnabledFeatures> => {
// const userSpace = getUserSpace(socket.userInfo.name)
switch (serverType) {
case 'server':
return {
list: featureVersion.list == supportedFeatures.list,
}
case 'desktop-app':
default:
return {
list: featureVersion.list == supportedFeatures.list,
}
}
}

View File

@ -0,0 +1,8 @@
import * as handler from './handler'
import { callObj as _callObj } from '../modules'
export { modules } from '../modules'
export const callObj = {
...handler,
..._callObj,
}

View File

@ -1,4 +1,4 @@
import { File } from './constants' import { File } from '../../../common/constants_sync'
import fs from 'node:fs' import fs from 'node:fs'
import path from 'node:path' import path from 'node:path'

View File

@ -10,3 +10,7 @@ export const modules = {
export { ListManage } from './list' export { ListManage } from './list'
export const featureVersion = {
list: 1,
} as const

View File

@ -44,7 +44,7 @@ export class ListManage {
await this.snapshotDataManage.updateDeviceSnapshotKey(clientId, key) await this.snapshotDataManage.updateDeviceSnapshotKey(clientId, key)
} }
getListData = async(): Promise<LX.Sync.ListData> => { getListData = async(): Promise<LX.Sync.List.ListData> => {
return getLocalListData() return getLocalListData()
} }
} }

View File

@ -3,7 +3,7 @@ import fs from 'node:fs'
import path from 'node:path' import path from 'node:path'
import syncLog from '../../../log' import syncLog from '../../../log'
import { getUserConfig, type UserDataManage } from '../../user/data' import { getUserConfig, type UserDataManage } from '../../user/data'
import { File } from '../../../constants' import { File } from '../../../../../../common/constants_sync'
import { checkAndCreateDirSync } from '../../utils' import { checkAndCreateDirSync } from '../../utils'
@ -11,7 +11,7 @@ interface SnapshotInfo {
latest: string | null latest: string | null
time: number time: number
list: string[] list: string[]
clients: Record<string, LX.Sync.ListInfo> clients: Record<string, LX.Sync.List.ListInfo>
} }
export class SnapshotDataManage { export class SnapshotDataManage {
userDataManage: UserDataManage userDataManage: UserDataManage
@ -70,7 +70,7 @@ export class SnapshotDataManage {
getSnapshot = async(name: string) => { getSnapshot = async(name: string) => {
const filePath = path.join(this.snapshotDir, `snapshot_${name}`) const filePath = path.join(this.snapshotDir, `snapshot_${name}`)
let listData: LX.Sync.ListData let listData: LX.Sync.List.ListData
try { try {
listData = JSON.parse((await fs.promises.readFile(filePath)).toString('utf-8')) listData = JSON.parse((await fs.promises.readFile(filePath)).toString('utf-8'))
} catch (err) { } catch (err) {

View File

@ -1,7 +1,8 @@
// 这个文件导出的方法将暴露给客户端调用,第一个参数固定为当前 socket 对象
// import { throttle } from '@common/utils/common' // import { throttle } from '@common/utils/common'
// import { sendSyncActionList } from '@main/modules/winMain' // import { sendSyncActionList } from '@main/modules/winMain'
// import { SYNC_CLOSE_CODE } from '@/constants' // import { SYNC_CLOSE_CODE } from '@/constants'
import { SYNC_CLOSE_CODE } from '@main/modules/sync/constants' import { SYNC_CLOSE_CODE } from '@common/constants_sync'
import { getUserSpace } from '@main/modules/sync/server/user' import { getUserSpace } from '@main/modules/sync/server/user'
import { handleRemoteListAction } from '@main/modules/sync/utils' import { handleRemoteListAction } from '@main/modules/sync/utils'
// import { encryptMsg } from '@/utils/tools' // import { encryptMsg } from '@/utils/tools'
@ -108,7 +109,7 @@ import { handleRemoteListAction } from '@main/modules/sync/utils'
// } // }
// } // }
// export const sendListAction = async(action: LX.Sync.ActionList) => { // export const sendListAction = async(action: LX.Sync.List.ActionList) => {
// console.log('sendListAction', action.action) // console.log('sendListAction', action.action)
// // io.sockets // // io.sockets
// await broadcast('list:sync:action', action) // await broadcast('list:sync:action', action)
@ -144,7 +145,8 @@ import { handleRemoteListAction } from '@main/modules/sync/utils'
// // } // // }
// } // }
export const onListSyncAction = async(socket: LX.Sync.Server.Socket, action: LX.Sync.ActionList) => { export const onListSyncAction = async(socket: LX.Sync.Server.Socket, action: LX.Sync.List.ActionList) => {
if (!socket.moduleReadys.list) return
const userSpace = getUserSpace(socket.userInfo.name) const userSpace = getUserSpace(socket.userInfo.name)
await handleRemoteListAction(action).then(async updated => { await handleRemoteListAction(action).then(async updated => {
if (!updated) { if (!updated) {
@ -156,8 +158,8 @@ export const onListSyncAction = async(socket: LX.Sync.Server.Socket, action: LX.
const currentUserName = socket.userInfo.name const currentUserName = socket.userInfo.name
const currentId = socket.keyInfo.clientId const currentId = socket.keyInfo.clientId
socket.broadcast((client) => { socket.broadcast((client) => {
if (client.keyInfo.clientId == currentId || !client.isReady || client.userInfo.name != currentUserName) return if (client.keyInfo.clientId == currentId || !client.moduleReadys?.list || client.userInfo.name != currentUserName) return
void client.remoteSyncList.onListSyncAction(action) void client.remoteQueueList.onListSyncAction(action)
}) })
}) })
} }

View File

@ -1,3 +1,3 @@
export * as handler from './handler' export * as handler from './handler'
export { default as sync } from './sync' export { sync } from './sync'
export * from './localEvent' export * from './localEvent'

View File

@ -5,13 +5,13 @@ import { getUserSpace } from '../../../user'
let unregisterLocalListAction: (() => void) | null let unregisterLocalListAction: (() => void) | null
const sendListAction = async(wss: LX.Sync.Server.SocketServer, action: LX.Sync.ActionList) => { const sendListAction = async(wss: LX.Sync.Server.SocketServer, action: LX.Sync.List.ActionList) => {
// console.log('sendListAction', action.action) // console.log('sendListAction', action.action)
const userSpace = getUserSpace() const userSpace = getUserSpace()
const key = await userSpace.listManage.createSnapshot() const key = await userSpace.listManage.createSnapshot()
for (const client of wss.clients) { for (const client of wss.clients) {
if (!client.isReady) return if (!client.moduleReadys?.list) continue
void client.remoteSyncList.onListSyncAction(action).then(() => { void client.remoteQueueList.onListSyncAction(action).then(() => {
void userSpace.listManage.updateDeviceSnapshotKey(client.keyInfo.clientId, key) void userSpace.listManage.updateDeviceSnapshotKey(client.keyInfo.clientId, key)
}) })
} }
@ -23,6 +23,7 @@ export const registerEvent = (wss: LX.Sync.Server.SocketServer) => {
// unregisterLocalListAction?.() // unregisterLocalListAction?.()
// unregisterLocalListAction = null // unregisterLocalListAction = null
// }) // })
unregisterEvent()
unregisterLocalListAction = registerListActionEvent((action) => { unregisterLocalListAction = registerListActionEvent((action) => {
void sendListAction(wss, action) void sendListAction(wss, action)
}) })

View File

@ -1,4 +1,4 @@
import { SYNC_CLOSE_CODE } from '../../../../constants' // import { SYNC_CLOSE_CODE } from '../../../../constants'
import { getUserSpace, getUserConfig } from '../../../user' import { getUserSpace, getUserConfig } from '../../../user'
import { getLocalListData, setLocalListData } from '@main/modules/sync/utils' import { getLocalListData, setLocalListData } from '@main/modules/sync/utils'
// import { LIST_IDS } from '@common/constants' // import { LIST_IDS } from '@common/constants'
@ -9,7 +9,7 @@ import { getLocalListData, setLocalListData } from '@main/modules/sync/utils'
let syncingId: string | null = null let syncingId: string | null = null
const wait = async(time = 1000) => await new Promise((resolve, reject) => setTimeout(resolve, time)) const wait = async(time = 1000) => await new Promise((resolve, reject) => setTimeout(resolve, time))
const patchListData = (listData: Partial<LX.Sync.ListData>): LX.Sync.ListData => { const patchListData = (listData: Partial<LX.Sync.List.ListData>): LX.Sync.List.ListData => {
return Object.assign({ return Object.assign({
defaultList: [], defaultList: [],
loveList: [], loveList: [],
@ -17,39 +17,39 @@ const patchListData = (listData: Partial<LX.Sync.ListData>): LX.Sync.ListData =>
}, listData) }, listData)
} }
const getRemoteListData = async(socket: LX.Sync.Server.Socket): Promise<LX.Sync.ListData> => { const getRemoteListData = async(socket: LX.Sync.Server.Socket): Promise<LX.Sync.List.ListData> => {
console.log('getRemoteListData') console.log('getRemoteListData')
return patchListData(await socket.remoteSyncList.list_sync_get_list_data()) return patchListData(await socket.remoteQueueList.list_sync_get_list_data())
} }
const getRemoteListMD5 = async(socket: LX.Sync.Server.Socket): Promise<string> => { const getRemoteListMD5 = async(socket: LX.Sync.Server.Socket): Promise<string> => {
return socket.remoteSyncList.list_sync_get_md5() return socket.remoteQueueList.list_sync_get_md5()
} }
// const getLocalListData = async(socket: LX.Sync.Server.Socket): Promise<LX.Sync.ListData> => { // const getLocalListData = async(socket: LX.Sync.Server.Socket): Promise<LX.Sync.List.ListData> => {
// return getUserSpace(socket.userInfo.name).listManage.getListData() // return getUserSpace(socket.userInfo.name).listManage.getListData()
// } // }
const getSyncMode = async(socket: LX.Sync.Server.Socket): Promise<LX.Sync.ListSyncMode> => { const getSyncMode = async(socket: LX.Sync.Server.Socket): Promise<LX.Sync.List.SyncMode> => {
return socket.remoteSyncList.list_sync_get_sync_mode() return socket.remoteQueueList.list_sync_get_sync_mode()
} }
const finishedSync = async(socket: LX.Sync.Server.Socket) => { const finishedSync = async(socket: LX.Sync.Server.Socket) => {
await socket.remoteSyncList.list_sync_finished() await socket.remoteQueueList.list_sync_finished()
} }
const setLocalList = async(socket: LX.Sync.Server.Socket, listData: LX.Sync.ListData) => { const setLocalList = async(socket: LX.Sync.Server.Socket, listData: LX.Sync.List.ListData) => {
await setLocalListData(listData) await setLocalListData(listData)
const userSpace = getUserSpace(socket.userInfo.name) const userSpace = getUserSpace(socket.userInfo.name)
return userSpace.listManage.createSnapshot() return userSpace.listManage.createSnapshot()
} }
const overwriteRemoteListData = async(socket: LX.Sync.Server.Socket, listData: LX.Sync.ListData, key: string, excludeIds: string[] = []) => { const overwriteRemoteListData = async(socket: LX.Sync.Server.Socket, listData: LX.Sync.List.ListData, key: string, excludeIds: string[] = []) => {
const action = { action: 'list_data_overwrite', data: listData } as const const action = { action: 'list_data_overwrite', data: listData } as const
const tasks: Array<Promise<void>> = [] const tasks: Array<Promise<void>> = []
socket.broadcast((client) => { socket.broadcast((client) => {
if (excludeIds.includes(client.keyInfo.clientId) || client.userInfo.name != socket.userInfo.name || !client.isReady) return if (excludeIds.includes(client.keyInfo.clientId) || client.userInfo.name != socket.userInfo.name || !client.moduleReadys?.list) return
tasks.push(client.remoteSyncList.onListSyncAction(action).then(async() => { tasks.push(client.remoteQueueList.onListSyncAction(action).then(async() => {
const userSpace = getUserSpace(socket.userInfo.name) const userSpace = getUserSpace(socket.userInfo.name)
return userSpace.listManage.updateDeviceSnapshotKey(socket.keyInfo.clientId, key) return userSpace.listManage.updateDeviceSnapshotKey(socket.keyInfo.clientId, key)
}).catch(err => { }).catch(err => {
@ -59,14 +59,14 @@ const overwriteRemoteListData = async(socket: LX.Sync.Server.Socket, listData: L
if (!tasks.length) return if (!tasks.length) return
await Promise.all(tasks) await Promise.all(tasks)
} }
const setRemotelList = async(socket: LX.Sync.Server.Socket, listData: LX.Sync.ListData, key: string): Promise<void> => { const setRemotelList = async(socket: LX.Sync.Server.Socket, listData: LX.Sync.List.ListData, key: string): Promise<void> => {
await socket.remoteSyncList.list_sync_set_list_data(listData) await socket.remoteQueueList.list_sync_set_list_data(listData)
const userSpace = getUserSpace(socket.userInfo.name) const userSpace = getUserSpace(socket.userInfo.name)
await userSpace.listManage.updateDeviceSnapshotKey(socket.keyInfo.clientId, key) await userSpace.listManage.updateDeviceSnapshotKey(socket.keyInfo.clientId, key)
} }
type UserDataObj = Map<string, LX.List.UserListInfoFull> type UserDataObj = Map<string, LX.List.UserListInfoFull>
const createUserListDataObj = (listData: LX.Sync.ListData): UserDataObj => { const createUserListDataObj = (listData: LX.Sync.List.ListData): UserDataObj => {
const userListDataObj: UserDataObj = new Map() const userListDataObj: UserDataObj = new Map()
for (const list of listData.userList) userListDataObj.set(list.id, list) for (const list of listData.userList) userListDataObj.set(list.id, list)
return userListDataObj return userListDataObj
@ -111,9 +111,9 @@ const handleMergeList = (
} }
return ids.map(id => map.get(id)) as LX.Music.MusicInfo[] return ids.map(id => map.get(id)) as LX.Music.MusicInfo[]
} }
const mergeList = (socket: LX.Sync.Server.Socket, sourceListData: LX.Sync.ListData, targetListData: LX.Sync.ListData): LX.Sync.ListData => { const mergeList = (socket: LX.Sync.Server.Socket, sourceListData: LX.Sync.List.ListData, targetListData: LX.Sync.List.ListData): LX.Sync.List.ListData => {
const addMusicLocationType = getUserConfig(socket.userInfo.name)['list.addMusicLocationType'] const addMusicLocationType = getUserConfig(socket.userInfo.name)['list.addMusicLocationType']
const newListData: LX.Sync.ListData = { const newListData: LX.Sync.List.ListData = {
defaultList: [], defaultList: [],
loveList: [], loveList: [],
userList: [], userList: [],
@ -147,8 +147,8 @@ const mergeList = (socket: LX.Sync.Server.Socket, sourceListData: LX.Sync.ListDa
return newListData return newListData
} }
const overwriteList = (sourceListData: LX.Sync.ListData, targetListData: LX.Sync.ListData): LX.Sync.ListData => { const overwriteList = (sourceListData: LX.Sync.List.ListData, targetListData: LX.Sync.List.ListData): LX.Sync.List.ListData => {
const newListData: LX.Sync.ListData = { const newListData: LX.Sync.List.ListData = {
defaultList: [], defaultList: [],
loveList: [], loveList: [],
userList: [], userList: [],
@ -171,16 +171,13 @@ const overwriteList = (sourceListData: LX.Sync.ListData, targetListData: LX.Sync
return newListData return newListData
} }
const handleMergeListData = async(socket: LX.Sync.Server.Socket): Promise<[LX.Sync.ListData, boolean, boolean]> => { const handleMergeListData = async(socket: LX.Sync.Server.Socket): Promise<[LX.Sync.List.ListData, boolean, boolean]> => {
const mode: LX.Sync.ListSyncMode = await getSyncMode(socket) const mode: LX.Sync.List.SyncMode = await getSyncMode(socket)
if (mode == 'cancel') { if (mode == 'cancel') throw new Error('cancel')
socket.close(SYNC_CLOSE_CODE.normal)
throw new Error('cancel')
}
const [remoteListData, localListData] = await Promise.all([getRemoteListData(socket), getLocalListData()]) const [remoteListData, localListData] = await Promise.all([getRemoteListData(socket), getLocalListData()])
console.log('handleMergeListData', 'remoteListData, localListData') console.log('handleMergeListData', 'remoteListData, localListData')
let listData: LX.Sync.ListData let listData: LX.Sync.List.ListData
let requiredUpdateLocalListData = true let requiredUpdateLocalListData = true
let requiredUpdateRemoteListData = true let requiredUpdateRemoteListData = true
switch (mode) { switch (mode) {
@ -206,9 +203,7 @@ const handleMergeListData = async(socket: LX.Sync.Server.Socket): Promise<[LX.Sy
break break
// case 'none': return null // case 'none': return null
// case 'cancel': // case 'cancel':
default: default: throw new Error('cancel')
socket.close(SYNC_CLOSE_CODE.normal)
throw new Error('cancel')
} }
return [listData, requiredUpdateLocalListData, requiredUpdateRemoteListData] return [listData, requiredUpdateLocalListData, requiredUpdateRemoteListData]
} }
@ -299,12 +294,12 @@ const checkListLatest = async(socket: LX.Sync.Server.Socket) => {
if (latest && userCurrentListInfoKey != currentListInfoKey) await userSpace.listManage.updateDeviceSnapshotKey(socket.keyInfo.clientId, currentListInfoKey) if (latest && userCurrentListInfoKey != currentListInfoKey) await userSpace.listManage.updateDeviceSnapshotKey(socket.keyInfo.clientId, currentListInfoKey)
return latest return latest
} }
const handleMergeListDataFromSnapshot = async(socket: LX.Sync.Server.Socket, snapshot: LX.Sync.ListData) => { const handleMergeListDataFromSnapshot = async(socket: LX.Sync.Server.Socket, snapshot: LX.Sync.List.ListData) => {
if (await checkListLatest(socket)) return if (await checkListLatest(socket)) return
const addMusicLocationType = getUserConfig(socket.userInfo.name)['list.addMusicLocationType'] const addMusicLocationType = getUserConfig(socket.userInfo.name)['list.addMusicLocationType']
const [remoteListData, localListData] = await Promise.all([getRemoteListData(socket), getLocalListData()]) const [remoteListData, localListData] = await Promise.all([getRemoteListData(socket), getLocalListData()])
const newListData: LX.Sync.ListData = { const newListData: LX.Sync.List.ListData = {
defaultList: [], defaultList: [],
loveList: [], loveList: [],
userList: [], userList: [],
@ -413,7 +408,7 @@ const syncList = async(socket: LX.Sync.Server.Socket) => {
// await fsPromises.unlink(filePath) // await fsPromises.unlink(filePath)
// } // }
export default async(socket: LX.Sync.Server.Socket) => { export const sync = async(socket: LX.Sync.Server.Socket) => {
let disconnected = false let disconnected = false
socket.onClose(() => { socket.onClose(() => {
disconnected = true disconnected = true
@ -428,7 +423,8 @@ export default async(socket: LX.Sync.Server.Socket) => {
syncingId = socket.keyInfo.clientId syncingId = socket.keyInfo.clientId
await syncList(socket).then(async() => { await syncList(socket).then(async() => {
return finishedSync(socket) await finishedSync(socket)
socket.moduleReadys.list = true
}).finally(() => { }).finally(() => {
syncingId = null syncingId = null
}) })

View File

@ -1,5 +1,4 @@
import type http from 'http' import type http from 'http'
import { SYNC_CODE } from '@common/constants'
import { import {
aesEncrypt, aesEncrypt,
aesDecrypt, aesDecrypt,
@ -10,6 +9,7 @@ import querystring from 'node:querystring'
import { getUserSpace, createClientKeyInfo } from '../user' import { getUserSpace, createClientKeyInfo } from '../user'
import { toMD5 } from '../utils' import { toMD5 } from '../utils'
import { getComputerName } from '../../utils' import { getComputerName } from '../../utils'
import { SYNC_CODE } from '@common/constants_sync'
const requestIps = new Map<string, number>() const requestIps = new Map<string, number>()

View File

@ -1,10 +1,9 @@
import http, { type IncomingMessage } from 'node:http' import http, { type IncomingMessage } from 'node:http'
import url from 'node:url'
import { WebSocketServer } from 'ws' import { WebSocketServer } from 'ws'
import { modules, callObj } from '../modules' import { registerLocalSyncEvent, callObj, sync, unregisterLocalSyncEvent } from './sync'
import { authCode, authConnect } from './auth' import { authCode, authConnect } from './auth'
import { getAddress } from '../../utils' import { getAddress } from '../../utils'
import { SYNC_CLOSE_CODE, SYNC_CODE } from '../../constants' import { SYNC_CLOSE_CODE, SYNC_CODE } from '@common/constants_sync'
import { getUserSpace, releaseUserSpace, getServerId, initServerInfo } from '../user' import { getUserSpace, releaseUserSpace, getServerId, initServerInfo } from '../user'
import { createMsg2call } from 'message2call' import { createMsg2call } from 'message2call'
import log from '../../log' import log from '../../log'
@ -23,6 +22,8 @@ let status: LX.Sync.ServerStatus = {
let stopingServer = false let stopingServer = false
let host = 'http://localhost'
const codeTools: { const codeTools: {
timeout: NodeJS.Timer | null timeout: NodeJS.Timer | null
start: () => void start: () => void
@ -42,46 +43,25 @@ const codeTools: {
}, },
} }
const syncData = async(socket: LX.Sync.Server.Socket) => {
let disconnected = false
socket.onClose(() => {
disconnected = true
})
for (const module of Object.values(modules)) {
await module.sync(socket)
if (disconnected) throw new Error('disconnected')
}
}
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) => { const checkDuplicateClient = (newSocket: LX.Sync.Server.Socket) => {
for (const client of [...wss!.clients]) { for (const client of [...wss!.clients]) {
if (client === newSocket || client.keyInfo.clientId != newSocket.keyInfo.clientId) continue if (client === newSocket || client.keyInfo.clientId != newSocket.keyInfo.clientId) continue
log.info('duplicate client', client.userInfo.name, client.keyInfo.deviceName) log.info('duplicate client', client.userInfo.name, client.keyInfo.deviceName)
client.isReady = false client.isReady = false
for (const name of Object.keys(client.moduleReadys) as Array<keyof LX.Sync.Server.Socket['moduleReadys']>) {
client.moduleReadys[name] = false
}
client.close(SYNC_CLOSE_CODE.normal) client.close(SYNC_CLOSE_CODE.normal)
} }
} }
const handleConnection = async(socket: LX.Sync.Server.Socket, request: IncomingMessage) => { const handleConnection = async(socket: LX.Sync.Server.Socket, request: IncomingMessage) => {
const queryData = url.parse(request.url as string, true).query as Record<string, string> const queryData = new URL(request.url as string, host).searchParams
const clientId = queryData.get('i')
// // if (typeof socket.handshake.query.i != 'string') return socket.disconnect(true) // // if (typeof socket.handshake.query.i != 'string') return socket.disconnect(true)
const userSpace = getUserSpace() const userSpace = getUserSpace()
const keyInfo = userSpace.dataManage.getClientKeyInfo(queryData.i) const keyInfo = userSpace.dataManage.getClientKeyInfo(clientId)
if (!keyInfo) { if (!keyInfo) {
socket.close(SYNC_CLOSE_CODE.failed) socket.close(SYNC_CLOSE_CODE.failed)
return return
@ -95,7 +75,7 @@ const handleConnection = async(socket: LX.Sync.Server.Socket, request: IncomingM
checkDuplicateClient(socket) checkDuplicateClient(socket)
try { try {
await syncData(socket) await sync(socket)
} catch (err) { } catch (err) {
// console.log(err) // console.log(err)
log.warn(err) log.warn(err)
@ -174,6 +154,12 @@ const handleStartServer = async(port = 9527, ip = '0.0.0.0') => await new Promis
wss.on('connection', function(socket, request) { wss.on('connection', function(socket, request) {
socket.isReady = false socket.isReady = false
socket.moduleReadys = {
list: false,
}
socket.feature = {
list: false,
}
socket.on('pong', () => { socket.on('pong', () => {
socket.isAlive = true socket.isAlive = true
}) })
@ -182,10 +168,12 @@ const handleStartServer = async(port = 9527, ip = '0.0.0.0') => await new Promis
// const events = new Map<keyof LX.Sync.ActionSyncType, Array<(err: Error | null, data: LX.Sync.ActionSyncType[keyof LX.Sync.ActionSyncType]) => void>>() // const events = new Map<keyof LX.Sync.ActionSyncType, Array<(err: Error | null, data: LX.Sync.ActionSyncType[keyof LX.Sync.ActionSyncType]) => 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<void>)> = [] let closeEvents: Array<(err: Error) => (void | Promise<void>)> = []
const msg2call = createMsg2call<LX.Sync.ClientActions>({ let disconnected = false
const msg2call = createMsg2call<LX.Sync.ClientSyncActions>({
funcsObj: callObj, funcsObj: callObj,
timeout: 120 * 1000, timeout: 120 * 1000,
sendMessage(data) { sendMessage(data) {
if (disconnected) throw new Error('disconnected')
void encryptMsg(socket.keyInfo, JSON.stringify(data)).then((data) => { void encryptMsg(socket.keyInfo, JSON.stringify(data)).then((data) => {
// console.log('sendData', eventName) // console.log('sendData', eventName)
socket.send(data) socket.send(data)
@ -200,13 +188,14 @@ const handleStartServer = async(port = 9527, ip = '0.0.0.0') => await new Promis
}, },
onError(error, path, groupName) { onError(error, path, groupName) {
const name = groupName ?? '' const name = groupName ?? ''
log.error(`sync call ${name} ${path.join('.')} error:`, error) const deviceName = socket.keyInfo?.deviceName ?? ''
log.error(`sync call ${deviceName} ${name} ${path.join('.')} error:`, error)
if (groupName == null) return if (groupName == null) return
socket.close(SYNC_CLOSE_CODE.failed) socket.close(SYNC_CLOSE_CODE.failed)
}, },
}) })
socket.remote = msg2call.remote socket.remote = msg2call.remote
socket.remoteSyncList = msg2call.createSyncRemote('list') socket.remoteQueueList = msg2call.createQueueRemote('list')
socket.addEventListener('message', ({ data }) => { socket.addEventListener('message', ({ data }) => {
if (typeof data != 'string') return if (typeof data != 'string') return
void decryptMsg(socket.keyInfo, data).then((data) => { void decryptMsg(socket.keyInfo, data).then((data) => {
@ -233,14 +222,15 @@ const handleStartServer = async(port = 9527, ip = '0.0.0.0') => await new Promis
log.error(err?.message) log.error(err?.message)
} }
closeEvents = [] closeEvents = []
disconnected = true
msg2call.onDestroy() msg2call.onDestroy()
if (socket.isReady) { if (socket.isReady) {
log.info('deconnection', socket.userInfo.name, socket.keyInfo.deviceName) log.info('deconnection', socket.userInfo.name, socket.keyInfo.deviceName)
// events = {} // events = {}
if (!status.devices.length) handleUnconnection() if (!status.devices.length) handleUnconnection()
} else { } else {
const queryData = url.parse(request.url as string, true).query as Record<string, string> const queryData = new URL(request.url as string, host).searchParams
log.info('deconnection', queryData.i) log.info('deconnection', queryData.get('i'))
} }
}) })
socket.onClose = function(handler: typeof closeEvents[number]) { socket.onClose = function(handler: typeof closeEvents[number]) {
@ -311,6 +301,7 @@ const handleStartServer = async(port = 9527, ip = '0.0.0.0') => await new Promis
void registerLocalSyncEvent(wss as LX.Sync.Server.SocketServer) void registerLocalSyncEvent(wss as LX.Sync.Server.SocketServer)
}) })
host = `http://${ip}:${port}`
httpServer.listen(port, ip) httpServer.listen(port, ip)
}) })

View File

@ -0,0 +1,14 @@
import { modules } from '../../modules'
export const registerLocalSyncEvent = async(wss: LX.Sync.Server.SocketServer) => {
unregisterLocalSyncEvent()
for (const module of Object.values(modules)) {
module.registerEvent(wss)
}
}
export const unregisterLocalSyncEvent = () => {
for (const module of Object.values(modules)) {
module.unregisterEvent()
}
}

View File

@ -0,0 +1,23 @@
// 这个文件导出的方法将暴露给客户端调用,第一个参数固定为当前 socket 对象
// import { getUserSpace } from '@/user'
import { FeaturesList } from '../../../../../../common/constants_sync'
import { modules } from '../../modules'
export const onFeatureChanged = async(socket: LX.Sync.Server.Socket, feature: LX.Sync.EnabledFeatures) => {
// const userSpace = getUserSpace(socket.userInfo.name)
const beforeFeature = socket.feature
for (const name of FeaturesList) {
if (feature[name] == beforeFeature[name]) continue
if (feature[name]) {
await modules[name].sync(socket).then(() => {
beforeFeature[name] = true
}).catch(_ => _)
} else {
socket.moduleReadys[name] = false
beforeFeature[name] = false
}
}
}

View File

@ -0,0 +1,10 @@
import * as handler from './handler'
import { callObj as _callObj } from '../../modules'
export { sync } from './sync'
export { modules } from '../../modules'
export * from './event'
export const callObj = {
...handler,
..._callObj,
}

View File

@ -0,0 +1,22 @@
import { FeaturesList } from '../../../../../../common/constants_sync'
import { featureVersion, modules } from '../../modules'
export const sync = async(socket: LX.Sync.Server.Socket) => {
let disconnected = false
socket.onClose(() => {
disconnected = true
})
const enabledFeatures = await socket.remote.getEnabledFeatures('desktop-app', featureVersion)
if (disconnected) throw new Error('disconnected')
for (const moduleName of FeaturesList) {
if (enabledFeatures[moduleName]) {
await modules[moduleName].sync(socket).then(() => {
socket.feature[moduleName] = true
}).catch(_ => _)
}
if (disconnected) throw new Error('disconnected')
}
await socket.remote.finished()
}

View File

@ -3,7 +3,7 @@ import path from 'node:path'
import { randomBytes } from 'node:crypto' import { randomBytes } from 'node:crypto'
import { throttle } from '@common/utils/common' import { throttle } from '@common/utils/common'
import { filterFileName, toMD5 } from '../utils' import { filterFileName, toMD5 } from '../utils'
import { File } from '../../constants' import { File } from '../../../../../common/constants_sync'
interface ServerInfo { interface ServerInfo {
@ -111,7 +111,7 @@ export class UserDataManage {
this.saveDevicesInfoThrottle() this.saveDevicesInfoThrottle()
} }
getClientKeyInfo = (clientId?: string): LX.Sync.ServerKeyInfo | null => { getClientKeyInfo = (clientId?: string | null): LX.Sync.ServerKeyInfo | null => {
if (!clientId) return null if (!clientId) return null
return this.devicesInfo.clients[clientId] ?? null return this.devicesInfo.clients[clientId] ?? null
} }

View File

@ -90,8 +90,8 @@ export const rsaDecrypt = (buffer: Buffer, key: string): Buffer => {
return privateDecrypt({ key, padding: constants.RSA_PKCS1_OAEP_PADDING }, buffer) return privateDecrypt({ key, padding: constants.RSA_PKCS1_OAEP_PADDING }, buffer)
} }
export const getLocalListData = async(): Promise<LX.Sync.ListData> => { export const getLocalListData = async(): Promise<LX.Sync.List.ListData> => {
const lists: LX.Sync.ListData = { const lists: LX.Sync.List.ListData = {
defaultList: await global.lx.worker.dbService.getListMusics(LIST_IDS.DEFAULT), defaultList: await global.lx.worker.dbService.getListMusics(LIST_IDS.DEFAULT),
loveList: await global.lx.worker.dbService.getListMusics(LIST_IDS.LOVE), loveList: await global.lx.worker.dbService.getListMusics(LIST_IDS.LOVE),
userList: [], userList: [],
@ -106,12 +106,12 @@ export const getLocalListData = async(): Promise<LX.Sync.ListData> => {
return lists return lists
} }
export const setLocalListData = async(listData: LX.Sync.ListData) => { export const setLocalListData = async(listData: LX.Sync.List.ListData) => {
await global.lx.event_list.list_data_overwrite(listData, true) await global.lx.event_list.list_data_overwrite(listData, true)
} }
export const registerListActionEvent = (sendListAction: (action: LX.Sync.ActionList) => (void | Promise<void>)) => { export const registerListActionEvent = (sendListAction: (action: LX.Sync.List.ActionList) => (void | Promise<void>)) => {
const list_data_overwrite = async(listData: MakeOptional<LX.List.ListDataFull, 'tempList'>, isRemote: boolean = false) => { const list_data_overwrite = async(listData: MakeOptional<LX.List.ListDataFull, 'tempList'>, isRemote: boolean = false) => {
if (isRemote) return if (isRemote) return
await sendListAction({ action: 'list_data_overwrite', data: listData }) await sendListAction({ action: 'list_data_overwrite', data: listData })
@ -188,7 +188,7 @@ export const registerListActionEvent = (sendListAction: (action: LX.Sync.ActionL
} }
} }
export const handleRemoteListAction = async({ action, data }: LX.Sync.ActionList) => { export const handleRemoteListAction = async({ action, data }: LX.Sync.List.ActionList) => {
// console.log('handleRemoteListAction', action) // console.log('handleRemoteListAction', action)
switch (action) { switch (action) {

View File

@ -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 { startServer, stopServer, getServerStatus, generateCode, connectServer, disconnectServer, getClientStatus } from '@main/modules/sync'
import { sendEvent } from '../main' import { sendEvent } from '../main'
let selectModeListenr: ((mode: LX.Sync.ListSyncMode | null) => void) | null = null let selectModeListenr: ((mode: LX.Sync.List.SyncMode | null) => void) | null = null
export default () => { export default () => {
mainHandle<LX.Sync.SyncServiceActions, any>(WIN_MAIN_RENDERER_EVENT_NAME.sync_action, async({ params: data }) => { mainHandle<LX.Sync.SyncServiceActions, any>(WIN_MAIN_RENDERER_EVENT_NAME.sync_action, async({ params: data }) => {
@ -46,7 +46,7 @@ export const sendServerStatus = (status: LX.Sync.ServerStatus) => {
data: status, data: status,
}) })
} }
export const sendSelectMode = (deviceName: string, listener: (mode: LX.Sync.ListSyncMode | null) => void) => { export const sendSelectMode = (deviceName: string, listener: (mode: LX.Sync.List.SyncMode | null) => void) => {
selectModeListenr = listener selectModeListenr = listener
sendSyncAction({ action: 'select_mode', data: deviceName }) sendSyncAction({ action: 'select_mode', data: deviceName })
} }

View File

@ -3,8 +3,8 @@ import '@common/types/app_setting'
import '@common/types/common' import '@common/types/common'
import '@common/types/user_api' import '@common/types/user_api'
import '@common/types/sync' import '@common/types/sync'
import '@common/types/sync_common'
import '@common/types/list' import '@common/types/list'
import '@common/types/list_sync'
import '@common/types/download_list' import '@common/types/download_list'
import '@common/types/music' import '@common/types/music'
import '@common/types/player' import '@common/types/player'

View File

@ -13,9 +13,13 @@ declare global {
keyInfo: ClientKeyInfo keyInfo: ClientKeyInfo
urlInfo: UrlInfo urlInfo: UrlInfo
} }
moduleReadys: {
list: boolean
}
onClose: (handler: (err: Error) => (void | Promise<void>)) => () => void onClose: (handler: (err: Error) => (void | Promise<void>)) => () => void
remoteSyncList: LX.Sync.ServerActions remote: LX.Sync.ServerSyncActions
remoteQueueList: LX.Sync.ServerSyncListActions
} }
interface UrlInfo { interface UrlInfo {
@ -31,11 +35,16 @@ declare global {
isReady: boolean isReady: boolean
userInfo: { name: 'default' } userInfo: { name: 'default' }
keyInfo: ServerKeyInfo keyInfo: ServerKeyInfo
feature: LX.Sync.EnabledFeatures
moduleReadys: {
list: boolean
}
onClose: (handler: (err: Error) => (void | Promise<void>)) => () => void onClose: (handler: (err: Error) => (void | Promise<void>)) => () => void
broadcast: (handler: (client: Socket) => void) => void broadcast: (handler: (client: Socket) => void) => void
remote: LX.Sync.ClientActions remote: LX.Sync.ClientSyncActions
remoteSyncList: LX.Sync.ClientActions remoteQueueList: LX.Sync.ClientSyncListActions
} }
type SocketServer = WS.Server<Socket> type SocketServer = WS.Server<Socket>
} }

25
src/main/types/sync_common.d.ts vendored Normal file
View File

@ -0,0 +1,25 @@
declare namespace LX {
namespace Sync {
type ServerSyncActions = WarpPromiseRecord<{
onFeatureChanged: (feature: EnabledFeatures) => void
}>
type ServerSyncListActions = WarpPromiseRecord<{
onListSyncAction: (action: LX.Sync.List.ActionList) => void
}>
type ClientSyncActions = WarpPromiseRecord<{
getEnabledFeatures: (serverType: ServerType, supportedFeatures: SupportedFeatures) => EnabledFeatures
finished: () => void
}>
type ClientSyncListActions = WarpPromiseRecord<{
onListSyncAction: (action: LX.Sync.List.ActionList) => void
list_sync_get_md5: () => string
list_sync_get_sync_mode: () => LX.Sync.List.SyncMode
list_sync_get_list_data: () => LX.Sync.List.ListData
list_sync_set_list_data: (data: LX.Sync.List.ListData) => void
list_sync_finished: () => void
}>
}
}

View File

@ -2,7 +2,6 @@
"extends": "../../tsconfig.json", "extends": "../../tsconfig.json",
"compilerOptions": { "compilerOptions": {
"isolatedModules": true, "isolatedModules": true,
"moduleResolution": "nodenext",
"paths": { /* Specify a set of entries that re-map imports to additional lookup locations. */ "paths": { /* Specify a set of entries that re-map imports to additional lookup locations. */
"@common/*": ["common/*"], "@common/*": ["common/*"],
// "@renderer/*": ["renderer/*"], // "@renderer/*": ["renderer/*"],

View File

@ -2,7 +2,7 @@ import { markRaw, onBeforeUnmount } from '@common/utils/vueTools'
import { onSyncAction, sendSyncAction } from '@renderer/utils/ipc' import { onSyncAction, sendSyncAction } from '@renderer/utils/ipc'
import { sync } from '@renderer/store' import { sync } from '@renderer/store'
import { appSetting } from '@renderer/store/setting' import { appSetting } from '@renderer/store/setting'
import { SYNC_CODE } from '@common/constants' import { SYNC_CODE } from '@common/constants_sync'
export default () => { export default () => {
const handleSyncList = (event: LX.Sync.SyncMainWindowActions) => { const handleSyncList = (event: LX.Sync.SyncMainWindowActions) => {

View File

@ -2,9 +2,6 @@
"extends": "../../tsconfig.json", "extends": "../../tsconfig.json",
"compilerOptions": { "compilerOptions": {
"isolatedModules": true, "isolatedModules": true,
"module": "ESNext",
"moduleResolution": "nodenext",
"resolveJsonModule": true,
"paths": { /* Specify a set of entries that re-map imports to additional lookup locations. */ "paths": { /* Specify a set of entries that re-map imports to additional lookup locations. */
"@common/*": ["common/*"], "@common/*": ["common/*"],
"@renderer/*": ["renderer/*"], "@renderer/*": ["renderer/*"],

View File

@ -2,6 +2,7 @@ import '@common/types/app_setting'
import '@common/types/common' import '@common/types/common'
import '@common/types/user_api' import '@common/types/user_api'
import '@common/types/sync' import '@common/types/sync'
import '@common/types/list_sync'
import '@common/types/music' import '@common/types/music'
import '@common/types/list' import '@common/types/list'
import '@common/types/download_list' import '@common/types/download_list'

View File

@ -46,7 +46,7 @@ import { openUrl } from '@common/utils/electron'
import { useI18n } from '@renderer/plugins/i18n' import { useI18n } from '@renderer/plugins/i18n'
import { appSetting, updateSetting } from '@renderer/store/setting' import { appSetting, updateSetting } from '@renderer/store/setting'
import { debounce } from '@common/utils/common' import { debounce } from '@common/utils/common'
import { SYNC_CODE } from '@common/constants' import { SYNC_CODE } from '@common/constants_sync'
export default { export default {
name: 'SettingSync', name: 'SettingSync',

View File

@ -5,8 +5,8 @@
"target": "ESNext", "target": "ESNext",
"allowJs": true, "allowJs": true,
"module": "esnext", "module": "esnext",
"resolveJsonModule": true,
"moduleResolution": "nodenext", "moduleResolution": "nodenext",
"resolveJsonModule": true,
"outDir": "./dist", "outDir": "./dist",
"baseUrl": "./src", /* Specify the base directory to resolve non-relative module names. */ "baseUrl": "./src", /* Specify the base directory to resolve non-relative module names. */
// "paths": { /* Specify a set of entries that re-map imports to additional lookup locations. */ // "paths": { /* Specify a set of entries that re-map imports to additional lookup locations. */