重构数据同步功能,新增客户端模式
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
import { request, generateRsaKey } from './utils'
|
||||
import { getSyncAuthKey, setSyncAuthKey } from '../data'
|
||||
import { SYNC_CODE } from '@common/constants'
|
||||
import log from '../log'
|
||||
import { aesDecrypt, aesEncrypt, getComputerName, rsaDecrypt } from '../utils'
|
||||
|
||||
|
||||
const hello = async(urlInfo: LX.Sync.Client.UrlInfo) => request(`${urlInfo.httpProtocol}//${urlInfo.hostPath}/hello`)
|
||||
.then(({ text }) => text == SYNC_CODE.helloMsg)
|
||||
.catch((err: any) => {
|
||||
log.error('[auth] hello', err.message)
|
||||
console.log(err)
|
||||
return false
|
||||
})
|
||||
|
||||
const getServerId = async(urlInfo: LX.Sync.Client.UrlInfo) => request(`${urlInfo.httpProtocol}//${urlInfo.hostPath}/id`)
|
||||
.then(({ text }) => {
|
||||
if (!text.startsWith(SYNC_CODE.idPrefix)) return ''
|
||||
return text.replace(SYNC_CODE.idPrefix, '')
|
||||
})
|
||||
.catch((err: any) => {
|
||||
log.error('[auth] getServerId', err.message)
|
||||
console.log(err)
|
||||
throw err
|
||||
})
|
||||
|
||||
const codeAuth = async(urlInfo: LX.Sync.Client.UrlInfo, serverId: string, authCode: string) => {
|
||||
let key = ''.padStart(16, Buffer.from(authCode).toString('hex'))
|
||||
// const iv = Buffer.from(key.split('').reverse().join('')).toString('base64')
|
||||
key = Buffer.from(key).toString('base64')
|
||||
let { publicKey, privateKey } = await generateRsaKey()
|
||||
publicKey = publicKey.replace(/\n/g, '')
|
||||
.replace('-----BEGIN PUBLIC KEY-----', '')
|
||||
.replace('-----END PUBLIC KEY-----', '')
|
||||
const msg = aesEncrypt(`${SYNC_CODE.authMsg}\n${publicKey}\n${getComputerName()}\nlx_music_desktop`, key)
|
||||
// console.log(msg, key)
|
||||
return request(`${urlInfo.httpProtocol}//${urlInfo.hostPath}/ah`, { headers: { m: msg } }).then(async({ text, code }) => {
|
||||
// console.log(text)
|
||||
switch (text) {
|
||||
case SYNC_CODE.msgBlockedIp:
|
||||
throw new Error(SYNC_CODE.msgBlockedIp)
|
||||
case SYNC_CODE.authFailed:
|
||||
throw new Error(SYNC_CODE.authFailed)
|
||||
default:
|
||||
if (code != 200) throw new Error(SYNC_CODE.authFailed)
|
||||
}
|
||||
let msg
|
||||
try {
|
||||
msg = rsaDecrypt(Buffer.from(text, 'base64'), privateKey).toString()
|
||||
} catch (err: any) {
|
||||
log.error('[auth] codeAuth decryptMsg error', err.message)
|
||||
throw new Error(SYNC_CODE.authFailed)
|
||||
}
|
||||
// console.log(msg)
|
||||
if (!msg) return Promise.reject(new Error(SYNC_CODE.authFailed))
|
||||
const info = JSON.parse(msg) as LX.Sync.ClientKeyInfo
|
||||
void setSyncAuthKey(serverId, info)
|
||||
return info
|
||||
})
|
||||
}
|
||||
|
||||
const keyAuth = async(urlInfo: LX.Sync.Client.UrlInfo, keyInfo: LX.Sync.ClientKeyInfo) => {
|
||||
const msg = aesEncrypt(SYNC_CODE.authMsg + getComputerName(), keyInfo.key)
|
||||
return request(`${urlInfo.httpProtocol}//${urlInfo.hostPath}/ah`, { headers: { i: keyInfo.clientId, m: msg } }).then(({ text, code }) => {
|
||||
if (code != 200) throw new Error(SYNC_CODE.authFailed)
|
||||
|
||||
let msg
|
||||
try {
|
||||
msg = aesDecrypt(text, keyInfo.key)
|
||||
} catch (err: any) {
|
||||
log.error('[auth] keyAuth decryptMsg error', err.message)
|
||||
throw new Error(SYNC_CODE.authFailed)
|
||||
}
|
||||
if (msg != SYNC_CODE.helloMsg) return Promise.reject(new Error(SYNC_CODE.authFailed))
|
||||
})
|
||||
}
|
||||
|
||||
const auth = async(urlInfo: LX.Sync.Client.UrlInfo, serverId: string, authCode?: string) => {
|
||||
if (authCode) return codeAuth(urlInfo, serverId, authCode)
|
||||
const keyInfo = await getSyncAuthKey(serverId)
|
||||
if (!keyInfo) throw new Error(SYNC_CODE.missingAuthCode)
|
||||
await keyAuth(urlInfo, keyInfo)
|
||||
return keyInfo
|
||||
}
|
||||
|
||||
export default async(urlInfo: LX.Sync.Client.UrlInfo, authCode?: string) => {
|
||||
console.log('connect: ', urlInfo.href, authCode)
|
||||
if (!await hello(urlInfo)) throw new Error(SYNC_CODE.connectServiceFailed)
|
||||
const serverId = await getServerId(urlInfo)
|
||||
if (!serverId) throw new Error(SYNC_CODE.getServiceIdFailed)
|
||||
return auth(urlInfo, serverId, authCode)
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
import WebSocket from 'ws'
|
||||
import { encryptMsg, decryptMsg } from './utils'
|
||||
import * as modules from './modules'
|
||||
// import { action as commonAction } from '@/store/modules/common'
|
||||
// import { getStore } from '@/store'
|
||||
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'
|
||||
|
||||
let status: LX.Sync.ClientStatus = {
|
||||
status: false,
|
||||
message: '',
|
||||
address: [],
|
||||
}
|
||||
|
||||
export const sendSyncStatus = (newStatus: Omit<LX.Sync.ClientStatus, 'address'>) => {
|
||||
status.status = newStatus.status
|
||||
status.message = newStatus.message
|
||||
if (status.status) {
|
||||
status.address = getAddress()
|
||||
}
|
||||
sendClientStatus(status)
|
||||
}
|
||||
|
||||
export const sendSyncMessage = (message: string) => {
|
||||
status.message = message
|
||||
sendClientStatus(status)
|
||||
}
|
||||
|
||||
const handleConnection = (socket: LX.Sync.Client.Socket) => {
|
||||
for (const moduleInit of Object.values(modules)) {
|
||||
moduleInit(socket)
|
||||
}
|
||||
}
|
||||
|
||||
const heartbeatTools = {
|
||||
failedNum: 0,
|
||||
pingTimeout: null as NodeJS.Timeout | null,
|
||||
delayRetryTimeout: null as NodeJS.Timeout | null,
|
||||
handleOpen() {
|
||||
console.log('open')
|
||||
this.failedNum = 0
|
||||
this.heartbeat()
|
||||
},
|
||||
heartbeat() {
|
||||
if (this.pingTimeout) clearTimeout(this.pingTimeout)
|
||||
|
||||
// Use `WebSocket#terminate()`, which immediately destroys the connection,
|
||||
// instead of `WebSocket#close()`, which waits for the close timer.
|
||||
// Delay should be equal to the interval at which your server
|
||||
// sends out pings plus a conservative assumption of the latency.
|
||||
this.pingTimeout = setTimeout(() => {
|
||||
client?.terminate()
|
||||
}, 30000 + 1000)
|
||||
},
|
||||
reConnnect() {
|
||||
if (this.pingTimeout) {
|
||||
clearTimeout(this.pingTimeout)
|
||||
this.pingTimeout = null
|
||||
}
|
||||
// client = null
|
||||
if (!client) return
|
||||
|
||||
if (this.failedNum > 3) throw new Error('connect error')
|
||||
|
||||
this.delayRetryTimeout = setTimeout(() => {
|
||||
this.delayRetryTimeout = null
|
||||
if (!client) return
|
||||
console.log(dateFormat(new Date()), 'reconnnect...')
|
||||
connect(client.data.urlInfo, client.data.keyInfo)
|
||||
}, 2000)
|
||||
|
||||
this.failedNum++
|
||||
},
|
||||
clearTimeout() {
|
||||
if (this.delayRetryTimeout) {
|
||||
clearTimeout(this.delayRetryTimeout)
|
||||
this.delayRetryTimeout = null
|
||||
}
|
||||
if (this.pingTimeout) {
|
||||
clearTimeout(this.pingTimeout)
|
||||
this.pingTimeout = null
|
||||
}
|
||||
},
|
||||
connect(socket: LX.Sync.Client.Socket) {
|
||||
console.log('heartbeatTools connect')
|
||||
socket.on('open', () => {
|
||||
this.handleOpen()
|
||||
})
|
||||
socket.on('ping', () => {
|
||||
this.heartbeat()
|
||||
})
|
||||
socket.on('close', (code) => {
|
||||
console.log(code)
|
||||
switch (code) {
|
||||
case SYNC_CLOSE_CODE.normal:
|
||||
case SYNC_CLOSE_CODE.failed:
|
||||
return
|
||||
}
|
||||
this.reConnnect()
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
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))}`, {
|
||||
}) as LX.Sync.Client.Socket
|
||||
client.data = {
|
||||
keyInfo,
|
||||
urlInfo,
|
||||
}
|
||||
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>)> = []
|
||||
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)
|
||||
}
|
||||
}
|
||||
})
|
||||
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 () => {
|
||||
closeEvents.splice(closeEvents.indexOf(handler), 1)
|
||||
}
|
||||
}
|
||||
|
||||
client.addEventListener('open', () => {
|
||||
log.info('connect')
|
||||
// const store = getStore()
|
||||
// global.lx.syncKeyInfo = keyInfo
|
||||
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
|
||||
sendSyncStatus({
|
||||
status: true,
|
||||
message: '',
|
||||
})
|
||||
}).catch(err => {
|
||||
console.log(err)
|
||||
log.r_error(err.stack)
|
||||
sendSyncStatus({
|
||||
status: false,
|
||||
message: err.message,
|
||||
})
|
||||
})
|
||||
})
|
||||
client.addEventListener('close', () => {
|
||||
sendSyncStatus({
|
||||
status: false,
|
||||
message: '',
|
||||
})
|
||||
const err = new Error('closed')
|
||||
for (const handler of closeEvents) void handler(err)
|
||||
closeEvents = []
|
||||
events = {}
|
||||
})
|
||||
}
|
||||
|
||||
export const disconnect = async() => {
|
||||
if (!client) return
|
||||
log.info('disconnecting...')
|
||||
client.close(SYNC_CLOSE_CODE.normal)
|
||||
client = null
|
||||
heartbeatTools.clearTimeout()
|
||||
}
|
||||
|
||||
export const getStatus = (): LX.Sync.ClientStatus => status
|
||||
@@ -0,0 +1,69 @@
|
||||
import handleAuth from './auth'
|
||||
import { connect as socketConnect, disconnect as socketDisconnect, sendSyncStatus, sendSyncMessage } from './client'
|
||||
// import { getSyncHost } from '@/utils/data'
|
||||
import { SYNC_CODE } from '@common/constants'
|
||||
import log from '../log'
|
||||
import { parseUrl } from './utils'
|
||||
|
||||
const handleConnect = async(host: string, authCode?: string) => {
|
||||
// const hostInfo = await getSyncHost()
|
||||
// console.log(hostInfo)
|
||||
// if (!hostInfo || !hostInfo.host || !hostInfo.port) throw new Error(SYNC_CODE.unknownServiceAddress)
|
||||
const urlInfo = parseUrl(host)
|
||||
await disconnectServer(false)
|
||||
const keyInfo = await handleAuth(urlInfo, authCode)
|
||||
socketConnect(urlInfo, keyInfo)
|
||||
}
|
||||
const handleDisconnect = async() => {
|
||||
await socketDisconnect()
|
||||
}
|
||||
|
||||
const connectServer = async(host: string, authCode?: string) => {
|
||||
sendSyncStatus({
|
||||
status: false,
|
||||
message: SYNC_CODE.connecting,
|
||||
})
|
||||
return handleConnect(host, authCode).then(() => {
|
||||
sendSyncStatus({
|
||||
status: true,
|
||||
message: '',
|
||||
})
|
||||
}).catch(async err => {
|
||||
sendSyncStatus({
|
||||
status: false,
|
||||
message: err.message,
|
||||
})
|
||||
switch (err.message) {
|
||||
case SYNC_CODE.connectServiceFailed:
|
||||
case SYNC_CODE.missingAuthCode:
|
||||
break
|
||||
default:
|
||||
log.r_warn(err.message)
|
||||
break
|
||||
}
|
||||
|
||||
return Promise.reject(err)
|
||||
})
|
||||
}
|
||||
|
||||
const disconnectServer = async(isResetStatus = true) => handleDisconnect().then(() => {
|
||||
log.info('disconnect...')
|
||||
if (isResetStatus) {
|
||||
sendSyncStatus({
|
||||
status: false,
|
||||
message: '',
|
||||
})
|
||||
}
|
||||
}).catch((err: any) => {
|
||||
log.error(`disconnect error: ${err.message as string}`)
|
||||
sendSyncMessage(err.message)
|
||||
})
|
||||
|
||||
export {
|
||||
connectServer,
|
||||
disconnectServer,
|
||||
}
|
||||
|
||||
export {
|
||||
getStatus,
|
||||
} from './client'
|
||||
@@ -0,0 +1 @@
|
||||
export { default as list } from './list'
|
||||
@@ -0,0 +1,7 @@
|
||||
import initOn from './on'
|
||||
import initSend from './send'
|
||||
|
||||
export default (socket: LX.Sync.Client.Socket) => {
|
||||
initOn(socket)
|
||||
initSend(socket)
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
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)
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
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)
|
||||
}))
|
||||
})
|
||||
@@ -0,0 +1,115 @@
|
||||
import { generateKeyPair } from 'node:crypto'
|
||||
import { httpFetch, type RequestOptions } from '@main/utils/request'
|
||||
|
||||
export const request = async(url: string, options: RequestOptions = { }) => {
|
||||
return httpFetch(url, {
|
||||
...options,
|
||||
timeout: options.timeout ?? 10000,
|
||||
}).then(response => {
|
||||
return {
|
||||
text: response.body,
|
||||
code: response.statusCode,
|
||||
}
|
||||
})
|
||||
// const controller = new AbortController()
|
||||
// let id: number | null = setTimeout(() => {
|
||||
// id = null
|
||||
// controller.abort()
|
||||
// }, timeout)
|
||||
// return fetch(url, {
|
||||
// ...options,
|
||||
// signal: controller.signal,
|
||||
// // eslint-disable-next-line @typescript-eslint/promise-function-async
|
||||
// }).then(async(response) => {
|
||||
// const text = await response.text()
|
||||
// return {
|
||||
// text,
|
||||
// code: response.status,
|
||||
// }
|
||||
// }).catch(err => {
|
||||
// // console.log(err, err.code, err.message)
|
||||
// throw err
|
||||
// }).finally(() => {
|
||||
// if (id == null) return
|
||||
// clearTimeout(id)
|
||||
// })
|
||||
}
|
||||
|
||||
// export const aesEncrypt = (text: string, key: string, iv: string) => {
|
||||
// const cipher = createCipheriv('aes-128-cbc', Buffer.from(key, 'base64'), Buffer.from(iv, 'base64'))
|
||||
// return Buffer.concat([cipher.update(Buffer.from(text)), cipher.final()]).toString('base64')
|
||||
// }
|
||||
|
||||
// export const aesDecrypt = (text: string, key: string, iv: string) => {
|
||||
// const decipher = createDecipheriv('aes-128-cbc', Buffer.from(key, 'base64'), Buffer.from(iv, 'base64'))
|
||||
// return Buffer.concat([decipher.update(Buffer.from(text, 'base64')), decipher.final()]).toString()
|
||||
// }
|
||||
|
||||
export const generateRsaKey = async() => new Promise<{ publicKey: string, privateKey: string }>((resolve, reject) => {
|
||||
generateKeyPair(
|
||||
'rsa',
|
||||
{
|
||||
modulusLength: 2048, // It holds a number. It is the key size in bits and is applicable for RSA, and DSA algorithm only.
|
||||
publicKeyEncoding: {
|
||||
type: 'spki', // Note the type is pkcs1 not spki
|
||||
format: 'pem',
|
||||
},
|
||||
privateKeyEncoding: {
|
||||
type: 'pkcs8', // Note again the type is set to pkcs1
|
||||
format: 'pem',
|
||||
// cipher: "aes-256-cbc", //Optional
|
||||
// passphrase: "", //Optional
|
||||
},
|
||||
},
|
||||
(err, publicKey, privateKey) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
return
|
||||
}
|
||||
resolve({
|
||||
publicKey,
|
||||
privateKey,
|
||||
})
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
export const encryptMsg = (keyInfo: LX.Sync.ClientKeyInfo, msg: string): string => {
|
||||
return msg
|
||||
// if (!keyInfo) return ''
|
||||
// return aesEncrypt(msg, keyInfo.key, keyInfo.iv)
|
||||
}
|
||||
|
||||
export const decryptMsg = (keyInfo: LX.Sync.ClientKeyInfo, enMsg: string): string => {
|
||||
return enMsg
|
||||
// if (!keyInfo) return ''
|
||||
// let msg = ''
|
||||
// try {
|
||||
// msg = aesDecrypt(enMsg, keyInfo.key, keyInfo.iv)
|
||||
// } catch (err) {
|
||||
// console.log(err)
|
||||
// }
|
||||
// return msg
|
||||
}
|
||||
|
||||
|
||||
export const parseUrl = (host: string): LX.Sync.Client.UrlInfo => {
|
||||
const url = new URL(host)
|
||||
let hostPath = url.host + url.pathname
|
||||
let href = url.href
|
||||
if (hostPath.endsWith('/')) hostPath = hostPath.replace(/\/$/, '')
|
||||
if (href.endsWith('/')) href = href.replace(/\/$/, '')
|
||||
|
||||
return {
|
||||
wsProtocol: url.protocol == 'https:' ? 'wss:' : 'ws:',
|
||||
httpProtocol: url.protocol,
|
||||
hostPath,
|
||||
href,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export const sendStatus = (status: LX.Sync.ClientStatus) => {
|
||||
// syncLog.log(JSON.stringify(status))
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
import { randomBytes } from 'node:crypto'
|
||||
import { STORE_NAMES } from '@common/constants'
|
||||
import getStore from '@main/utils/store'
|
||||
import { throttle } from '@common/utils/common'
|
||||
import path from 'node:path'
|
||||
import fs from 'node:fs'
|
||||
import log from './log'
|
||||
|
||||
|
||||
export const getSyncAuthKey = async(serverId: string) => {
|
||||
const store = getStore(STORE_NAMES.SYNC)
|
||||
const keys = store.get('syncAuthKey') as Record<string, LX.Sync.ClientKeyInfo> | null
|
||||
if (!keys) return null
|
||||
return keys[serverId] ?? null
|
||||
}
|
||||
export const setSyncAuthKey = async(serverId: string, info: LX.Sync.ClientKeyInfo) => {
|
||||
const store = getStore(STORE_NAMES.SYNC)
|
||||
let keys: Record<string, LX.Sync.ClientKeyInfo> = (store.get('syncAuthKey') as Record<string, LX.Sync.ClientKeyInfo> | null) ?? {}
|
||||
keys[serverId] = info
|
||||
store.set('syncAuthKey', keys)
|
||||
}
|
||||
|
||||
let syncHost: string
|
||||
export const getSyncHost = async() => {
|
||||
if (syncHost === undefined) {
|
||||
const store = getStore(STORE_NAMES.SYNC)
|
||||
syncHost = (store.get('syncHost') as typeof syncHost | null) ?? ''
|
||||
}
|
||||
return syncHost
|
||||
}
|
||||
export const setSyncHost = async(host: string) => {
|
||||
// let hostInfo = await getData(syncHostPrefix) || {}
|
||||
// hostInfo.host = host
|
||||
// hostInfo.port = port
|
||||
syncHost = host
|
||||
const store = getStore(STORE_NAMES.SYNC)
|
||||
store.set('syncHost', syncHost)
|
||||
}
|
||||
let syncHostHistory: string[]
|
||||
export const getSyncHostHistory = async() => {
|
||||
if (syncHostHistory === undefined) {
|
||||
const store = getStore(STORE_NAMES.SYNC)
|
||||
syncHostHistory = (store.get('syncHostHistory') as string[]) ?? []
|
||||
}
|
||||
return syncHostHistory
|
||||
}
|
||||
export const addSyncHostHistory = async(host: string) => {
|
||||
let syncHostHistory = await getSyncHostHistory()
|
||||
if (syncHostHistory.some(h => h == host)) return
|
||||
syncHostHistory.unshift(host)
|
||||
if (syncHostHistory.length > 20) syncHostHistory = syncHostHistory.slice(0, 20) // 最多存储20个
|
||||
const store = getStore(STORE_NAMES.SYNC)
|
||||
store.set('syncHostHistory', syncHostHistory)
|
||||
}
|
||||
export const removeSyncHostHistory = async(index: number) => {
|
||||
syncHostHistory.splice(index, 1)
|
||||
const store = getStore(STORE_NAMES.SYNC)
|
||||
store.set('syncHostHistory', syncHostHistory)
|
||||
}
|
||||
|
||||
export interface SnapshotInfo {
|
||||
latest: string | null
|
||||
time: number
|
||||
list: string[]
|
||||
}
|
||||
interface DevicesInfo {
|
||||
serverId: string
|
||||
clients: Record<string, LX.Sync.ServerKeyInfo>
|
||||
snapshotInfo: SnapshotInfo
|
||||
}
|
||||
// const devicesFilePath = path.join(global.lx.dataPath, 'devices.json')
|
||||
const devicesInfo: DevicesInfo = { serverId: '', clients: {}, snapshotInfo: { latest: null, time: 0, list: [] } }
|
||||
let deviceKeys: string[] = []
|
||||
const saveDevicesInfoThrottle = throttle(() => {
|
||||
const store = getStore(STORE_NAMES.SYNC)
|
||||
store.set('keys', devicesInfo.clients)
|
||||
})
|
||||
|
||||
const initDeviceInfo = () => {
|
||||
const store = getStore(STORE_NAMES.SYNC)
|
||||
const serverId = store.get('serverId') as string | undefined
|
||||
if (serverId) devicesInfo.serverId = serverId
|
||||
else {
|
||||
devicesInfo.serverId = randomBytes(4 * 4).toString('base64')
|
||||
saveDevicesInfoThrottle()
|
||||
}
|
||||
const devices = store.get('clients') as DevicesInfo['clients'] | undefined
|
||||
if (devices) devicesInfo.clients = devices
|
||||
deviceKeys = Object.values(devicesInfo.clients).map(device => device.key).filter(k => k)
|
||||
const snapshotInfo = store.get('snapshotInfo') as DevicesInfo['snapshotInfo'] | undefined
|
||||
if (snapshotInfo) devicesInfo.snapshotInfo = snapshotInfo
|
||||
}
|
||||
|
||||
export const createClientKeyInfo = (deviceName: string, isMobile: boolean): LX.Sync.ServerKeyInfo => {
|
||||
const keyInfo: LX.Sync.ServerKeyInfo = {
|
||||
clientId: randomBytes(4 * 4).toString('base64'),
|
||||
key: randomBytes(16).toString('base64'),
|
||||
deviceName,
|
||||
isMobile,
|
||||
snapshotKey: '',
|
||||
lastSyncDate: 0,
|
||||
}
|
||||
saveClientKeyInfo(keyInfo)
|
||||
return keyInfo
|
||||
}
|
||||
export const saveClientKeyInfo = (keyInfo: LX.Sync.ServerKeyInfo) => {
|
||||
if (devicesInfo.clients[keyInfo.clientId] == null && Object.keys(devicesInfo.clients).length > 101) throw new Error('max keys')
|
||||
devicesInfo.clients[keyInfo.clientId] = keyInfo
|
||||
saveDevicesInfoThrottle()
|
||||
}
|
||||
export const getClientKeyInfo = (clientId?: string): LX.Sync.ServerKeyInfo | null => {
|
||||
if (!clientId) return null
|
||||
return devicesInfo.clients[clientId] ?? null
|
||||
}
|
||||
export const getServerId = (): string => {
|
||||
if (!devicesInfo.serverId) initDeviceInfo()
|
||||
return devicesInfo.serverId
|
||||
}
|
||||
export const isIncluedsDevice = (name: string) => {
|
||||
return deviceKeys.includes(name)
|
||||
}
|
||||
export const clearOldSnapshot = async() => {
|
||||
if (!devicesInfo.snapshotInfo) return
|
||||
const snapshotList = devicesInfo.snapshotInfo.list.filter(name => !isIncluedsDevice(name))
|
||||
let requiredSave = snapshotList.length > global.lx.appSetting['sync.server.maxSsnapshotNum']
|
||||
while (snapshotList.length > global.lx.appSetting['sync.server.maxSsnapshotNum']) {
|
||||
const name = snapshotList.pop()
|
||||
if (name) {
|
||||
await removeSnapshot(name)
|
||||
devicesInfo.snapshotInfo.list.splice(devicesInfo.snapshotInfo.list.indexOf(name), 1)
|
||||
} else break
|
||||
}
|
||||
if (requiredSave) saveSnapshotInfo(devicesInfo.snapshotInfo)
|
||||
}
|
||||
export const updateDeviceSnapshotKey = (keyInfo: LX.Sync.ServerKeyInfo, key: string) => {
|
||||
if (keyInfo.snapshotKey) deviceKeys.splice(deviceKeys.indexOf(keyInfo.snapshotKey), 1)
|
||||
keyInfo.snapshotKey = key
|
||||
keyInfo.lastSyncDate = Date.now()
|
||||
saveClientKeyInfo(keyInfo)
|
||||
deviceKeys.push(key)
|
||||
saveDevicesInfoThrottle()
|
||||
void clearOldSnapshot()
|
||||
}
|
||||
|
||||
const saveSnapshotInfoThrottle = throttle(() => {
|
||||
const store = getStore(STORE_NAMES.SYNC)
|
||||
store.set('snapshotInfo', devicesInfo.snapshotInfo)
|
||||
})
|
||||
export const getSnapshotInfo = (): SnapshotInfo => {
|
||||
return devicesInfo.snapshotInfo
|
||||
}
|
||||
export const saveSnapshotInfo = (info: SnapshotInfo) => {
|
||||
devicesInfo.snapshotInfo = info
|
||||
saveSnapshotInfoThrottle()
|
||||
}
|
||||
|
||||
export const getSnapshot = async(name: string) => {
|
||||
const filePath = path.join(global.lxDataPath, `snapshot_${name}`)
|
||||
let listData: LX.Sync.ListData
|
||||
try {
|
||||
listData = JSON.parse((await fs.promises.readFile(filePath)).toString('utf-8'))
|
||||
} catch (err) {
|
||||
log.warn(err)
|
||||
return null
|
||||
}
|
||||
return listData
|
||||
}
|
||||
export const saveSnapshot = async(name: string, data: string) => {
|
||||
const filePath = path.join(global.lxDataPath, `snapshot_${name}`)
|
||||
return fs.promises.writeFile(filePath, data).catch((err) => {
|
||||
log.error(err)
|
||||
throw err
|
||||
})
|
||||
}
|
||||
export const removeSnapshot = async(name: string) => {
|
||||
const filePath = path.join(global.lxDataPath, `snapshot_${name}`)
|
||||
return fs.promises.unlink(filePath).catch((err) => {
|
||||
log.error(err)
|
||||
})
|
||||
}
|
||||
@@ -1,21 +1,28 @@
|
||||
// import Event from './event/event'
|
||||
|
||||
import { disconnectServer } from './client'
|
||||
import { stopServer } from './server'
|
||||
|
||||
// import eventNames from './event/name'
|
||||
import * as modules from './modules'
|
||||
import { startServer, stopServer, getStatus, generateCode } from './server/server'
|
||||
|
||||
|
||||
export {
|
||||
startServer,
|
||||
stopServer,
|
||||
getStatus,
|
||||
getStatus as getServerStatus,
|
||||
generateCode,
|
||||
// Event,
|
||||
// eventNames,
|
||||
modules,
|
||||
}
|
||||
} from './server'
|
||||
|
||||
export {
|
||||
connectServer,
|
||||
disconnectServer,
|
||||
getStatus as getClientStatus,
|
||||
} from './client'
|
||||
|
||||
export default () => {
|
||||
global.lx.event_app.on('main_window_close', () => {
|
||||
void stopServer()
|
||||
if (global.lx.appSetting['sync.mode'] == 'server') {
|
||||
void stopServer()
|
||||
} else {
|
||||
void disconnectServer()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import { log as writeLog } from '@common/utils'
|
||||
|
||||
export default {
|
||||
r_info(...params: any[]) {
|
||||
writeLog.info(...params)
|
||||
},
|
||||
r_warn(...params: any[]) {
|
||||
writeLog.warn(...params)
|
||||
},
|
||||
r_error(...params: any[]) {
|
||||
writeLog.error(...params)
|
||||
},
|
||||
info(...params: any[]) {
|
||||
// if (global.lx.isEnableSyncLog) writeLog.info(...params)
|
||||
console.log(...params)
|
||||
},
|
||||
warn(...params: any[]) {
|
||||
// if (global.lx.isEnableSyncLog) writeLog.warn(...params)
|
||||
console.warn(...params)
|
||||
},
|
||||
error(...params: any[]) {
|
||||
// if (global.lx.isEnableSyncLog) writeLog.error(...params)
|
||||
console.warn(...params)
|
||||
},
|
||||
}
|
||||
@@ -1,22 +1,17 @@
|
||||
import type http from 'http'
|
||||
import { SYNC_CODE } from './config'
|
||||
import {
|
||||
aesEncrypt,
|
||||
aesDecrypt,
|
||||
createClientKeyInfo,
|
||||
getClientKeyInfo,
|
||||
setClientKeyInfo,
|
||||
rsaEncrypt,
|
||||
} from './utils'
|
||||
import { SYNC_CODE } from '@common/constants'
|
||||
import querystring from 'node:querystring'
|
||||
import { getIP } from './utils'
|
||||
import { createClientKeyInfo, getClientKeyInfo, saveClientKeyInfo } from '../data'
|
||||
import { aesDecrypt, aesEncrypt, getComputerName, rsaEncrypt } from '../utils'
|
||||
|
||||
const requestIps = new Map<string, number>()
|
||||
|
||||
export const authCode = async(req: http.IncomingMessage, res: http.ServerResponse, authCode: string) => {
|
||||
export const authCode = async(req: http.IncomingMessage, res: http.ServerResponse, password: string) => {
|
||||
let code = 401
|
||||
let msg: string = SYNC_CODE.msgAuthFailed
|
||||
|
||||
let ip = req.socket.remoteAddress
|
||||
let ip = getIP(req)
|
||||
// console.log(req.headers)
|
||||
if (typeof req.headers.m == 'string') {
|
||||
if (ip && (requestIps.get(ip) ?? 0) < 10) {
|
||||
@@ -38,12 +33,12 @@ export const authCode = async(req: http.IncomingMessage, res: http.ServerRespons
|
||||
const deviceName = text.replace(SYNC_CODE.authMsg, '') || 'Unknown'
|
||||
if (deviceName != keyInfo.deviceName) {
|
||||
keyInfo.deviceName = deviceName
|
||||
setClientKeyInfo(keyInfo)
|
||||
saveClientKeyInfo(keyInfo)
|
||||
}
|
||||
msg = aesEncrypt(SYNC_CODE.helloMsg, keyInfo.key)
|
||||
}
|
||||
} else { // 连接码验证
|
||||
let key = ''.padStart(16, Buffer.from(authCode).toString('hex'))
|
||||
let key = ''.padStart(16, Buffer.from(password).toString('hex'))
|
||||
// const iv = Buffer.from(key.split('').reverse().join('')).toString('base64')
|
||||
key = Buffer.from(key).toString('base64')
|
||||
// console.log(req.headers.m, authCode, key)
|
||||
@@ -59,7 +54,13 @@ export const authCode = async(req: http.IncomingMessage, res: http.ServerRespons
|
||||
const data = text.split('\n')
|
||||
const publicKey = `-----BEGIN PUBLIC KEY-----\n${data[1]}\n-----END PUBLIC KEY-----`
|
||||
const deviceName = data[2] || 'Unknown'
|
||||
msg = rsaEncrypt(Buffer.from(JSON.stringify(createClientKeyInfo(deviceName))), publicKey)
|
||||
const isMobile = data[3] == 'lx_music_mobile'
|
||||
const keyInfo = createClientKeyInfo(deviceName, isMobile)
|
||||
msg = rsaEncrypt(Buffer.from(JSON.stringify({
|
||||
clientId: keyInfo.clientId,
|
||||
key: keyInfo.key,
|
||||
serverName: getComputerName(),
|
||||
})), publicKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
export const SYNC_CODE = {
|
||||
helloMsg: 'Hello~::^-^::~v2~',
|
||||
idPrefix: 'OjppZDo6',
|
||||
authMsg: 'lx-music auth::',
|
||||
msgAuthFailed: 'Auth failed',
|
||||
msgBlockedIp: 'Blocked IP',
|
||||
msgConnect: 'lx-music connect',
|
||||
} as const
|
||||
@@ -1,7 +1,16 @@
|
||||
import { startServer, stopServer, getStatus } from './server'
|
||||
import * as modules from './modules'
|
||||
import {
|
||||
startServer,
|
||||
stopServer,
|
||||
getStatus,
|
||||
generateCode,
|
||||
} from './server'
|
||||
|
||||
|
||||
export {
|
||||
startServer,
|
||||
stopServer,
|
||||
getStatus,
|
||||
generateCode,
|
||||
modules,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
// 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 } from '../../utils'
|
||||
import { createSnapshot, encryptMsg } 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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const sendListAction = async(action: LX.Sync.ActionList) => {
|
||||
console.log('sendListAction', action.action)
|
||||
// io.sockets
|
||||
await broadcast('list:sync:action', action)
|
||||
}
|
||||
|
||||
export const registerListHandler = (_wss: LX.Sync.Server.SocketServer, socket: LX.Sync.Server.Socket) => {
|
||||
if (!wss) {
|
||||
wss = _wss
|
||||
// removeListener = registerListActionEvent()
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
import http from 'http'
|
||||
import { Server, type Socket } from 'socket.io'
|
||||
import { createHttpTerminator, type HttpTerminator } from 'http-terminator'
|
||||
import * as modules from '../modules'
|
||||
import http, { type IncomingMessage } from 'node:http'
|
||||
import url from 'node:url'
|
||||
import { WebSocketServer } from 'ws'
|
||||
import * as modules from './modules'
|
||||
import { authCode, authConnect } from './auth'
|
||||
import { getAddress, getServerId, generateCode as handleGenerateCode, getClientKeyInfo, setClientKeyInfo } from './utils'
|
||||
import { syncList, removeSnapshot } from './syncList'
|
||||
import { log } from '@common/utils'
|
||||
import { sendStatus } from '@main/modules/winMain'
|
||||
import { SYNC_CODE } from './config'
|
||||
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'
|
||||
|
||||
|
||||
let status: LX.Sync.Status = {
|
||||
let status: LX.Sync.ServerStatus = {
|
||||
status: false,
|
||||
message: '',
|
||||
address: [],
|
||||
@@ -27,7 +29,7 @@ const codeTools: {
|
||||
start() {
|
||||
this.stop()
|
||||
this.timeout = setInterval(() => {
|
||||
void generateCode()
|
||||
void handleGenerateCode()
|
||||
}, 60 * 3 * 1000)
|
||||
},
|
||||
stop() {
|
||||
@@ -37,12 +39,45 @@ const codeTools: {
|
||||
},
|
||||
}
|
||||
|
||||
const handleConnection = (io: Server, socket: LX.Sync.Socket) => {
|
||||
console.log('connection')
|
||||
const handleConnection = async(socket: LX.Sync.Server.Socket, request: IncomingMessage) => {
|
||||
const queryData = url.parse(request.url as string, true).query as Record<string, string>
|
||||
|
||||
socket.onClose(() => {
|
||||
// console.log('disconnect', reason)
|
||||
status.devices.splice(status.devices.findIndex(k => k.clientId == keyInfo?.clientId), 1)
|
||||
sendServerStatus(status)
|
||||
})
|
||||
|
||||
|
||||
// // if (typeof socket.handshake.query.i != 'string') return socket.disconnect(true)
|
||||
const keyInfo = getClientKeyInfo(queryData.i)
|
||||
if (!keyInfo) {
|
||||
socket.close(SYNC_CLOSE_CODE.failed)
|
||||
return
|
||||
}
|
||||
keyInfo.lastSyncDate = Date.now()
|
||||
saveClientKeyInfo(keyInfo)
|
||||
// // socket.lx_keyInfo = keyInfo
|
||||
socket.keyInfo = keyInfo
|
||||
try {
|
||||
await syncList(wss as LX.Sync.Server.SocketServer, socket)
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
log.warn(err)
|
||||
return
|
||||
}
|
||||
status.devices.push(keyInfo)
|
||||
// handleConnection(io, socket)
|
||||
sendServerStatus(status)
|
||||
|
||||
// console.log('connection', keyInfo.deviceName)
|
||||
log.info('connection', keyInfo.deviceName)
|
||||
// console.log(socket.handshake.query)
|
||||
for (const module of Object.values(modules)) {
|
||||
module.registerListHandler(io, socket)
|
||||
module.registerListHandler(wss as LX.Sync.Server.SocketServer, socket)
|
||||
}
|
||||
|
||||
socket.isReady = true
|
||||
}
|
||||
|
||||
const handleUnconnection = () => {
|
||||
@@ -64,11 +99,16 @@ const authConnection = (req: http.IncomingMessage, callback: (err: string | null
|
||||
})
|
||||
}
|
||||
|
||||
let httpTerminator: HttpTerminator | null = null
|
||||
let io: Server | null = null
|
||||
let wss: LX.Sync.Server.SocketServer | null
|
||||
let httpServer: http.Server
|
||||
|
||||
const handleStartServer = async(port = 9527) => await new Promise((resolve, reject) => {
|
||||
const httpServer = http.createServer((req, res) => {
|
||||
function noop() {}
|
||||
function onSocketError(err: Error) {
|
||||
console.error(err)
|
||||
}
|
||||
|
||||
const handleStartServer = async(port = 9527, ip = '0.0.0.0') => await new Promise((resolve, reject) => {
|
||||
httpServer = http.createServer((req, res) => {
|
||||
// console.log(req.url)
|
||||
let code
|
||||
let msg
|
||||
@@ -93,46 +133,96 @@ const handleStartServer = async(port = 9527) => await new Promise((resolve, reje
|
||||
res.writeHead(code)
|
||||
res.end(msg)
|
||||
})
|
||||
httpTerminator = createHttpTerminator({
|
||||
server: httpServer,
|
||||
})
|
||||
io = new Server(httpServer, {
|
||||
path: '/sync',
|
||||
serveClient: false,
|
||||
connectTimeout: 10000,
|
||||
pingTimeout: 30000,
|
||||
maxHttpBufferSize: 1e9, // 1G
|
||||
allowRequest: authConnection,
|
||||
transports: ['websocket'],
|
||||
|
||||
wss = new WebSocketServer({
|
||||
noServer: true,
|
||||
})
|
||||
|
||||
io.on('connection', async(_socket: Socket) => {
|
||||
const socket = _socket as LX.Sync.Socket
|
||||
socket.on('disconnect', reason => {
|
||||
console.log('disconnect', reason)
|
||||
status.devices.splice(status.devices.findIndex(k => k.clientId == keyInfo?.clientId), 1)
|
||||
sendStatus(status)
|
||||
if (!status.devices.length) handleUnconnection()
|
||||
wss.on('connection', function(socket, request) {
|
||||
socket.isReady = false
|
||||
socket.on('pong', () => {
|
||||
socket.isAlive = true
|
||||
})
|
||||
if (typeof socket.handshake.query.i != 'string') return socket.disconnect(true)
|
||||
const keyInfo = getClientKeyInfo(socket.handshake.query.i)
|
||||
if (!keyInfo || !io) return socket.disconnect(true)
|
||||
keyInfo.connectionTime = Date.now()
|
||||
setClientKeyInfo(keyInfo)
|
||||
// socket.lx_keyInfo = keyInfo
|
||||
socket.data.keyInfo = keyInfo
|
||||
socket.data.isReady = false
|
||||
try {
|
||||
await syncList(io, socket)
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
log.warn(err)
|
||||
return
|
||||
|
||||
// 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> }> = {}
|
||||
socket.addEventListener('message', ({ data }) => {
|
||||
if (typeof data === 'string') {
|
||||
let syncData: LX.Sync.ActionSync
|
||||
try {
|
||||
syncData = JSON.parse(decryptMsg(socket.keyInfo, 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)
|
||||
}
|
||||
}
|
||||
})
|
||||
socket.addEventListener('close', () => {
|
||||
const err = new Error('closed')
|
||||
for (const handler of Object.values(events).flat()) {
|
||||
// @ts-expect-error
|
||||
handler(err, null)
|
||||
}
|
||||
events = {}
|
||||
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)
|
||||
}
|
||||
}
|
||||
status.devices.push(keyInfo)
|
||||
handleConnection(io, socket)
|
||||
socket.data.isReady = true
|
||||
sendStatus(status)
|
||||
socket.sendData = function(eventName, data, callback) {
|
||||
socket.send(encryptMsg(socket.keyInfo, JSON.stringify({ action: eventName, data })), callback)
|
||||
}
|
||||
void handleConnection(socket, request)
|
||||
})
|
||||
|
||||
httpServer.on('upgrade', function upgrade(request, socket, head) {
|
||||
socket.on('error', onSocketError)
|
||||
// This function is not defined on purpose. Implement it with your own logic.
|
||||
authConnection(request, err => {
|
||||
if (err) {
|
||||
socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n')
|
||||
socket.destroy()
|
||||
return
|
||||
}
|
||||
socket.removeListener('error', onSocketError)
|
||||
|
||||
wss?.handleUpgrade(request, socket, head, function done(ws) {
|
||||
wss?.emit('connection', ws, request)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
const interval = setInterval(() => {
|
||||
wss?.clients.forEach(ws => {
|
||||
if (ws.isAlive == false) {
|
||||
ws.terminate()
|
||||
return
|
||||
}
|
||||
|
||||
ws.isAlive = false
|
||||
ws.ping(noop)
|
||||
if (ws.keyInfo.isMobile) ws.send('ping', noop)
|
||||
})
|
||||
}, 30000)
|
||||
|
||||
wss.on('close', function close() {
|
||||
clearInterval(interval)
|
||||
})
|
||||
|
||||
httpServer.on('error', error => {
|
||||
@@ -142,35 +232,41 @@ const handleStartServer = async(port = 9527) => await new Promise((resolve, reje
|
||||
|
||||
httpServer.on('listening', () => {
|
||||
const addr = httpServer.address()
|
||||
// console.log(addr)
|
||||
if (!addr) {
|
||||
reject(new Error('address is null'))
|
||||
return
|
||||
}
|
||||
const bind = typeof addr == 'string' ? `pipe ${addr}` : `port ${addr.port}`
|
||||
console.info(`Listening on ${bind}`)
|
||||
log.info(`Listening on ${ip} ${bind}`)
|
||||
resolve(null)
|
||||
})
|
||||
|
||||
httpServer.listen(port)
|
||||
httpServer.listen(port, ip)
|
||||
})
|
||||
|
||||
const handleStopServer = async() => {
|
||||
if (!httpTerminator) return
|
||||
if (!io) return
|
||||
io.close()
|
||||
await httpTerminator.terminate().catch(() => {})
|
||||
io = null
|
||||
httpTerminator = null
|
||||
}
|
||||
const handleStopServer = async() => new Promise<void>((resolve, reject) => {
|
||||
if (!wss) return
|
||||
wss.close()
|
||||
wss = null
|
||||
httpServer.close((err) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
return
|
||||
}
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
|
||||
export const stopServer = async() => {
|
||||
console.log('stop')
|
||||
codeTools.stop()
|
||||
if (!status.status) {
|
||||
status.status = false
|
||||
status.message = ''
|
||||
status.address = []
|
||||
status.code = ''
|
||||
sendStatus(status)
|
||||
sendServerStatus(status)
|
||||
return
|
||||
}
|
||||
console.log('stoping sync server...')
|
||||
@@ -184,14 +280,15 @@ export const stopServer = async() => {
|
||||
console.log(err)
|
||||
status.message = err.message
|
||||
}).finally(() => {
|
||||
sendStatus(status)
|
||||
sendServerStatus(status)
|
||||
})
|
||||
}
|
||||
|
||||
export const startServer = async(port: number) => {
|
||||
console.log('status.status', status.status)
|
||||
if (status.status) await handleStopServer()
|
||||
|
||||
console.log('starting sync server...')
|
||||
log.info('starting sync server')
|
||||
await handleStartServer(port).then(() => {
|
||||
console.log('sync server started')
|
||||
status.status = true
|
||||
@@ -207,18 +304,14 @@ export const startServer = async(port: number) => {
|
||||
status.address = []
|
||||
status.code = ''
|
||||
}).finally(() => {
|
||||
sendStatus(status)
|
||||
sendServerStatus(status)
|
||||
})
|
||||
}
|
||||
|
||||
export const getStatus = (): LX.Sync.Status => status
|
||||
export const getStatus = (): LX.Sync.ServerStatus => status
|
||||
|
||||
export const generateCode = async() => {
|
||||
status.code = handleGenerateCode()
|
||||
sendStatus(status)
|
||||
sendServerStatus(status)
|
||||
return status.code
|
||||
}
|
||||
|
||||
export {
|
||||
removeSnapshot,
|
||||
}
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
import { promises as fsPromises } from 'fs'
|
||||
import { encryptMsg, decryptMsg, getSnapshotFilePath } from './utils'
|
||||
import { throttle } from '@common/utils'
|
||||
import { type Server } from 'socket.io'
|
||||
import { sendCloseSelectMode, sendSelectMode } from '@main/modules/winMain'
|
||||
import { LIST_IDS } from '@common/constants'
|
||||
import { SYNC_CLOSE_CODE } from '@common/constants'
|
||||
import { removeSelectModeListener, sendCloseSelectMode, sendSelectMode } from '@main/modules/winMain'
|
||||
import { getSnapshot, updateDeviceSnapshotKey } from '../data'
|
||||
import { getLocalListData, setLocalListData } from '../utils'
|
||||
import { createSnapshot, encryptMsg, getCurrentListInfoKey } from './utils'
|
||||
|
||||
|
||||
const handleSetLocalListData = async(listData: LX.Sync.ListData) => {
|
||||
await setLocalListData(listData)
|
||||
return createSnapshot()
|
||||
}
|
||||
|
||||
// type ListInfoType = LX.List.UserListInfoFull | LX.List.MyDefaultListInfoFull | LX.List.MyLoveListInfoFull
|
||||
|
||||
let io: Server | 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))
|
||||
|
||||
@@ -19,107 +24,93 @@ const patchListData = (listData: Partial<LX.Sync.ListData>): LX.Sync.ListData =>
|
||||
}, listData)
|
||||
}
|
||||
|
||||
const getRemoteListData = async(socket: LX.Sync.Socket): Promise<LX.Sync.ListData> => await new Promise((resolve, reject) => {
|
||||
const getRemoteListData = async(socket: LX.Sync.Server.Socket): Promise<LX.Sync.ListData> => await new Promise((resolve, reject) => {
|
||||
console.log('getRemoteListData')
|
||||
const handleError = (reason: string) => {
|
||||
socket.removeListener('list:sync', handleSuccess)
|
||||
socket.removeListener('disconnect', handleError)
|
||||
reject(new Error(reason))
|
||||
}
|
||||
const handleSuccess = (enData: string) => {
|
||||
socket.removeListener('disconnect', handleError)
|
||||
socket.removeListener('list:sync', handleSuccess)
|
||||
console.log('getRemoteListData', 'handleSuccess')
|
||||
const data: LX.Sync.Data | null = JSON.parse(decryptMsg(socket.data.keyInfo, enData))
|
||||
if (!data) {
|
||||
reject(new Error('Get remote list data failed'))
|
||||
return
|
||||
}
|
||||
if (data.action != 'getData') return
|
||||
resolve(patchListData(data.data))
|
||||
}
|
||||
|
||||
socket.on('disconnect', handleError)
|
||||
socket.on('list:sync', handleSuccess)
|
||||
socket.emit('list:sync', encryptMsg(socket.data.keyInfo, JSON.stringify({ action: 'getData', data: 'all' })))
|
||||
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 getAllLists = async() => {
|
||||
// const lists = []
|
||||
// lists.push(await getListMusics(defaultList.id).then(musics => ({ ...defaultList, list: toRaw(musics).map(m => toOldMusicInfo(m)) })))
|
||||
// lists.push(await getListMusics(loveList.id).then(musics => ({ ...loveList, list: toRaw(musics).map(m => toOldMusicInfo(m)) })))
|
||||
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()
|
||||
})
|
||||
})
|
||||
|
||||
// for await (const list of userLists) {
|
||||
// lists.push(await getListMusics(list.id).then(musics => ({ ...toRaw(list), list: toRaw(musics).map(m => toOldMusicInfo(m)) })))
|
||||
// }
|
||||
|
||||
// return lists
|
||||
// }
|
||||
|
||||
const getLocalListData = async(): Promise<LX.Sync.ListData> => {
|
||||
const lists: LX.Sync.ListData = {
|
||||
defaultList: await global.lx.worker.dbService.getListMusics(LIST_IDS.DEFAULT),
|
||||
loveList: await global.lx.worker.dbService.getListMusics(LIST_IDS.LOVE),
|
||||
userList: [],
|
||||
}
|
||||
|
||||
const userListInfos = await global.lx.worker.dbService.getAllUserList()
|
||||
for await (const list of userListInfos) {
|
||||
lists.userList.push(await global.lx.worker.dbService.getListMusics(list.id)
|
||||
.then(musics => ({ ...list, list: musics })))
|
||||
}
|
||||
|
||||
return lists
|
||||
}
|
||||
const getSyncMode = async(socket: LX.Sync.Socket): Promise<LX.Sync.Mode> => await new Promise((resolve, reject) => {
|
||||
const handleDisconnect = () => {
|
||||
const getSyncMode = async(socket: LX.Sync.Server.Socket): Promise<LX.Sync.Mode> => new Promise((resolve, reject) => {
|
||||
const handleDisconnect = (err: Error) => {
|
||||
sendCloseSelectMode()
|
||||
reject(new Error('disconnect'))
|
||||
removeSelectModeListener()
|
||||
reject(err)
|
||||
}
|
||||
socket.on('disconnect', handleDisconnect)
|
||||
let removeListener = sendSelectMode(socket.data.keyInfo, (mode) => {
|
||||
removeListener()
|
||||
sendSelectMode(socket.keyInfo.deviceName, (mode) => {
|
||||
removeSelectModeListener()
|
||||
removeEventClose()
|
||||
resolve(mode)
|
||||
})
|
||||
let removeEventClose = socket.onClose(handleDisconnect)
|
||||
})
|
||||
|
||||
const finishedSync = (socket: LX.Sync.Socket) => {
|
||||
return socket.emit('list:sync', encryptMsg(socket.data.keyInfo, JSON.stringify({
|
||||
action: 'finished',
|
||||
})))
|
||||
}
|
||||
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 setLocalList = (listData: LX.Sync.ListData) => {
|
||||
void global.lx.event_list.list_data_overwrite(listData, true)
|
||||
}
|
||||
const setRemotelList = async(socket: LX.Sync.Socket, listData: LX.Sync.ListData) => {
|
||||
if (!io) return
|
||||
|
||||
const sockets: LX.Sync.RemoteSocket[] = await io.fetchSockets()
|
||||
for (const socket of sockets) {
|
||||
// if (excludeIds.includes(socket.data.keyInfo.clientId)) continue
|
||||
socket.emit('list:sync', encryptMsg(socket.data.keyInfo, JSON.stringify({ action: 'setData', data: listData })))
|
||||
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()
|
||||
return
|
||||
}
|
||||
updateDeviceSnapshotKey(socket.keyInfo, key)
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
const overwriteRemoteListData = async(listData: LX.Sync.ListData, key: string, excludeIds: string[] = []) => {
|
||||
if (!wss) return
|
||||
const dataStr = JSON.stringify({ action: 'list:sync:action', data: { action: 'list_data_overwrite', data: listData } })
|
||||
const 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))
|
||||
}
|
||||
if (!tasks.length) return
|
||||
await Promise.all(tasks)
|
||||
}
|
||||
|
||||
const writeFilePromises = new Map<string, Promise<void>>()
|
||||
const updateSnapshot = async(path: string, data: string) => {
|
||||
console.log('updateSnapshot', path)
|
||||
let writeFilePromise = writeFilePromises.get(path) ?? Promise.resolve()
|
||||
writeFilePromise = writeFilePromise.then(async() => {
|
||||
if (writeFilePromise !== writeFilePromises.get(path)) return
|
||||
await fsPromises.writeFile(path, data)
|
||||
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()
|
||||
})
|
||||
writeFilePromises.set(path, writeFilePromise)
|
||||
await writeFilePromise.finally(() => {
|
||||
if (writeFilePromise !== writeFilePromises.get(path)) return
|
||||
writeFilePromises.delete(path)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
type UserDataObj = Record<string, LX.List.UserListInfoFull>
|
||||
|
||||
const createUserListDataObj = (listData: LX.Sync.ListData): UserDataObj => {
|
||||
const userListDataObj: UserDataObj = {}
|
||||
for (const list of listData.userList) userListDataObj[list.id] = list
|
||||
@@ -225,12 +216,14 @@ const overwriteList = (sourceListData: LX.Sync.ListData, targetListData: LX.Sync
|
||||
return newListData
|
||||
}
|
||||
|
||||
const handleMergeListData = async(socket: LX.Sync.Socket): Promise<LX.Sync.ListData | null> => {
|
||||
const handleMergeListData = async(socket: LX.Sync.Server.Socket): Promise<[LX.Sync.ListData, boolean, boolean]> => {
|
||||
const mode: LX.Sync.Mode = await getSyncMode(socket)
|
||||
|
||||
const [remoteListData, localListData] = await Promise.all([getRemoteListData(socket), getLocalListData()])
|
||||
console.log('handleMergeListData', 'remoteListData, localListData')
|
||||
let listData: LX.Sync.ListData
|
||||
let requiredUpdateLocalListData = true
|
||||
let requiredUpdateRemoteListData = true
|
||||
switch (mode) {
|
||||
case 'merge_local_remote':
|
||||
listData = mergeList(localListData, remoteListData)
|
||||
@@ -246,63 +239,47 @@ const handleMergeListData = async(socket: LX.Sync.Socket): Promise<LX.Sync.ListD
|
||||
break
|
||||
case 'overwrite_local_remote_full':
|
||||
listData = localListData
|
||||
requiredUpdateLocalListData = false
|
||||
break
|
||||
case 'overwrite_remote_local_full':
|
||||
listData = remoteListData
|
||||
requiredUpdateRemoteListData = false
|
||||
break
|
||||
case 'none': return null
|
||||
// case 'none': return null
|
||||
case 'cancel':
|
||||
socket.disconnect(true)
|
||||
default:
|
||||
socket.close(SYNC_CLOSE_CODE.normal)
|
||||
throw new Error('cancel')
|
||||
}
|
||||
return listData
|
||||
return [listData, requiredUpdateLocalListData, requiredUpdateRemoteListData]
|
||||
}
|
||||
|
||||
const handleSyncList = async(socket: LX.Sync.Socket): Promise<LX.Sync.ListData | null> => {
|
||||
const handleSyncList = async(socket: LX.Sync.Server.Socket) => {
|
||||
const [remoteListData, localListData] = await Promise.all([getRemoteListData(socket), getLocalListData()])
|
||||
console.log('handleSyncList', 'remoteListData, localListData')
|
||||
const listData: LX.Sync.ListData = {
|
||||
defaultList: [],
|
||||
loveList: [],
|
||||
userList: [],
|
||||
}
|
||||
if (localListData.defaultList.length || localListData.loveList.length || localListData.userList.length) {
|
||||
if (remoteListData.defaultList.length || remoteListData.loveList.length || remoteListData.userList.length) {
|
||||
const mergedList = await handleMergeListData(socket)
|
||||
const [mergedList, requiredUpdateLocalListData, requiredUpdateRemoteListData] = await handleMergeListData(socket)
|
||||
console.log('handleMergeListData', 'mergedList')
|
||||
// console.log(mergedList)
|
||||
if (!mergedList) return null
|
||||
listData.defaultList = mergedList.defaultList
|
||||
listData.loveList = mergedList.loveList
|
||||
listData.userList = mergedList.userList
|
||||
setLocalList(mergedList)
|
||||
void setRemotelList(socket, mergedList)
|
||||
let key
|
||||
if (requiredUpdateLocalListData) {
|
||||
key = await handleSetLocalListData(mergedList)
|
||||
await overwriteRemoteListData(mergedList, key, [socket.keyInfo.clientId])
|
||||
}
|
||||
if (requiredUpdateRemoteListData) {
|
||||
if (!key) key = await getCurrentListInfoKey()
|
||||
await setRemotelList(socket, mergedList, key)
|
||||
}
|
||||
} else {
|
||||
void setRemotelList(socket, localListData)
|
||||
listData.defaultList = localListData.defaultList
|
||||
listData.loveList = localListData.loveList
|
||||
listData.userList = localListData.userList
|
||||
await setRemotelList(socket, localListData, await getCurrentListInfoKey())
|
||||
}
|
||||
} else {
|
||||
if (remoteListData.defaultList.length || remoteListData.loveList.length || remoteListData.userList.length) {
|
||||
setLocalList(remoteListData)
|
||||
listData.defaultList = remoteListData.defaultList
|
||||
listData.loveList = remoteListData.loveList
|
||||
listData.userList = remoteListData.userList
|
||||
} else {
|
||||
listData.defaultList = localListData.defaultList
|
||||
listData.loveList = localListData.loveList
|
||||
listData.userList = localListData.userList
|
||||
const key = await handleSetLocalListData(remoteListData)
|
||||
updateDeviceSnapshotKey(socket.keyInfo, key)
|
||||
await overwriteRemoteListData(remoteListData, key, [socket.keyInfo.clientId])
|
||||
}
|
||||
}
|
||||
return await updateSnapshot(socket.data.snapshotFilePath, JSON.stringify({
|
||||
defaultList: listData.defaultList,
|
||||
loveList: listData.loveList,
|
||||
userList: listData.userList,
|
||||
})).then(() => {
|
||||
socket.data.isCreatedSnapshot = true
|
||||
return listData
|
||||
})
|
||||
}
|
||||
|
||||
const mergeListDataFromSnapshot = (
|
||||
@@ -347,7 +324,16 @@ const mergeListDataFromSnapshot = (
|
||||
}
|
||||
return ids.map(id => map.get(id)) as LX.Music.MusicInfo[]
|
||||
}
|
||||
const handleMergeListDataFromSnapshot = async(socket: LX.Sync.Socket, snapshot: LX.Sync.ListData): Promise<LX.Sync.ListData> => {
|
||||
const checkListLatest = async(socket: LX.Sync.Server.Socket) => {
|
||||
const remoteListMD5 = await getRemoteListMD5(socket)
|
||||
const currentListInfoKey = await getCurrentListInfoKey()
|
||||
const latest = remoteListMD5 == currentListInfoKey
|
||||
if (latest && socket.keyInfo.snapshotKey != currentListInfoKey) updateDeviceSnapshotKey(socket.keyInfo, currentListInfoKey)
|
||||
return latest
|
||||
}
|
||||
const handleMergeListDataFromSnapshot = async(socket: LX.Sync.Server.Socket, snapshot: LX.Sync.ListData) => {
|
||||
if (await checkListLatest(socket)) return
|
||||
|
||||
const addMusicLocationType = global.lx.appSetting['list.addMusicLocationType']
|
||||
const [remoteListData, localListData] = await Promise.all([getRemoteListData(socket), getLocalListData()])
|
||||
const newListData: LX.Sync.ListData = {
|
||||
@@ -404,79 +390,37 @@ const handleMergeListDataFromSnapshot = async(socket: LX.Sync.Socket, snapshot:
|
||||
})
|
||||
|
||||
newListData.userList = newUserList
|
||||
setLocalList(newListData)
|
||||
void setRemotelList(socket, newListData)
|
||||
return await updateSnapshot(socket.data.snapshotFilePath, JSON.stringify({
|
||||
defaultList: newListData.defaultList,
|
||||
loveList: newListData.loveList,
|
||||
userList: newListData.userList,
|
||||
})).then(() => {
|
||||
socket.data.isCreatedSnapshot = true
|
||||
return newListData
|
||||
})
|
||||
const key = await handleSetLocalListData(newListData)
|
||||
await setRemotelList(socket, newListData, key)
|
||||
await overwriteRemoteListData(newListData, key, [socket.keyInfo.clientId])
|
||||
}
|
||||
|
||||
const registerUpdateSnapshotTask = (socket: LX.Sync.Socket, snapshot: LX.Sync.ListData) => {
|
||||
if (!socket.data.isCreatedSnapshot) return
|
||||
const handleUpdateSnapshot = throttle(() => {
|
||||
// TODO: 同步成功后再保存快照
|
||||
void getLocalListData().then(({ defaultList, loveList, userList }) => {
|
||||
if (defaultList != null) snapshot.defaultList = defaultList
|
||||
if (loveList != null) snapshot.loveList = loveList
|
||||
if (userList != null) snapshot.userList = userList
|
||||
void updateSnapshot(socket.data.snapshotFilePath, JSON.stringify(snapshot))
|
||||
})
|
||||
}, 2000)
|
||||
global.lx.event_list.on('list_changed', handleUpdateSnapshot)
|
||||
socket.on('disconnect', () => {
|
||||
global.lx.event_list.off('list_changed', handleUpdateSnapshot)
|
||||
})
|
||||
}
|
||||
|
||||
const syncList = async(socket: LX.Sync.Socket): Promise<LX.Sync.ListData | null> => {
|
||||
socket.data.snapshotFilePath = getSnapshotFilePath(socket.data.keyInfo)
|
||||
let fileData: any
|
||||
let isSyncRequired = false
|
||||
try {
|
||||
fileData = (await fsPromises.readFile(socket.data.snapshotFilePath)).toString()
|
||||
fileData = JSON.parse(fileData)
|
||||
} catch (error) {
|
||||
const err = error as NodeJS.ErrnoException
|
||||
if (err.code != 'ENOENT') throw err
|
||||
isSyncRequired = true
|
||||
const syncList = async(socket: LX.Sync.Server.Socket) => {
|
||||
// socket.data.snapshotFilePath = getSnapshotFilePath(socket.keyInfo)
|
||||
if (socket.keyInfo.snapshotKey) {
|
||||
const listData = await getSnapshot(socket.keyInfo.snapshotKey)
|
||||
if (listData) {
|
||||
await handleMergeListDataFromSnapshot(socket, listData)
|
||||
return
|
||||
}
|
||||
}
|
||||
console.log('isSyncRequired', isSyncRequired)
|
||||
if (isSyncRequired) return await handleSyncList(socket)
|
||||
return await handleMergeListDataFromSnapshot(socket, patchListData(fileData))
|
||||
await handleSyncList(socket)
|
||||
}
|
||||
|
||||
// const checkSyncQueue = async(): Promise<void> => {
|
||||
// if (!syncingId) return
|
||||
// console.log('sync queue...')
|
||||
// await wait()
|
||||
// await checkSyncQueue()
|
||||
// }
|
||||
|
||||
// export {
|
||||
// syncList = async(_io: Server, socket: LX.Sync.Socket) => {
|
||||
// io = _io
|
||||
// await checkSyncQueue()
|
||||
// syncingId = socket.data.keyInfo.clientId
|
||||
// return await syncList(socket).then(newListData => {
|
||||
// registerUpdateSnapshotTask(socket, { ...newListData })
|
||||
// return finishedSync(socket)
|
||||
// }).finally(() => {
|
||||
// syncingId = null
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
export default async(_wss: LX.Sync.Server.SocketServer, socket: LX.Sync.Server.Socket) => {
|
||||
if (!wss) {
|
||||
wss = _wss
|
||||
_wss.addListener('close', () => {
|
||||
wss = null
|
||||
})
|
||||
}
|
||||
|
||||
const _syncList = async(_io: Server, socket: LX.Sync.Socket) => {
|
||||
io = _io
|
||||
let disconnected = false
|
||||
socket.on('disconnect', () => {
|
||||
socket.onClose(() => {
|
||||
disconnected = true
|
||||
if (syncingId == socket.data.keyInfo.clientId) syncingId = null
|
||||
if (syncingId == socket.keyInfo.clientId) syncingId = null
|
||||
})
|
||||
|
||||
while (true) {
|
||||
@@ -485,22 +429,16 @@ const _syncList = async(_io: Server, socket: LX.Sync.Socket) => {
|
||||
await wait()
|
||||
}
|
||||
|
||||
syncingId = socket.data.keyInfo.clientId
|
||||
return await syncList(socket).then(newListData => {
|
||||
if (newListData) registerUpdateSnapshotTask(socket, { ...newListData })
|
||||
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 {
|
||||
_syncList as syncList,
|
||||
removeSnapshot,
|
||||
}
|
||||
|
||||
// const removeSnapshot = async(keyInfo: LX.Sync.KeyInfo) => {
|
||||
// const filePath = getSnapshotFilePath(keyInfo)
|
||||
// await fsPromises.unlink(filePath)
|
||||
// }
|
||||
|
||||
@@ -1,97 +1,28 @@
|
||||
import { networkInterfaces } from 'os'
|
||||
import { randomBytes, createCipheriv, createDecipheriv, publicEncrypt, privateDecrypt, constants } from 'crypto'
|
||||
import { join } from 'path'
|
||||
import getStore from '@main/utils/store'
|
||||
|
||||
const STORE_NAME = 'sync'
|
||||
|
||||
type KeyInfos = Record<string, LX.Sync.KeyInfo>
|
||||
|
||||
export const getAddress = (): string[] => {
|
||||
const nets = networkInterfaces()
|
||||
const results: string[] = []
|
||||
// console.log(nets)
|
||||
|
||||
for (const interfaceInfos of Object.values(nets)) {
|
||||
if (!interfaceInfos) continue
|
||||
// Skip over non-IPv4 and internal (i.e. 127.0.0.1) addresses
|
||||
for (const interfaceInfo of interfaceInfos) {
|
||||
if (interfaceInfo.family === 'IPv4' && !interfaceInfo.internal) {
|
||||
results.push(interfaceInfo.address)
|
||||
}
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
let serverId: string | undefined
|
||||
export const getServerId = (): string => {
|
||||
if (serverId) return serverId
|
||||
const store = getStore(STORE_NAME)
|
||||
serverId = store.get('serverId') as string | undefined
|
||||
if (!serverId) {
|
||||
serverId = randomBytes(4 * 4).toString('base64')
|
||||
store.set('serverId', serverId)
|
||||
}
|
||||
return serverId
|
||||
}
|
||||
|
||||
let keyInfos: KeyInfos
|
||||
export const createClientKeyInfo = (deviceName: string): LX.Sync.KeyInfo => {
|
||||
const keyInfo: LX.Sync.KeyInfo = {
|
||||
clientId: randomBytes(4 * 4).toString('base64'),
|
||||
key: randomBytes(16).toString('base64'),
|
||||
deviceName,
|
||||
}
|
||||
const store = getStore(STORE_NAME)
|
||||
keyInfos ??= store.get('keys') as KeyInfos || {}
|
||||
if (Object.keys(keyInfos).length > 101) throw new Error('max keys')
|
||||
|
||||
keyInfos[keyInfo.clientId] = keyInfo
|
||||
store.set('keys', keyInfos)
|
||||
return keyInfo
|
||||
}
|
||||
export const setClientKeyInfo = (keyInfo: LX.Sync.KeyInfo) => {
|
||||
keyInfos[keyInfo.clientId] = keyInfo
|
||||
const store = getStore(STORE_NAME)
|
||||
store.set('keys', keyInfos)
|
||||
}
|
||||
export const getClientKeyInfo = (clientId: string): LX.Sync.KeyInfo | null => {
|
||||
if (!keyInfos) {
|
||||
const store = getStore(STORE_NAME)
|
||||
keyInfos = store.get('keys') as KeyInfos || {}
|
||||
}
|
||||
return keyInfos[clientId] ?? null
|
||||
}
|
||||
import { toMD5 } from '@common/utils/nodejs'
|
||||
import type http from 'node:http'
|
||||
import {
|
||||
getSnapshotInfo,
|
||||
saveSnapshot,
|
||||
saveSnapshotInfo,
|
||||
type SnapshotInfo,
|
||||
} from '../data'
|
||||
import { getLocalListData } from '../utils'
|
||||
|
||||
export const generateCode = (): string => {
|
||||
return Math.random().toString().substring(2, 8)
|
||||
}
|
||||
|
||||
export const aesEncrypt = (buffer: string | Buffer, key: string): string => {
|
||||
const cipher = createCipheriv('aes-128-ecb', Buffer.from(key, 'base64'), '')
|
||||
return Buffer.concat([cipher.update(buffer), cipher.final()]).toString('base64')
|
||||
export const getIP = (request: http.IncomingMessage) => {
|
||||
return request.socket.remoteAddress
|
||||
}
|
||||
|
||||
export const aesDecrypt = (text: string, key: string): string => {
|
||||
const decipher = createDecipheriv('aes-128-ecb', Buffer.from(key, 'base64'), '')
|
||||
return Buffer.concat([decipher.update(Buffer.from(text, 'base64')), decipher.final()]).toString()
|
||||
}
|
||||
|
||||
export const rsaEncrypt = (buffer: Buffer, key: string): string => {
|
||||
return publicEncrypt({ key, padding: constants.RSA_PKCS1_OAEP_PADDING }, buffer).toString('base64')
|
||||
}
|
||||
export const rsaDecrypt = (buffer: Buffer, key: string): Buffer => {
|
||||
return privateDecrypt({ key, padding: constants.RSA_PKCS1_OAEP_PADDING }, buffer)
|
||||
}
|
||||
|
||||
export const encryptMsg = (keyInfo: LX.Sync.KeyInfo, msg: string): string => {
|
||||
export const encryptMsg = (keyInfo: LX.Sync.ServerKeyInfo, msg: string): string => {
|
||||
return msg
|
||||
// if (!keyInfo) return ''
|
||||
// return aesEncrypt(msg, keyInfo.key, keyInfo.iv)
|
||||
}
|
||||
|
||||
export const decryptMsg = (keyInfo: LX.Sync.KeyInfo, enMsg: string): string => {
|
||||
export const decryptMsg = (keyInfo: LX.Sync.ServerKeyInfo, enMsg: string): string => {
|
||||
return enMsg
|
||||
// if (!keyInfo) return ''
|
||||
// let msg = ''
|
||||
@@ -103,6 +34,29 @@ export const decryptMsg = (keyInfo: LX.Sync.KeyInfo, enMsg: string): string => {
|
||||
// return msg
|
||||
}
|
||||
|
||||
export const getSnapshotFilePath = (keyInfo: LX.Sync.KeyInfo): string => {
|
||||
return join(global.lxDataPath, `snapshot-${Buffer.from(keyInfo.clientId).toString('hex').substring(0, 10)}.json`)
|
||||
let snapshotInfo: SnapshotInfo
|
||||
export const createSnapshot = async() => {
|
||||
if (!snapshotInfo) snapshotInfo = getSnapshotInfo()
|
||||
const listData = JSON.stringify(await getLocalListData())
|
||||
const md5 = toMD5(listData)
|
||||
if (snapshotInfo.latest == md5) return md5
|
||||
if (snapshotInfo.list.includes(md5)) {
|
||||
snapshotInfo.list.splice(snapshotInfo.list.indexOf(md5), 1)
|
||||
} else await saveSnapshot(md5, listData)
|
||||
if (snapshotInfo.latest) snapshotInfo.list.unshift(snapshotInfo.latest)
|
||||
snapshotInfo.latest = md5
|
||||
snapshotInfo.time = Date.now()
|
||||
saveSnapshotInfo(snapshotInfo)
|
||||
return md5
|
||||
}
|
||||
|
||||
|
||||
export const getCurrentListInfoKey = async() => {
|
||||
if (!snapshotInfo) snapshotInfo = getSnapshotInfo()
|
||||
if (snapshotInfo.latest) {
|
||||
return snapshotInfo.latest
|
||||
}
|
||||
snapshotInfo.latest = toMD5(JSON.stringify(await getLocalListData()))
|
||||
saveSnapshotInfo(snapshotInfo)
|
||||
return snapshotInfo.latest
|
||||
}
|
||||
|
||||
@@ -1,59 +1,84 @@
|
||||
// import { throttle } from '@common/utils/common'
|
||||
import { type Server } from 'socket.io'
|
||||
// import { sendSyncActionList } from '@main/modules/winMain'
|
||||
import { encryptMsg, decryptMsg } from '../server/utils'
|
||||
import { createCipheriv, createDecipheriv, publicEncrypt, privateDecrypt, constants } from 'node:crypto'
|
||||
import os, { networkInterfaces } from 'node:os'
|
||||
import cp from 'node:child_process'
|
||||
import { LIST_IDS } from '@common/constants'
|
||||
|
||||
let io: Server | null
|
||||
let removeListener: (() => void) | null
|
||||
|
||||
type listAction = 'list:action'
|
||||
export const getAddress = (): string[] => {
|
||||
const nets = networkInterfaces()
|
||||
const results: string[] = []
|
||||
// console.log(nets)
|
||||
|
||||
const handleListAction = ({ action, data }: LX.Sync.ActionList) => {
|
||||
// console.log('handleListAction', action)
|
||||
|
||||
switch (action) {
|
||||
case 'list_data_overwrite':
|
||||
void global.lx.event_list.list_data_overwrite(data, true)
|
||||
break
|
||||
case 'list_create':
|
||||
void global.lx.event_list.list_create(data.position, data.listInfos, true)
|
||||
break
|
||||
case 'list_remove':
|
||||
void global.lx.event_list.list_remove(data, true)
|
||||
break
|
||||
case 'list_update':
|
||||
void global.lx.event_list.list_update(data, true)
|
||||
break
|
||||
case 'list_update_position':
|
||||
void global.lx.event_list.list_update_position(data.position, data.ids, true)
|
||||
break
|
||||
case 'list_music_add':
|
||||
void global.lx.event_list.list_music_add(data.id, data.musicInfos, data.addMusicLocationType, true)
|
||||
break
|
||||
case 'list_music_move':
|
||||
void global.lx.event_list.list_music_move(data.fromId, data.toId, data.musicInfos, data.addMusicLocationType, true)
|
||||
break
|
||||
case 'list_music_remove':
|
||||
void global.lx.event_list.list_music_remove(data.listId, data.ids, true)
|
||||
break
|
||||
case 'list_music_update':
|
||||
void global.lx.event_list.list_music_update(data, true)
|
||||
break
|
||||
case 'list_music_update_position':
|
||||
void global.lx.event_list.list_music_update_position(data.listId, data.position, data.ids, true)
|
||||
break
|
||||
case 'list_music_overwrite':
|
||||
void global.lx.event_list.list_music_overwrite(data.listId, data.musicInfos, true)
|
||||
break
|
||||
case 'list_music_clear':
|
||||
void global.lx.event_list.list_music_clear(data, true)
|
||||
break
|
||||
default:
|
||||
break
|
||||
for (const interfaceInfos of Object.values(nets)) {
|
||||
if (!interfaceInfos) continue
|
||||
// Skip over non-IPv4 and internal (i.e. 127.0.0.1) addresses
|
||||
for (const interfaceInfo of interfaceInfos) {
|
||||
if (interfaceInfo.family === 'IPv4' && !interfaceInfo.internal) {
|
||||
results.push(interfaceInfo.address)
|
||||
}
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
const registerListActionEvent = () => {
|
||||
|
||||
// https://stackoverflow.com/a/75309339
|
||||
export const getComputerName = () => {
|
||||
let name: string | undefined
|
||||
switch (process.platform) {
|
||||
case 'win32':
|
||||
name = process.env.COMPUTERNAME
|
||||
break
|
||||
case 'darwin':
|
||||
name = cp.execSync('scutil --get ComputerName').toString().trim()
|
||||
break
|
||||
case 'linux':
|
||||
name = cp.execSync('hostnamectl --pretty').toString().trim()
|
||||
break
|
||||
}
|
||||
if (!name) name = os.hostname()
|
||||
return name
|
||||
}
|
||||
|
||||
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')
|
||||
}
|
||||
|
||||
export const aesDecrypt = (text: string, key: string) => {
|
||||
const decipher = createDecipheriv('aes-128-ecb', Buffer.from(key, 'base64'), '')
|
||||
return Buffer.concat([decipher.update(Buffer.from(text, 'base64')), decipher.final()]).toString()
|
||||
}
|
||||
|
||||
export const rsaEncrypt = (buffer: Buffer, key: string): string => {
|
||||
return publicEncrypt({ key, padding: constants.RSA_PKCS1_OAEP_PADDING }, buffer).toString('base64')
|
||||
}
|
||||
export const rsaDecrypt = (buffer: Buffer, key: string): Buffer => {
|
||||
return privateDecrypt({ key, padding: constants.RSA_PKCS1_OAEP_PADDING }, buffer)
|
||||
}
|
||||
|
||||
export const getLocalListData = async(): Promise<LX.Sync.ListData> => {
|
||||
const lists: LX.Sync.ListData = {
|
||||
defaultList: await global.lx.worker.dbService.getListMusics(LIST_IDS.DEFAULT),
|
||||
loveList: await global.lx.worker.dbService.getListMusics(LIST_IDS.LOVE),
|
||||
userList: [],
|
||||
}
|
||||
|
||||
const userListInfos = await global.lx.worker.dbService.getAllUserList()
|
||||
for await (const list of userListInfos) {
|
||||
lists.userList.push(await global.lx.worker.dbService.getListMusics(list.id)
|
||||
.then(musics => ({ ...list, list: musics })))
|
||||
}
|
||||
|
||||
return lists
|
||||
}
|
||||
|
||||
export const setLocalListData = async(listData: LX.Sync.ListData) => {
|
||||
await global.lx.event_list.list_data_overwrite(listData, true)
|
||||
}
|
||||
|
||||
|
||||
export const registerListActionEvent = (sendListAction: (action: LX.Sync.ActionList) => (void | Promise<void>)) => {
|
||||
const list_data_overwrite = async(listData: MakeOptional<LX.List.ListDataFull, 'tempList'>, isRemote: boolean = false) => {
|
||||
if (isRemote) return
|
||||
await sendListAction({ action: 'list_data_overwrite', data: listData })
|
||||
@@ -130,48 +155,48 @@ const registerListActionEvent = () => {
|
||||
}
|
||||
}
|
||||
|
||||
// const addMusic = (orderId, callback) => {
|
||||
// // ...
|
||||
// }
|
||||
export const handleRemoteListAction = async({ action, data }: LX.Sync.ActionList) => {
|
||||
// console.log('handleRemoteListAction', action)
|
||||
|
||||
const broadcast = async(action: listAction, data: any, excludeIds: string[] = []) => {
|
||||
if (!io) return
|
||||
const sockets: LX.Sync.RemoteSocket[] = await io.fetchSockets()
|
||||
for (const socket of sockets) {
|
||||
if (excludeIds.includes(socket.data.keyInfo.clientId) || !socket.data.isReady) continue
|
||||
socket.emit(action, encryptMsg(socket.data.keyInfo, data))
|
||||
}
|
||||
}
|
||||
|
||||
export const sendListAction = async(action: LX.Sync.ActionList) => {
|
||||
console.log('sendListAction', action.action)
|
||||
// io.sockets
|
||||
await broadcast('list:action', JSON.stringify(action))
|
||||
}
|
||||
|
||||
export const registerListHandler = (_io: Server, socket: LX.Sync.Socket) => {
|
||||
if (!io) {
|
||||
io = _io
|
||||
removeListener = registerListActionEvent()
|
||||
}
|
||||
|
||||
socket.on('list:action', msg => {
|
||||
if (!socket.data.isReady) return
|
||||
// console.log(msg)
|
||||
msg = decryptMsg(socket.data.keyInfo, msg)
|
||||
if (!msg) return
|
||||
handleListAction(JSON.parse(msg))
|
||||
void broadcast('list:action', msg, [socket.data.keyInfo.clientId])
|
||||
// socket.broadcast.emit('list:action', { action: 'list_remove', data: { id: 'default', index: 0 } })
|
||||
})
|
||||
|
||||
// socket.on('list:add', addMusic)
|
||||
}
|
||||
export const unregisterListHandler = () => {
|
||||
io = null
|
||||
|
||||
if (removeListener) {
|
||||
removeListener()
|
||||
removeListener = null
|
||||
switch (action) {
|
||||
case 'list_data_overwrite':
|
||||
void global.lx.event_list.list_data_overwrite(data, true)
|
||||
break
|
||||
case 'list_create':
|
||||
void global.lx.event_list.list_create(data.position, data.listInfos, true)
|
||||
break
|
||||
case 'list_remove':
|
||||
void global.lx.event_list.list_remove(data, true)
|
||||
break
|
||||
case 'list_update':
|
||||
void global.lx.event_list.list_update(data, true)
|
||||
break
|
||||
case 'list_update_position':
|
||||
void global.lx.event_list.list_update_position(data.position, data.ids, true)
|
||||
break
|
||||
case 'list_music_add':
|
||||
void global.lx.event_list.list_music_add(data.id, data.musicInfos, data.addMusicLocationType, true)
|
||||
break
|
||||
case 'list_music_move':
|
||||
void global.lx.event_list.list_music_move(data.fromId, data.toId, data.musicInfos, data.addMusicLocationType, true)
|
||||
break
|
||||
case 'list_music_remove':
|
||||
void global.lx.event_list.list_music_remove(data.listId, data.ids, true)
|
||||
break
|
||||
case 'list_music_update':
|
||||
void global.lx.event_list.list_music_update(data, true)
|
||||
break
|
||||
case 'list_music_update_position':
|
||||
void global.lx.event_list.list_music_update_position(data.listId, data.position, data.ids, true)
|
||||
break
|
||||
case 'list_music_overwrite':
|
||||
void global.lx.event_list.list_music_overwrite(data.listId, data.musicInfos, true)
|
||||
break
|
||||
case 'list_music_clear':
|
||||
void global.lx.event_list.list_music_clear(data, true)
|
||||
break
|
||||
default:
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { mainHandle } from '@common/mainIpc'
|
||||
import { WIN_MAIN_RENDERER_EVENT_NAME } from '@common/ipcNames'
|
||||
import { startServer, stopServer, getStatus, generateCode } from '@main/modules/sync'
|
||||
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
|
||||
@@ -8,13 +8,15 @@ let selectModeListenr: ((mode: LX.Sync.Mode) => void) | null = null
|
||||
export default () => {
|
||||
mainHandle<LX.Sync.SyncServiceActions, any>(WIN_MAIN_RENDERER_EVENT_NAME.sync_action, async({ params: data }) => {
|
||||
switch (data.action) {
|
||||
case 'enable':
|
||||
case 'enable_server':
|
||||
data.data.enable ? await startServer(parseInt(data.data.port)) : await stopServer()
|
||||
return
|
||||
case 'get_status':
|
||||
return getStatus()
|
||||
case 'generate_code':
|
||||
return await generateCode()
|
||||
case 'enable_client':
|
||||
data.data.enable ? await connectServer(data.data.host, data.data.authCode) : await disconnectServer()
|
||||
return
|
||||
case 'get_server_status': return getServerStatus()
|
||||
case 'get_client_status': return getClientStatus()
|
||||
case 'generate_code': return generateCode()
|
||||
case 'select_mode':
|
||||
if (selectModeListenr) selectModeListenr(data.data)
|
||||
break
|
||||
@@ -29,18 +31,24 @@ export const sendSyncAction = (data: LX.Sync.SyncMainWindowActions) => {
|
||||
sendEvent(WIN_MAIN_RENDERER_EVENT_NAME.sync_action, data)
|
||||
}
|
||||
|
||||
export const sendStatus = (status: LX.Sync.Status) => {
|
||||
export const sendClientStatus = (status: LX.Sync.ClientStatus) => {
|
||||
sendSyncAction({
|
||||
action: 'status',
|
||||
action: 'client_status',
|
||||
data: status,
|
||||
})
|
||||
}
|
||||
export const sendSelectMode = (keyInfo: LX.Sync.KeyInfo, listener: (mode: LX.Sync.Mode) => void) => {
|
||||
export const sendServerStatus = (status: LX.Sync.ServerStatus) => {
|
||||
sendSyncAction({
|
||||
action: 'server_status',
|
||||
data: status,
|
||||
})
|
||||
}
|
||||
export const sendSelectMode = (deviceName: string, listener: (mode: LX.Sync.Mode) => void) => {
|
||||
selectModeListenr = listener
|
||||
sendSyncAction({ action: 'select_mode', data: keyInfo })
|
||||
return () => {
|
||||
selectModeListenr = null
|
||||
}
|
||||
sendSyncAction({ action: 'select_mode', data: deviceName })
|
||||
}
|
||||
export const removeSelectModeListener = () => {
|
||||
selectModeListenr = null
|
||||
}
|
||||
export const sendCloseSelectMode = () => {
|
||||
sendSyncAction({ action: 'close_select_mode' })
|
||||
|
||||
Reference in New Issue
Block a user