优化同步逻辑

This commit is contained in:
lyswhut
2023-08-14 15:33:59 +08:00
parent 4ad9c81381
commit 1c159f59bb
29 changed files with 569 additions and 407 deletions
+2 -1
View File
@@ -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()
+79 -59
View File
@@ -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<void>
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<void>)> }> = {}
let closeEvents: Array<(err: Error) => (void | Promise<void>)> = []
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 }) => {
+10 -1
View File
@@ -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,
}
@@ -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<LX.Sync.Mode> => 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)
}
@@ -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'
@@ -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
}
@@ -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)
})
}
@@ -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)
}
-73
View File
@@ -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<void>((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)
}))
})
+5 -5
View File
@@ -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<string> => {
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<string> => {
return decodeData(enMsg)
// if (!keyInfo) return ''
// let msg = ''
// try {
+2 -1
View File
@@ -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<string, number>()
@@ -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)
+10 -1
View File
@@ -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,
}
@@ -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
}
}
@@ -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)
})
})
})
}
@@ -0,0 +1,4 @@
export * as handler from './handler'
export { default as sync } from './sync'
export * from './localEvent'
@@ -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
}
@@ -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>): LX.Sync.ListData =>
}, listData)
}
const getRemoteListData = async(socket: LX.Sync.Server.Socket): Promise<LX.Sync.ListData> => await new Promise((resolve, reject) => {
const getRemoteListData = async(socket: LX.Sync.Server.Socket): Promise<LX.Sync.ListData> => {
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<string> => 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<string> => {
return socket.remoteSyncList.list_sync_get_md5()
}
const getSyncMode = async(socket: LX.Sync.Server.Socket): Promise<LX.Sync.Mode> => new Promise((resolve, reject) => {
const handleDisconnect = (err: Error) => {
@@ -62,53 +39,38 @@ const getSyncMode = async(socket: LX.Sync.Server.Socket): Promise<LX.Sync.Mode>
removeSelectModeListener()
reject(err)
}
sendSelectMode(socket.keyInfo.deviceName, (mode) => {
removeSelectModeListener()
removeEventClose()
resolve(mode)
})
let removeEventClose = socket.onClose(handleDisconnect)
})
const finishedSync = async(socket: LX.Sync.Server.Socket) => new Promise<void>((resolve, reject) => {
socket.sendData('list:sync:finished', undefined, (err) => {
if (err) reject(err)
else resolve()
})
})
const sendDataPromise = async(socket: LX.Sync.Server.Socket, dataStr: string, key: string) => new Promise<void>((resolve, reject) => {
socket.send(encryptMsg(socket.keyInfo, dataStr), (err) => {
if (err) {
socket.close(SYNC_CLOSE_CODE.failed)
resolve()
sendSelectMode(socket.keyInfo.deviceName, (mode) => {
if (mode == null) {
reject(new Error('cancel'))
return
}
updateDeviceSnapshotKey(socket.keyInfo, key)
resolve()
resolve(mode)
removeSelectModeListener()
removeEventClose()
})
})
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 finishedSync = async(socket: LX.Sync.Server.Socket) => {
await socket.remoteSyncList.list_sync_finished()
}
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<Promise<void>> = []
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<void> => 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<void> => {
await socket.remoteSyncList.list_sync_set_list_data(listData)
updateDeviceSnapshotKey(socket.keyInfo, key)
}
type UserDataObj = Map<string, LX.List.UserListInfoFull>
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)
// }
+68 -33
View File
@@ -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<keyof ActionsType, 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>)> = []
const msg2call = createMsg2call<LX.Sync.ClientActions>({
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<string, string>
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<void>((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) => {
+9 -5
View File
@@ -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<string> => {
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<string> => {
return decodeData(enMsg)
// console.log('decmsg raw: ', len.length, 'en: ', enMsg.length)
// return len
// if (!keyInfo) return ''
// let msg = ''
// try {
+33
View File
@@ -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<string>((resolve, reject) => {
zlib.gzip(data, (err, buf) => {
if (err) {
reject(err)
return
}
resolve(buf.toString('base64'))
})
})
const unGzip = async(data: string) => new Promise<string>((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')
@@ -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<LX.Sync.SyncServiceActions, any>(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 = () => {