自定义源变更

This commit is contained in:
lyswhut
2023-11-07 22:18:56 +08:00
parent 0c6cedf879
commit b1f2a27eac
11 changed files with 248 additions and 151 deletions
+7 -6
View File
@@ -4,6 +4,7 @@ import fs from 'fs'
import path from 'node:path'
import { openDevTools as handleOpenDevTools } from '@main/utils'
import { encodePath } from '@common/utils/electron'
import USER_API_RENDERER_EVENT_NAME from './rendererEvent/name'
let browserWindow: Electron.BrowserWindow | null = null
@@ -87,12 +88,12 @@ export const createWindow = async(userApi: LX.UserApi.UserApiInfo) => {
winEvent()
// console.log(html.replace('</body>', `<script>${userApi.script}</script></body>`))
const randomNum = Math.random().toString().substring(2, 10)
await browserWindow.loadURL(
'data:text/html;charset=UTF-8,' + encodeURIComponent(html
.replace('<meta http-equiv="Content-Security-Policy" content="default-src \'none\'">',
`<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'nonce-${randomNum}';">`)
.replace('</body>', `<script nonce="${randomNum}">${userApi.script}</script></body>`)))
// const randomNum = Math.random().toString().substring(2, 10)
await browserWindow.loadURL('data:text/html;charset=UTF-8,' + encodeURIComponent(html))
browserWindow.on('ready-to-show', () => {
sendEvent(USER_API_RENDERER_EVENT_NAME.initEnv, userApi)
})
// global.modules.userApiWindow.loadFile(join(dir, 'renderer/user-api.html'))
// global.modules.userApiWindow.webContents.openDevTools()
+172 -132
View File
@@ -1,4 +1,4 @@
import { contextBridge, ipcRenderer } from 'electron'
import { contextBridge, ipcRenderer, webFrame } from 'electron'
import needle from 'needle'
import zlib from 'zlib'
import { createCipheriv, publicEncrypt, constants, randomBytes, createHash } from 'crypto'
@@ -74,7 +74,6 @@ const handleRequest = (context, { requestKey, data }) => {
* @param {*} context
* @param {*} info {
* openDevTools: false,
* status: true,
* message: 'xxx',
* sources: {
* kw: ['128k', '320k', 'flac', 'flac24bit'],
@@ -87,18 +86,18 @@ const handleRequest = (context, { requestKey, data }) => {
*/
const handleInit = (context, info) => {
if (!info) {
sendMessage(USER_API_RENDERER_EVENT_NAME.init, null, false, 'Init failed')
sendMessage(USER_API_RENDERER_EVENT_NAME.init, null, false, 'Missing required parameter init info')
// sendMessage(USER_API_RENDERER_EVENT_NAME.init, false, null, typeof info.message === 'string' ? info.message.substring(0, 100) : '')
return
}
if (info.openDevTools === true) {
sendMessage(USER_API_RENDERER_EVENT_NAME.openDevTools)
}
if (!info.status) {
sendMessage(USER_API_RENDERER_EVENT_NAME.init, null, false, 'Init failed')
// sendMessage(USER_API_RENDERER_EVENT_NAME.init, false, null, typeof info.message === 'string' ? info.message.substring(0, 100) : '')
return
}
// if (!info.status) {
// sendMessage(USER_API_RENDERER_EVENT_NAME.init, null, false, 'Missing required parameter init info')
// // sendMessage(USER_API_RENDERER_EVENT_NAME.init, false, null, typeof info.message === 'string' ? info.message.substring(0, 100) : '')
// return
// }
const sourceInfo = {
sources: {},
}
@@ -138,140 +137,181 @@ const handleShowUpdateAlert = (data, resolve, reject) => {
resolve()
}
contextBridge.exposeInMainWorld('lx', {
EVENT_NAMES,
request(url, { method = 'get', timeout, headers, body, form, formData }, callback) {
let options = { headers }
let data
if (body) {
data = body
} else if (form) {
data = form
// data.content_type = 'application/x-www-form-urlencoded'
options.json = false
} else if (formData) {
data = formData
// data.content_type = 'multipart/form-data'
options.json = false
}
options.response_timeout = timeout
let request = needle.request(method, url, data, options, (err, resp, body) => {
if (!err) {
body = resp.body = resp.raw.toString()
try {
resp.body = JSON.parse(resp.body)
} catch (_) {}
body = resp.body
const initEnv = (userApi) => {
contextBridge.exposeInMainWorld('lx', {
EVENT_NAMES,
request(url, { method = 'get', timeout, headers, body, form, formData }, callback) {
let options = { headers }
let data
if (body) {
data = body
} else if (form) {
data = form
// data.content_type = 'application/x-www-form-urlencoded'
options.json = false
} else if (formData) {
data = formData
// data.content_type = 'multipart/form-data'
options.json = false
}
callback(err, {
statusCode: resp.statusCode,
statusMessage: resp.statusMessage,
headers: resp.headers,
bytes: resp.bytes,
raw: resp.raw,
body,
}, body)
}).request
options.response_timeout = timeout
return () => {
if (!request.aborted) request.abort()
request = null
}
},
send(eventName, data) {
return new Promise((resolve, reject) => {
if (!eventNames.includes(eventName)) return reject(new Error('The event is not supported: ' + eventName))
let request = needle.request(method, url, data, options, (err, resp, body) => {
// console.log(err, resp, body)
if (err) {
callback(err, null, null)
} else {
body = resp.body = resp.raw.toString()
try {
resp.body = JSON.parse(resp.body)
} catch (_) {}
body = resp.body
callback(err, {
statusCode: resp.statusCode,
statusMessage: resp.statusMessage,
headers: resp.headers,
bytes: resp.bytes,
raw: resp.raw,
body,
}, body)
}
}).request
return () => {
if (!request.aborted) request.abort()
request = null
}
},
send(eventName, data) {
return new Promise((resolve, reject) => {
if (!eventNames.includes(eventName)) return reject(new Error('The event is not supported: ' + eventName))
switch (eventName) {
case EVENT_NAMES.inited:
if (isInitedApi) return reject(new Error('Script is inited'))
isInitedApi = true
handleInit(this, data)
resolve()
break
case EVENT_NAMES.updateAlert:
if (isShowedUpdateAlert) return reject(new Error('The update alert can only be called once.'))
isShowedUpdateAlert = true
handleShowUpdateAlert(data, resolve, reject)
break
default:
reject(new Error('Unknown event name: ' + eventName))
}
})
},
on(eventName, handler) {
if (!eventNames.includes(eventName)) return Promise.reject(new Error('The event is not supported: ' + eventName))
switch (eventName) {
case EVENT_NAMES.inited:
if (isInitedApi) return reject(new Error('Script is inited'))
isInitedApi = true
handleInit(this, data)
resolve()
case EVENT_NAMES.request:
events.request = handler
break
case EVENT_NAMES.updateAlert:
if (isShowedUpdateAlert) return reject(new Error('The update alert can only be called once.'))
isShowedUpdateAlert = true
handleShowUpdateAlert(data, resolve, reject)
break
default:
reject(new Error('Unknown event name: ' + eventName))
default: return Promise.reject(new Error('The event is not supported: ' + eventName))
}
})
},
on(eventName, handler) {
if (!eventNames.includes(eventName)) return Promise.reject(new Error('The event is not supported: ' + eventName))
switch (eventName) {
case EVENT_NAMES.request:
events.request = handler
break
default: return Promise.reject(new Error('The event is not supported: ' + eventName))
}
return Promise.resolve()
},
utils: {
crypto: {
aesEncrypt(buffer, mode, key, iv) {
const cipher = createCipheriv(mode, key, iv)
return Buffer.concat([cipher.update(buffer), cipher.final()])
},
rsaEncrypt(buffer, key) {
buffer = Buffer.concat([Buffer.alloc(128 - buffer.length), buffer])
return publicEncrypt({ key, padding: constants.RSA_NO_PADDING }, buffer)
},
randomBytes(size) {
return randomBytes(size)
},
md5(str) {
return createHash('md5').update(str).digest('hex')
},
return Promise.resolve()
},
buffer: {
from(...args) {
return Buffer.from(...args)
utils: {
crypto: {
aesEncrypt(buffer, mode, key, iv) {
const cipher = createCipheriv(mode, key, iv)
return Buffer.concat([cipher.update(buffer), cipher.final()])
},
rsaEncrypt(buffer, key) {
buffer = Buffer.concat([Buffer.alloc(128 - buffer.length), buffer])
return publicEncrypt({ key, padding: constants.RSA_NO_PADDING }, buffer)
},
randomBytes(size) {
return randomBytes(size)
},
md5(str) {
return createHash('md5').update(str).digest('hex')
},
},
bufToString(buf, format) {
return Buffer.from(buf, 'binary').toString(format)
buffer: {
from(...args) {
return Buffer.from(...args)
},
bufToString(buf, format) {
return Buffer.from(buf, 'binary').toString(format)
},
},
},
zlib: {
inflate(buf) {
return new Promise((resolve, reject) => {
zlib.inflate(buf, (err, data) => {
if (err) reject(new Error(err.message))
else resolve(data)
zlib: {
inflate(buf) {
return new Promise((resolve, reject) => {
zlib.inflate(buf, (err, data) => {
if (err) reject(new Error(err.message))
else resolve(data)
})
})
})
},
deflate(data) {
return new Promise((resolve, reject) => {
zlib.deflate(data, (err, buf) => {
if (err) reject(new Error(err.message))
else resolve(buf)
},
deflate(data) {
return new Promise((resolve, reject) => {
zlib.deflate(data, (err, buf) => {
if (err) reject(new Error(err.message))
else resolve(buf)
})
})
})
},
},
},
},
version: '1.3.0',
// removeEvent(eventName, handler) {
// if (!eventNames.includes(eventName)) return Promise.reject(new Error('The event is not supported: ' + eventName))
// let handlers
// switch (eventName) {
// case EVENT_NAMES.request:
// handlers = events.request
// break
// }
// for (let index = 0; index < handlers.length; index++) {
// if (handlers[index] === handler) {
// handlers.splice(index, 1)
// break
// }
// }
// },
// removeAllEvents() {
// for (const handlers of Object.values(events)) {
// handlers.splice(0, handlers.length)
// }
// },
currentScriptInfo: {
name: userApi.name,
description: userApi.description,
version: userApi.version,
author: userApi.author,
homepage: userApi.homepage,
rawScript: userApi.script,
},
version: '2.0.0',
env: 'desktop',
// removeEvent(eventName, handler) {
// if (!eventNames.includes(eventName)) return Promise.reject(new Error('The event is not supported: ' + eventName))
// let handlers
// switch (eventName) {
// case EVENT_NAMES.request:
// handlers = events.request
// break
// }
// for (let index = 0; index < handlers.length; index++) {
// if (handlers[index] === handler) {
// handlers.splice(index, 1)
// break
// }
// }
// },
// removeAllEvents() {
// for (const handlers of Object.values(events)) {
// handlers.splice(0, handlers.length)
// }
// },
})
contextBridge.exposeInMainWorld('__lx_init_error_handler__', {
sendError(errorMessage) {
if (isInitedApi) return
isInitedApi = true
if (errorMessage.length > 1024) errorMessage = errorMessage.substring(0, 1024) + '...'
sendMessage(USER_API_RENDERER_EVENT_NAME.init, null, false, errorMessage)
},
})
webFrame.executeJavaScript(`(() => {
window.addEventListener('error', (event) => {
if (event.isTrusted) globalThis.__lx_init_error_handler__.sendError(event.message.replace(/^Uncaught\\sError:\\s/, ''))
})
window.addEventListener('unhandledrejection', (event) => {
if (!event.isTrusted) return
const message = typeof event.reason === 'string' ? event.reason : event.reason?.message ?? String(event.reason)
globalThis.__lx_init_error_handler__.sendError(message.replace(/^Error:\\s/, ''))
})
})()`)
webFrame.executeJavaScript(userApi.script).catch(_ => _)
}
ipcRenderer.on(USER_API_RENDERER_EVENT_NAME.initEnv, (event, data) => {
initEnv(data)
})
@@ -1,4 +1,5 @@
const names = {
initEnv: '',
init: '',
request: '',
response: '',
+35 -9
View File
@@ -18,18 +18,44 @@ export const getUserApis = (): LX.UserApi.UserApiInfo[] => {
return userApis
}
const INFO_NAMES = {
name: 24,
description: 36,
author: 56,
homepage: 1024,
version: 36,
} as const
type INFO_NAMES_Type = typeof INFO_NAMES
const matchInfo = (scriptInfo: string) => {
const infoArr = scriptInfo.split(/\r?\n/)
const rxp = /^\s?\*\s?@(\w+)\s(.+)$/
const infos: Partial<Record<keyof typeof INFO_NAMES, string>> = {}
for (const info of infoArr) {
const result = rxp.exec(info)
if (!result) continue
const key = result[1] as keyof typeof INFO_NAMES
if (INFO_NAMES[key] == null) continue
infos[key] = result[2].trim()
}
for (const [key, len] of Object.entries(INFO_NAMES) as Array<{ [K in keyof INFO_NAMES_Type]: [K, INFO_NAMES_Type[K]] }[keyof INFO_NAMES_Type]>) {
infos[key] ||= ''
if (infos[key] == null) infos[key] = ''
else if (infos[key]!.length > len) infos[key] = infos[key]!.substring(0, len) + '...'
}
return infos as Record<keyof typeof INFO_NAMES, string>
}
export const importApi = (script: string): LX.UserApi.UserApiInfo => {
let scriptInfo = script.split(/\r?\n/)
let name = scriptInfo[1] || ''
let description = scriptInfo[2] || ''
name = name.startsWith(' * @name ') ? name.replace(' * @name ', '').trim() : `user_api_${new Date().toLocaleString()}`
if (name.length > 24) name = name.substring(0, 24) + '...'
description = description.startsWith(' * @description ') ? description.replace(' * @description ', '').trim() : ''
if (description.length > 36) description = description.substring(0, 36) + '...'
const result = /^\/\*[\S|\s]+?\*\//.exec(script)
if (!result) throw new Error('无效的自定义源文件')
let scriptInfo = matchInfo(result[0])
scriptInfo.name ||= `user_api_${new Date().toLocaleString()}`
const apiInfo = {
id: `user_api_${Math.random().toString().substring(2, 5)}_${Date.now()}`,
name,
description,
...scriptInfo,
script,
allowShowUpdateAlert: true,
}