diff --git a/packages/client/client/context.ts b/packages/client/client/context.ts index 766e7c1..6270c16 100644 --- a/packages/client/client/context.ts +++ b/packages/client/client/context.ts @@ -1,4 +1,5 @@ import * as cordis from 'cordis' +import type { ClientEvents } from '@cordisjs/plugin-webui' import { App, Component, createApp, customRef, defineComponent, DefineComponent, h, inject, InjectionKey, markRaw, onErrorCaptured, onScopeDispose, provide, Ref, ref, resolveComponent, @@ -12,7 +13,7 @@ import ThemeService from './plugins/theme' // layout api -export interface Events extends cordis.Events {} +export interface Events extends cordis.Events, ClientEvents {} export interface Context { [Context.events]: Events diff --git a/packages/client/client/data.ts b/packages/client/client/data.ts index 132d86e..ce05d88 100644 --- a/packages/client/client/data.ts +++ b/packages/client/client/data.ts @@ -8,6 +8,7 @@ declare const CLIENT_CONFIG: ClientConfig export const global = CLIENT_CONFIG export const socket = ref() +export const clientId = ref() export function send(type: T, ...args: Parameters): Promisify> export async function send(type: string, ...args: any[]) { @@ -24,6 +25,7 @@ export async function send(type: string, ...args: any[]) { body: JSON.stringify(args[0]), headers: new Headers({ 'Content-Type': 'application/json', + 'X-Client-ID': clientId.value ?? '', }), }) if (!response.ok) { diff --git a/packages/client/client/plugins/loader.ts b/packages/client/client/plugins/loader.ts index 6da7ae8..f2ab764 100644 --- a/packages/client/client/plugins/loader.ts +++ b/packages/client/client/plugins/loader.ts @@ -3,7 +3,7 @@ import { Context } from '../context' import { Service } from '../utils' import { ForkScope } from 'cordis' import { defineProperty, Dict } from 'cosmokit' -import { Entry } from '@cordisjs/plugin-webui' +import { clientId } from '../data' declare module '../context' { interface Context { @@ -71,7 +71,7 @@ export default class LoaderService extends Service { constructor(ctx: Context) { super(ctx, '$loader', true) - ctx.on('entry:refresh', ({ id, data }) => { + ctx.on('entry:update', ({ id, data }) => { const entry = this.entries[id] if (!entry) return entry.data.value = data @@ -96,13 +96,14 @@ export default class LoaderService extends Service { initTask = new Promise((resolve) => { this.ctx.on('entry:init', async (value) => { - const { _id, ...rest } = value as Dict & { _id?: string } - if (this.id && _id && this.id !== _id as unknown) { + const { serverId, entries } = value + clientId.value = value.clientId + if (this.id && serverId && this.id !== serverId as unknown) { return window.location.reload() } - this.id = _id + this.id = serverId - await Promise.all(Object.entries(rest).map(([key, body]) => { + await Promise.all(Object.entries(entries).map(([key, body]) => { if (this.entries[key]) { if (body) return console.warn(`Entry ${key} already exists`) for (const fork of this.entries[key].forks) { @@ -135,7 +136,7 @@ export default class LoaderService extends Service { task.then(() => this.entries[key].done.value = true) })) - if (_id) resolve() + if (serverId) resolve() }) }) } diff --git a/plugins/webui/src/index.ts b/plugins/webui/src/index.ts index 5aec07d..9d2fa12 100644 --- a/plugins/webui/src/index.ts +++ b/plugins/webui/src/index.ts @@ -97,9 +97,9 @@ class NodeWebUI extends WebUI { addListener(event: K, callback: Events[K]) { this.ctx.server.post(`${this.config.apiPath}/${event}`, async (koa) => { - const { body } = koa.request + const { body, headers } = koa.request try { - koa.body = JSON.stringify(await (callback as any)(body) ?? {}) + koa.body = JSON.stringify(await (callback as any).call(headers, body) ?? {}) koa.type = 'application/json' koa.status = 200 } catch (error) { diff --git a/plugins/webui/src/shared/client.ts b/plugins/webui/src/shared/client.ts index a0b90d3..5085b3a 100644 --- a/plugins/webui/src/shared/client.ts +++ b/plugins/webui/src/shared/client.ts @@ -1,16 +1,28 @@ import { Context, Logger } from 'cordis' import { IncomingMessage } from 'node:http' import { WebSocket } from './types.ts' +import { mapValues } from 'cosmokit' +import { Entry } from './entry.ts' const logger = new Logger('webui') +export interface ClientEvents { + 'entry:init'(data: Entry.Init): void + 'entry:update'(data: Entry.Update): void + 'entry:patch'(data: Entry.Patch): void +} + export class Client { readonly id = Math.random().toString(36).slice(2) constructor(readonly ctx: Context, public socket: WebSocket, public request?: IncomingMessage) { socket.addEventListener('message', this.receive) const webui = this.ctx.get('webui')! - const body = { ...webui.entries, _id: webui.id } + const body: Entry.Init = { + entries: mapValues(webui.entries, entry => entry.toJSON(this)!), + serverId: webui.id, + clientId: this.id, + } this.send({ type: 'entry:init', body }) } diff --git a/plugins/webui/src/shared/entry.ts b/plugins/webui/src/shared/entry.ts index b5d6fa9..d36dbad 100644 --- a/plugins/webui/src/shared/entry.ts +++ b/plugins/webui/src/shared/entry.ts @@ -1,5 +1,6 @@ import { Context } from 'cordis' import { Client } from './index.ts' +import { Dict } from 'cosmokit' export namespace Entry { export interface Files { @@ -13,29 +14,52 @@ export namespace Entry { paths?: string[] data?: any } + + export interface Init { + entries: Dict + serverId: string + clientId: string + } + + export interface Update extends Data { + id: string + } + + export interface Patch extends Data { + id: string + key?: string + } } export class Entry { public id = Math.random().toString(36).slice(2) public dispose: () => void - constructor(public ctx: Context, public files: Entry.Files, public data?: () => T) { + constructor(public ctx: Context, public files: Entry.Files, public data?: (client: Client) => T) { ctx.webui.entries[this.id] = this - ctx.webui.broadcast('entry:init', { - [this.id]: this, - }) + ctx.webui.broadcast('entry:init', (client: Client) => ({ + serverId: ctx.webui.id, + clientId: client.id, + entries: { + [this.id]: this.toJSON(client), + }, + })) this.dispose = ctx.collect('entry', () => { delete this.ctx.webui.entries[this.id] - ctx.webui.broadcast('entry:init', { - [this.id]: null, - }) + ctx.webui.broadcast('entry:init', (client: Client) => ({ + serverId: ctx.webui.id, + clientId: client.id, + entries: { + [this.id]: null, + }, + })) }) } refresh() { - this.ctx.webui.broadcast('entry:refresh', (client: Client) => ({ + this.ctx.webui.broadcast('entry:update', (client: Client) => ({ id: this.id, - data: this.data?.(), + data: this.data?.(client), })) } @@ -47,12 +71,12 @@ export class Entry { }) } - toJSON(): Entry.Data | undefined { + toJSON(client: Client): Entry.Data | undefined { try { return { files: this.ctx.webui.resolveEntry(this.files, this.id), paths: this.ctx.get('loader')?.locate(), - data: JSON.parse(JSON.stringify(this.data?.())), + data: JSON.parse(JSON.stringify(this.data?.(client))), } } catch (e) { this.ctx.logger.error(e) diff --git a/plugins/webui/src/shared/index.ts b/plugins/webui/src/shared/index.ts index 2266e42..c7b7e64 100644 --- a/plugins/webui/src/shared/index.ts +++ b/plugins/webui/src/shared/index.ts @@ -52,7 +52,7 @@ export abstract class WebUI extends Service { abstract resolveEntry(files: Entry.Files, key: string): string[] abstract addListener(event: K, callback: Events[K]): void - addEntry(files: Entry.Files, data?: () => T) { + addEntry(files: Entry.Files, data?: (client: Client) => T) { return new Entry(this.ctx, files, data) }