Skip to content

订阅事件 ​

api.events.on(...) 让你对启动器里发生的事情做出反应——实例被创建/删除、安装进度变化、日志产生等等。

ts
const unsubscribe = api.events.on('instance', (payload) => {
	// 实例列表变了
})

需要 event:<类型> 权限(低风险,自动授予)。

全部事件 ​

事件什么时候触发payload
instance实例被创建、删除、修改实例信息
instance_groups_changed实例分组变化分组信息
instance_bulk_update_progress批量更新实例的进度进度信息
process启动器进程状态变化(游戏启动/退出等)进程状态
install_job安装任务的进度任务进度
library_changed多版本库变化(切换、新建库)库信息
log启动器产生一条日志日志内容

事件是「通知」,不是「完整数据」

payload 的形状没有强类型保证(SDK 里是 unknown)。它是给你「知道发生了什么事」用的,具体数据一般还是要通过 api.hostApi.call('instance.list') 之类的接口去取。

官方 hello-world 的做法就是这样——收到 instance 事件后,重新调一次 instance.list 拿最新数据。

用法 ​

ts
export async function activate(api: PluginHostApi) {
	const { ref } = api.vue

	const lastEvent = ref('(暂无)')

	// 返回一个取消订阅的函数
	const unsubscribe = api.events.on('instance', () => {
		lastEvent.value = new Date().toLocaleTimeString()
	})

	// 通常不需要手动调用 unsubscribe —— 插件卸载时启动器会自动清理。
	// 但如果你想在插件运行期间停止接收,可以调用它。
}

返回值 ​

api.events.on(...) 返回一个取消订阅函数:

ts
const unsubscribe = api.events.on('instance', handler)
// ...
unsubscribe() // 停止接收

一般不用手动取消

插件卸载时,启动器会自动清理所有订阅。所以除非你想在插件运行期间动态停止接收某个事件,否则不用管返回值。

权限 ​

event:<类型> 是低风险权限,声明了就自动授予:

json
"permissions": ["event:instance", "event:process"]

没声明的后果是抛错

ts
api.events.on('instance', handler)
// → Error: Plugin "com.example.x" cannot listen to "instance": the "event:instance" permission has not been granted.

如果这行在 activate() 顶层没包 try,整个插件启动失败。所以要么确保声明了,要么包 try。

事件类型是白名单 ​

插件只能订阅白名单上的事件——启动器内部的一些信号不给插件听:

ts
api.events.on('some_internal_event', handler)
// → Error: Event "some_internal_event" is not available to plugins.

只有上表里那七个。

一个插件的 handler 抛错不会影响别人 ​

启动器把每个 handler 包了一层——你的 handler 抛错只会记录日志,不会中断其他插件的 handler,也不会让启动器崩。

但这意味着你自己抛的错不会被自动上报。如果你需要知道 handler 里的失败,自己 try/catch + api.log:

ts
api.events.on('install_job', async () => {
	try {
		await refresh()
	} catch (error) {
		api.log('刷新失败', error)
	}
})

完整例子 ​

ts
import type { PluginHostApi } from '@celestial/plugin'

export async function activate(api: PluginHostApi): Promise<void> {
	const { h, ref } = api.vue

	const instanceCount = ref<number | null>(null)
	const lastEvent = ref('(暂无)')

	async function refreshInstanceCount() {
		try {
			const instances = await api.hostApi.call('instance.list')
			instanceCount.value = Array.isArray(instances) ? instances.length : 0
		} catch {
			// hostapi:instance.list 是高风险权限,用户可能没批准
			instanceCount.value = null
		}
	}

	await refreshInstanceCount()

	// event:instance 是低风险,装上就能用
	api.events.on('instance', () => {
		lastEvent.value = new Date().toLocaleTimeString()
		void refreshInstanceCount()
	})

	api.slots.add('sidebar.top', {
		id: 'card',
		component: {
			name: 'InstanceCard',
			setup() {
				return () =>
					h('div', { class: 'p-3 text-sm' }, [
						h('span', null, instanceCount.value === null ? '实例:未授权' : `实例:${instanceCount.value}`),
						h('span', { class: 'block text-xs opacity-80' }, `最近事件 ${lastEvent.value}`),
					])
			},
		},
	})
}

这个例子里:

  • event:instance(低风险)→ 订阅成功,实例变化时卡片更新。
  • hostapi:instance.list(高风险)→ 用户没批准时降级为「未授权」,插件不崩。

常见错误 ​

现象原因
cannot listen to "instance": the "event:instance" permission has not been grantedmanifest 里没声明 event:instance
Event "xxx" is not available to plugins事件名不在白名单里
收到事件但数据不对payload 是 unknown,要自己判断形状,或者重新调宿主接口取
事件触发太频繁有些事件(如 log)会很密集,handler 要轻

接下来:调用宿主接口。

Celestial Launcher 基于 Modrinth App 构建,遵循其开源许可。