Skip to content

Commit

Permalink
feat(eval): enhance event hooks, fix #194
Browse files Browse the repository at this point in the history
  • Loading branch information
shigma committed Apr 3, 2021
1 parent 7237377 commit cf4be06
Show file tree
Hide file tree
Showing 7 changed files with 43 additions and 48 deletions.
38 changes: 21 additions & 17 deletions packages/plugin-eval/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,19 @@ declare module 'koishi-core' {

interface Session {
_isEval: boolean
_sendCount: number
}

namespace Plugin {
interface Packages {
'koishi-plugin-eval': typeof import('.')
}
}

interface EventMap {
'eval/before-send'(content: string, session: Session): string | Promise<string>
'eval/before-start'(): void | Promise<void>
'eval/start'(response: WorkerResponse): void
}
}

interface OptionManifest extends Domain.OptionConfig {
Expand All @@ -54,7 +59,6 @@ const defaultConfig: EvalConfig = {
authority: 2,
timeout: 1000,
setupFiles: {},
maxLogs: Infinity,
channelFields: ['id'],
userFields: ['id', 'authority'],
dataKeys: ['inspect', 'setupFiles'],
Expand All @@ -74,7 +78,7 @@ export function apply(ctx: Context, config: Config = {}) {
}

ctx.on('connect', () => {
app.worker = new EvalWorker(app, config)
app.worker = new EvalWorker(ctx.app, config)
app.worker.start()
})

Expand All @@ -92,23 +96,24 @@ export function apply(ctx: Context, config: Config = {}) {
const channelAccess = Trap.resolve(config.channelFields)

// worker should be running for all the features
ctx = ctx.intersect(sess => sess.app.worker?.state === EvalWorker.State.open)
ctx = ctx.intersect(() => app.worker?.state === EvalWorker.State.open)

const command = ctx.command('evaluate [expr:text]', '执行 JavaScript 脚本', { noEval: true })
.alias('eval')
.userFields(['authority'])
.option('slient', '-s 不输出最后的结果')
.option('restart', '-r 重启子线程', { authority: 3 })
.action(({ session }) => {
.check(({ session }) => {
if (!session['_redirected'] && session.user?.authority < authority) return '权限不足。'
})
.action(async ({ options }) => {
if (options.restart) {
await app.worker.restart()
return '子线程已重启。'
}
})

Trap.action(command, userAccess, channelAccess, async ({ session, options, scope }, expr) => {
if (options.restart) {
await app.worker.restart()
return '子线程已重启。'
}

if (!expr) return '请输入要执行的脚本。'
expr = segment.unescape(expr)

Expand All @@ -133,7 +138,7 @@ export function apply(ctx: Context, config: Config = {}) {

const timer = setTimeout(async () => {
await app.worker.restart()
_resolve(!session._sendCount && '执行超时。')
_resolve('执行超时。')
}, config.timeout)

const dispose = app.worker.onError((error: Error) => {
Expand Down Expand Up @@ -216,7 +221,7 @@ function addon(ctx: Context, config: EvalConfig) {
return load(content) as Manifest
}

ctx.on('worker/start', async () => {
ctx.before('eval/start', async () => {
const dirents = await fs.readdir(root, { withFileTypes: true })
const paths = config.addonNames = dirents
.filter(dir => dir.isDirectory() && !exclude.test(dir.name))
Expand Down Expand Up @@ -245,10 +250,9 @@ function addon(ctx: Context, config: EvalConfig) {
.subcommand(rawName, desc, config)
.option('debug', '启用调试模式', { hidden: true })

Trap.action(cmd, userAccess, channelAccess, async ({ session, command, options, scope }, ...args) => {
const { name } = command, { remote } = session.app.worker
const result = await remote.callAddon(scope, { name, args, options })
return result
Trap.action(cmd, userAccess, channelAccess, async ({ command, options, scope }, ...args) => {
const { name } = command
return ctx.app.worker.remote.callAddon(scope, { name, args, options })
})

options.forEach((config) => {
Expand All @@ -258,7 +262,7 @@ function addon(ctx: Context, config: EvalConfig) {
})
}

ctx.on('worker/ready', (data) => {
ctx.on('eval/start', (data) => {
config.addonNames.map((path) => {
registerAddon(data, path)
})
Expand Down
2 changes: 1 addition & 1 deletion packages/plugin-eval/src/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ export default async function prepare() {
return fs.writeFile(tsconfigPath, json5.stringify({ compilerOptions }, null, 2))
}),
readSerialized(cachePath).then((data) => {
if (data.tag === CACHE_TAG && data.v8tag === V8_TAG) {
if (data && data.tag === CACHE_TAG && data.v8tag === V8_TAG) {
Object.assign(cachedFiles, data.files)
}
}),
Expand Down
26 changes: 7 additions & 19 deletions packages/plugin-eval/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,16 @@
import { App, Command, Channel, Argv as IArgv, User } from 'koishi-core'
import { Logger, Observed, pick, union } from 'koishi-utils'
import { Worker, ResourceLimits } from 'worker_threads'
import { WorkerAPI, WorkerConfig, WorkerData, WorkerResponse, ScopeData } from './worker'
import { WorkerAPI, WorkerConfig, WorkerData, ScopeData } from './worker'
import { expose, Remote, wrap } from './transfer'
import { resolve } from 'path'

declare module 'koishi-core' {
interface EventMap {
'worker/start'(): void | Promise<void>
'worker/ready'(response: WorkerResponse): void
'worker/exit'(): void
}
}

const logger = new Logger('eval')

export interface MainConfig extends Trap.Config {
prefix?: string
authority?: number
timeout?: number
maxLogs?: number
resourceLimits?: ResourceLimits
dataKeys?: (keyof WorkerData)[]
gitRemote?: string
Expand Down Expand Up @@ -126,7 +117,7 @@ export namespace Trap {
} finally {
if (inactive) delete app._sessions[id]
}
})
}, true)
}
}

Expand All @@ -149,12 +140,10 @@ export class MainAPI {
return result
}

async send(uuid: string, message: string) {
async send(uuid: string, content: string) {
const session = this.getSession(uuid)
if (!session._sendCount) session._sendCount = 0
if (this.app.worker.config.maxLogs > session._sendCount++) {
return await session.sendQueued(message)
}
content = await this.app.waterfall('eval/before-send', content, session)
if (content) return await session.sendQueued(content)
}

async updateUser(uuid: string, data: Partial<User>) {
Expand Down Expand Up @@ -191,7 +180,7 @@ export class EvalWorker {

async start() {
this.state = State.opening
await this.app.parallel('worker/start')
await this.app.parallel('eval/before-start')
process.on('beforeExit', this.beforeExit)

let index = 0
Expand All @@ -217,13 +206,12 @@ export class EvalWorker {
this.remote = wrap(this.worker)

await this.remote.start().then((response) => {
this.app.emit('worker/ready', response)
this.app.emit('eval/start', response)
logger.debug('worker started')
this.state = State.open

this.worker.on('exit', (code) => {
this.state = State.close
this.app.emit('worker/exit')
logger.debug('exited with code', code)
if (!this.prevent) this.promise = this.start()
})
Expand Down
7 changes: 4 additions & 3 deletions packages/plugin-eval/src/transfer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ interface Message {
value?: any
}

const logger = new Logger('eval:transfer')
const logger = new Logger('transfer')

export function request(ep: Endpoint, payload: Partial<Message>) {
const uuid = Random.uuid()
Expand Down Expand Up @@ -51,8 +51,9 @@ export function wrap<T>(ep: Endpoint) {

export function expose(ep: Endpoint, object: {}) {
ep.on('message', async (data: string) => {
const { type, key, uuid, args } = JSON.parse(data)
logger.debug('[receive] %o', { type, key, uuid, args })
const payload = JSON.parse(data)
logger.debug('[receive] %o', payload)
const { type, key, uuid, args } = payload
if (type !== 'apply') return
const value = await object[key](...args)
ep.postMessage(JSON.stringify({ uuid, value }))
Expand Down
6 changes: 4 additions & 2 deletions packages/plugin-eval/src/worker.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Channel, User, Logger, escapeRegExp, observe, difference, Time, segment, Random, Observed } from 'koishi-core'
import { Channel, User, Logger, escapeRegExp, observe, difference, Time, segment, Random } from 'koishi-core'
import { parentPort, workerData } from 'worker_threads'
import { InspectOptions, formatWithOptions } from 'util'
import { findSourceMap } from 'module'
Expand Down Expand Up @@ -113,7 +113,9 @@ export const Scope = ({ id, user, userWritable, channel, channelWritable }: Scop
}),

async send(...param: [string, ...any[]]) {
return await main.send(id, formatResult(...param))
const content = formatResult(...param)
if (!content) return
return await main.send(id, content)
},

async exec(message: string) {
Expand Down
10 changes: 5 additions & 5 deletions packages/plugin-eval/tests/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import * as eval from 'koishi-plugin-eval'
const app = new App()

app.plugin(eval, {
addonRoot: resolve(__dirname, 'fixtures'),
root: resolve(__dirname, 'fixtures'),
setupFiles: {
'test-worker': resolve(__dirname, 'worker.ts'),
},
Expand All @@ -19,7 +19,7 @@ const ses = app.session('123')
before(async () => {
await fs.rmdir(resolve(__dirname, 'fixtures/.koishi'), { recursive: true })
return new Promise<void>((resolve) => {
app.on('worker/ready', () => resolve())
app.on('eval/start', () => resolve())
app.start()
})
})
Expand All @@ -29,9 +29,9 @@ after(() => app.stop())
describe('Eval Plugin', () => {
it('basic support', async () => {
await ses.shouldReply('> 1 + 1', '2')
await ses.shouldNotReply('>> 1 + 1')
await ses.shouldReply('> send(1 + 1)', '2')
await ses.shouldReply('>> send(1 + 1)', '2')
await ses.shouldNotReply('>> 1 + 2')
await ses.shouldReply('> send(1 + 3)', '4')
await ses.shouldReply('>> send(1 + 4)', '5')
})

it('validation', async () => {
Expand Down
2 changes: 1 addition & 1 deletion tsconfig.test.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"baseUrl": ".",
"paths": {
"koishi-plugin-*": ["packages/plugin-*/src"],
"koishi-plugin-eval/dist/worker": ["packages/plugin-eval/src/worker"],
"koishi-plugin-eval/lib/worker": ["packages/plugin-eval/src/worker"],
"koishi-adapter-*": ["packages/adapter-*/src"],
"koishi-*": ["packages/koishi-*/src"],
},
Expand Down

0 comments on commit cf4be06

Please sign in to comment.