Skip to content

Commit

Permalink
feat(webui): fix edge cases and typings
Browse files Browse the repository at this point in the history
  • Loading branch information
shigma committed May 15, 2024
1 parent 298df91 commit 06b153d
Show file tree
Hide file tree
Showing 7 changed files with 45 additions and 42 deletions.
2 changes: 1 addition & 1 deletion packages/client/client/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import ThemeService from './plugins/theme'

// layout api

export interface Events<C extends Context> extends cordis.Events<C> {}
export interface Events<C extends Context = Context> extends cordis.Events<C> {}

export interface Context {
[Context.events]: Events<this>
Expand Down
22 changes: 12 additions & 10 deletions packages/client/client/data.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,31 @@
import type { ClientConfig, DataService, Events, WebSocket, WebUI } from '@cordisjs/plugin-webui'
import type { ClientConfig, Events, WebSocket } from '@cordisjs/plugin-webui'
import type { Promisify } from 'cosmokit'
import { markRaw, ref } from 'vue'
import { Context } from './context'

export type Store = {
[K in keyof WebUI.Services]?: WebUI.Services[K] extends DataService<infer T> ? T : never
}
import { root } from '.'

declare const KOISHI_CONFIG: ClientConfig
export const global = KOISHI_CONFIG

export function withProxy(url: string) {
return (global.proxyBase || '') + url
}

export const socket = ref<WebSocket>()
const listeners: Record<string, (data: any) => void> = {}

export function send<T extends keyof Events>(type: T, ...args: Parameters<Events[T]>): Promisify<ReturnType<Events[T]>>
export async function send(type: string, ...args: any[]) {
if (global.static) {
console.debug('[request]', type, ...args)
const result = root.webui.listeners[type]?.(...args)
console.debug('[response]', result)
return result
}
if (!socket.value) return
console.debug('[request]', type, args)
console.debug('[request]', type, ...args)
const response = await fetch(`${global.endpoint}/${type}`, {
method: 'POST',
body: JSON.stringify(args[0]),
headers: new Headers({
'Content-Type': 'application/json',
}),
})
const result = await response.json()
console.debug('[response]', result)
Expand Down
8 changes: 6 additions & 2 deletions packages/client/client/plugins/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,10 +118,14 @@ export default class LoaderService extends Service {
forks: [],
}

const task = Promise.allSettled(files.map(async (url, index) => {
const task = Promise.all(files.map(async (url, index) => {
for (const ext in loaders) {
if (!url.endsWith(ext)) continue
ctx.$entry.forks[index] = await loaders[ext](ctx, url)
try {
ctx.$entry.forks[index] = await loaders[ext](ctx, url)
} catch (e) {
console.error(e)
}
}
}))
task.then(() => this.entries[key].done.value = true)
Expand Down
11 changes: 3 additions & 8 deletions packages/client/client/plugins/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,7 @@ declare module '../context' {
page(options: Activity.Options): () => void
}

// https://github.com/typescript-eslint/typescript-eslint/issues/6720
// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface Events<C> {
interface Events {
'activity'(activity: Activity): boolean
}
}
Expand All @@ -40,8 +38,6 @@ export namespace Activity {
order?: number
authority?: number
position?: 'top' | 'bottom'
/** @deprecated */
when?: () => boolean
disabled?: () => boolean | undefined
}
}
Expand Down Expand Up @@ -126,7 +122,7 @@ export default class RouterService extends Service {
const initialTitle = document.title
ctx.effect(() => this.router.afterEach((route) => {
const { name, fullPath } = this.router.currentRoute.value
this.cache[name] = fullPath
this.cache[name!] = fullPath
if (route.meta.activity) {
document.title = `${route.meta.activity.name}`
if (initialTitle) document.title += ` | ${initialTitle}`
Expand All @@ -136,7 +132,7 @@ export default class RouterService extends Service {
this.router.beforeEach(async (to, from) => {
if (to.matched.length) {
if (to.matched[0].path !== '/') {
redirectTo.value = null
redirectTo.value = undefined
}
return
}
Expand All @@ -163,7 +159,6 @@ export default class RouterService extends Service {
const caller = this[Context.current]
options.order ??= 0
options.component = caller.wrapComponent(options.component)
if (options.when) options.disabled = () => !options.when()
return caller.effect(() => {
const list = this.views[options.type] ||= []
insert(list, options)
Expand Down
6 changes: 5 additions & 1 deletion plugins/webui/src/browser.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Schema } from 'cordis'
import { makeArray } from 'cosmokit'
import { Entry, WebUI } from './shared/index.ts'
import { Entry, Events, WebUI } from './shared/index.ts'
import {} from '@cordisjs/loader'

export * from './shared/index.ts'
Expand All @@ -10,6 +10,10 @@ class BrowserWebUI extends WebUI {
this.accept(this.ctx.loader[Symbol.for('cordis.webui.socket')])
}

addListener<K extends keyof Events>(event: K, callback: Events[K]): void {
// TODO
}

resolveEntry(files: Entry.Files) {
if (typeof files === 'string' || Array.isArray(files)) return makeArray(files)
return makeArray(files.prod)
Expand Down
22 changes: 17 additions & 5 deletions plugins/webui/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { extname, resolve } from 'node:path'
import { createReadStream, existsSync, Stats } from 'node:fs'
import { readFile, stat } from 'node:fs/promises'
import { fileURLToPath, pathToFileURL } from 'node:url'
import { Entry, WebUI } from './shared'
import { Entry, Events, WebUI } from './shared'
import open from 'open'

declare module 'cordis' {
Expand Down Expand Up @@ -52,8 +52,7 @@ class NodeWebUI extends WebUI<NodeWebUI.Config> {
super(ctx, config)

this.layer = ctx.server.ws(config.apiPath, (socket, request) => {
// @types/ws does not provide typings for `dispatchEvent`
this.accept(socket as any, request)
this.accept(socket, request)
})

ctx.on('webui/connection', () => {
Expand Down Expand Up @@ -92,6 +91,19 @@ class NodeWebUI extends WebUI<NodeWebUI.Config> {
})
}

addListener<K extends keyof Events>(event: K, callback: Events[K]) {
this.ctx.server.post(`${this.config.apiPath}/${event}`, async (koa) => {
const { body } = koa.request
try {
koa.body = await (callback as any)(body)
koa.status = 200
} catch (error) {
this.ctx.logger.warn(error)
koa.status = 500
}
})
}

private getFiles(files: Entry.Files) {
if (typeof files === 'string' || Array.isArray(files)) return files
if (!this.config.devMode) return files.prod
Expand Down Expand Up @@ -210,7 +222,7 @@ class NodeWebUI extends WebUI<NodeWebUI.Config> {
' import.meta.hot.accept(async (module) => {',
' const { root } = await import("@cordisjs/client");',
` const fork = root.$loader.entries["${key}"]?.forks[${index}];`,
' fork?.update(module, true);',
' return fork?.update(module, true);',
' });',
'}',
].join('\n') + '\n',
Expand Down Expand Up @@ -310,7 +322,7 @@ namespace NodeWebUI {
export const Config: Schema<Config> = Schema.intersect([
Schema.object({
uiPath: Schema.string().default(''),
apiPath: Schema.string().default('/status'),
apiPath: Schema.string().default('/api'),
selfUrl: Schema.string().role('link').default(''),
open: Schema.boolean(),
head: Schema.array(Head),
Expand Down
16 changes: 1 addition & 15 deletions plugins/webui/src/shared/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ export abstract class WebUI<T = unknown> extends Service<T> {
public id = Math.random().toString(36).slice(2)

readonly entries: Dict<Entry> = Object.create(null)
readonly apis: Dict<SocketListener> = Object.create(null)
readonly listeners: Dict<(args?: any) => any> = Object.create(null)
readonly clients: Dict<Client> = Object.create(null)

Expand All @@ -46,25 +45,12 @@ export abstract class WebUI<T = unknown> extends Service<T> {
}

abstract resolveEntry(files: Entry.Files, key: string): string[]
abstract addListener<K extends keyof Events>(event: K, callback: Events[K]): void

addEntry<T>(files: Entry.Files, data?: () => T) {
return new Entry(this[Context.origin], files, data)
}

addListener<K extends keyof Events>(event: K, callback: Events[K]) {
this.apis[event] = callback
this.ctx.server.post(`/${event}`, async (koa) => {
const { body } = koa.request
try {
koa.body = await (callback as any)(body)
koa.status = 200
} catch (error) {
this.ctx.logger.warn(error)
koa.status = 500
}
})
}

async broadcast(type: string, body: any) {
const handles = Object.values(this.clients)
if (!handles.length) return
Expand Down

0 comments on commit 06b153d

Please sign in to comment.