? P | IntersectingDimension : never;
// @public (undocumented)
-export type IntersectingDimension = T extends Array ? P | IntersectingDimension : never;
+export type Key = string | number;
// @public (undocumented)
export type MaybePromise = T | Promise;
@@ -180,13 +169,40 @@ export type SelectItem = {
// @public (undocumented)
export type SelectKey = string | number;
+// @public (undocumented)
+export type TableCreateOption = {
+ keyField: string;
+ name?: string;
+ operations?: UiAction[];
+ updateAction?: boolean;
+ addAction?: UiButton["props"];
+};
+
+// @public (undocumented)
+export type TableFilter = {
+ skip?: number;
+ number?: number;
+};
+
+// @public (undocumented)
+export type TableRenderFn = (args: {
+ record: Row;
+ index: number;
+ column: Readonly>;
+}) => UiBase | UiBase[];
+
+// @public (undocumented)
+export type TableRow = {
+ [key: string]: any;
+};
+
// @public
export abstract class TTY {
confirm(title: string, content?: string): Promise;
pick(title: string, options: SelectItem[]): Promise;
// Warning: (ae-forgotten-export) The symbol "TtyInputsReq" needs to be exported by the entry point index.d.ts
abstract read(config: TtyInputsReq): Promise;
- readFiles(option?: TtyReadFileOption): Promise;
+ readFiles(option?: TtyReadFileOption): Promise;
readText(title: string, max?: number): Promise;
readText(max?: number): Promise;
select(title: string, options: SelectItem[], config?: {
@@ -200,7 +216,7 @@ export abstract class TTY {
writeTable(data: any[][], header?: string[]): void;
writeText(title: string, option?: TtyWriteTextType | TTyWriteTextOption): void;
// (undocumented)
- writeUiLink(ui: VioChart): void;
+ writeUiLink(ui: VioObject): void;
}
// @public (undocumented)
@@ -251,17 +267,66 @@ export type TTyWriteTextOption = {
// @public (undocumented)
export type TtyWriteTextType = "warn" | "log" | "error" | "info";
+// @public (undocumented)
+export interface UiAction extends UiBase {
+ // (undocumented)
+ key: string;
+}
+
+// @public (undocumented)
+export interface UiBase {
+ // (undocumented)
+ ui: string;
+}
+
+// @public (undocumented)
+export class UiButton implements UiAction {
+ constructor(key: string, props?: UiButton["props"]);
+ // (undocumented)
+ readonly key: string;
+ // (undocumented)
+ props?: {
+ icon?: string;
+ text?: string;
+ type?: string;
+ tooltip?: string;
+ disable?: boolean;
+ };
+ // (undocumented)
+ readonly ui = "button";
+}
+
+// @public (undocumented)
+export interface UiInput extends UiBase {
+}
+
+// @public (undocumented)
+export interface UiOutput extends UiBase {
+}
+
+// @public (undocumented)
+export type UiTag = UiOutput & {
+ ui: "tag";
+ props: {
+ text?: string;
+ icon?: string;
+ color?: string;
+ };
+};
+
// @public
export interface Vio extends TTY {
- readonly chart: ChartCenter;
+ // @deprecated
+ readonly chart: VioObjectCenter;
// Warning: (ae-forgotten-export) The symbol "WebSocket_2" needs to be exported by the entry point index.d.ts
joinFormWebsocket(websocket: WebSocket_2, onDispose?: (viewer: Disposable_2) => void): Disposable_2;
+ readonly object: VioObjectCenter;
readonly tty: TtyCenter;
viewerNumber: number;
}
// @public
-export interface VioChart {
+export interface VioChart extends VioObject {
// (undocumented)
cachedSize: number;
// (undocumented)
@@ -272,22 +337,20 @@ export interface VioChart {
// (undocumented)
getCacheDateItem(): IterableIterator>>;
// (undocumented)
- readonly id: number;
- // (undocumented)
maxCacheSize: number;
// (undocumented)
readonly meta: VioChartMeta;
- onRequestUpdate?: () => MaybePromise;
+ requestUpdate(): MaybePromise>;
+ // (undocumented)
+ readonly type: "chart";
updateData(data: T, timeName?: string): void;
updateThrottle: number;
}
// @public
-export type VioChartCreateConfig = ChartCreateOption & {
+export type VioChartCreateConfig = ChartCreateOption & {
id: number;
dimension: number;
- onRequestUpdate?(): MaybePromise;
- updateThrottle?: number;
};
// @public (undocumented)
@@ -298,6 +361,13 @@ export type VioChartMeta = ChartMeta.Progress | ChartMeta.Gauge | ChartMeta.Line
// @public (undocumented)
export type VioChartType = VioChartMeta["chartType"];
+// @public (undocumented)
+export type VioFileData = {
+ name: string;
+ data: Uint8Array;
+ mime: string;
+};
+
// @public
export class VioHttpServer {
constructor(vio: Vio, opts?: VioHttpServerOption);
@@ -313,11 +383,86 @@ export class VioHttpServer {
// @public (undocumented)
export interface VioHttpServerOption {
+ frontendConfig?: object;
+ requestHandler?: (request: Request) => Response | undefined | Promise;
+ rpcAuthenticate?(request: Request): void;
+ // @deprecated (undocumented)
staticHandler?: (request: Request) => Response | undefined | Promise;
staticSetHeaders?: Record;
vioStaticDir?: string;
}
+// @public (undocumented)
+export interface VioObject {
+ // (undocumented)
+ readonly id: number;
+ // (undocumented)
+ name?: string;
+ // (undocumented)
+ readonly type: string;
+}
+
+// @public (undocumented)
+export interface VioObjectCenter {
+ // (undocumented)
+ chartsNumber: number;
+ // (undocumented)
+ disposeObject(object: VioObject): void;
+ getAll(): IterableIterator;
+}
+
+// @public (undocumented)
+export interface VioObjectCenter {
+ // @deprecated
+ create(dimension: 1, options?: ChartCreateOption): VioChart;
+ // @deprecated
+ create(dimension: 2, options?: ChartCreateOption): VioChart;
+ // @deprecated
+ create(dimension: 3, options?: ChartCreateOption): VioChart;
+ // @deprecated (undocumented)
+ create(dimension: number, options?: ChartCreateOption): VioChart;
+ createChart(dimension: 1, options?: ChartCreateOption): VioChart;
+ createChart(dimension: 2, options?: ChartCreateOption): VioChart;
+ createChart(dimension: 3, options?: ChartCreateOption): VioChart;
+ // (undocumented)
+ createChart(dimension: number, options?: ChartCreateOption): VioChart;
+ // @deprecated (undocumented)
+ disposeChart(chart: VioChart): void;
+ // @deprecated (undocumented)
+ get(chartId: number): VioChart | undefined;
+}
+
+// @public (undocumented)
+export interface VioObjectCenter {
+ // (undocumented)
+ createTable(columns: Column[], option?: TableCreateOption): VioTable;
+}
+
+// @public (undocumented)
+export interface VioTable extends VioObject {
+ addRow(row: Row, afterIndex?: number): void;
+ deleteRow(index: number, count: number): void;
+ // (undocumented)
+ getRow(index: number): Row;
+ // (undocumented)
+ getRowIndexByKey(key: Key): number;
+ // (undocumented)
+ getRows(filter?: TableFilter): {
+ rows: Row[];
+ index: number[];
+ };
+ onRowAction(operateKey: string, rowKey: Key): void;
+ onRowAdd(param: Add): void;
+ onRowUpdate(rowKey: string, param: Update): void;
+ onTableAction(operateKey: string, rowKeys: Key[]): void;
+ rowNumber: number;
+ // (undocumented)
+ readonly type: "table";
+ // (undocumented)
+ updateRow(row: Row, index: number): void;
+ updateTable(data: Row[]): void;
+}
+
// @public (undocumented)
export interface VioTty extends TTY {
cachedSize: number;
@@ -326,10 +471,6 @@ export interface VioTty extends TTY {
getCache(): IterableIterator;
}
-// Warnings were encountered during analysis:
-//
-// dist/mod.d.ts:309:5 - (ae-forgotten-export) The symbol "DimensionInfo" needs to be exported by the entry point index.d.ts
-
// (No @packageDocumentation comment for this package)
```
diff --git a/vio/build/rollup.config.js b/vio/build/rollup.config.js
index 3182b42..4fb316f 100644
--- a/vio/build/rollup.config.js
+++ b/vio/build/rollup.config.js
@@ -42,6 +42,7 @@ export default defineEvConfig({
],
extra: {
typescript: {
+ tsconfig: "./tsconfig.build.json",
compilerOptions: {
target: "ES2022",
module: "NodeNext",
diff --git a/vio/deno.json b/vio/deno.json
index ef21ed4..65b2447 100644
--- a/vio/deno.json
+++ b/vio/deno.json
@@ -1,6 +1,6 @@
{
"name": "@asla/vio",
- "version": "0.1.1",
+ "version": "0.2.0",
"exports": "./src/mod.deno.ts",
"tasks": {
"gen-doc": "deno doc --html --output=temp --name=VIO src/mod.ts",
@@ -10,7 +10,7 @@
"@asla/vio": "./src/mod.deno.ts",
"jbod": "jsr:@asn/jbod@^0.5.0",
"evlib": "jsr:@asn/evlib@^2.6.1",
- "cpcall": "jsr:@asn/cpcall@^0.6.0"
+ "cpcall": "jsr:@asn/cpcall@^0.6.2"
},
"compilerOptions": {
"lib": ["deno.window"]
diff --git a/vio/examples/cases/chart.ts b/vio/examples/cases/chart.ts
index 6c76b11..a44bf67 100644
--- a/vio/examples/cases/chart.ts
+++ b/vio/examples/cases/chart.ts
@@ -9,7 +9,8 @@ function getMemoryChartData() {
/** 内存图。每两秒更新一次图 */
export async function memoryChart(vio: Vio) {
- const chart = vio.chart.create(2, {
+ const chart = vio.object.createChart(2, {
+ name: "内存",
meta: {
chartType: "line", //折线图
title: "内存", // 图表标题
diff --git a/vio/examples/cases/table.ts b/vio/examples/cases/table.ts
new file mode 100644
index 0000000..2213521
--- /dev/null
+++ b/vio/examples/cases/table.ts
@@ -0,0 +1,112 @@
+import { Column, UiButton, Vio, VioTable, Key } from "@asla/vio";
+
+type Process = {
+ swapFile: string;
+ args: string[];
+ createTime: number;
+ status: 0 | 1;
+ id: string;
+};
+
+const columns: Column[] = [
+ { dataIndex: "swapFile" },
+ {
+ dataIndex: "args",
+ },
+ { title: "创建事件", dataIndex: "createTime" },
+ {
+ title: "状态",
+ dataIndex: "status",
+ render: `
+ const { record } = args;
+ const status = record.status;
+ return {
+ key: record.status ? "stop" : "run",
+ ui: "tag",
+ props: { text: status ? "running" : "stopped", color: status ? "green" : "red" },
+ };
+ `,
+ },
+ {
+ title: "操作",
+ render: `
+ const { record } = args;
+ return [
+ { key: "stop", ui: "button", props: { disable: record.status === 0, text: "停止", type: "text" } },
+ { key: "run", ui: "button", props: { disable: record.status === 1, text: "启动", type: "text" } },
+ ];
+ `,
+ },
+];
+
+export function appendTable(vio: Vio) {
+ const t1 = vio.object.createTable(columns, {
+ keyField: "id",
+ name: "只读表格",
+ });
+ let rows: Process[] = [];
+ for (let i = 0; i < 100; i++) {
+ rows[i] = createRow("ps-" + i);
+ }
+ t1.updateTable(rows);
+ const t2 = vio.object.createTable(columns, {
+ keyField: "id",
+ name: "可编辑表格",
+ addAction: { text: "添加进程" },
+ updateAction: true,
+ operations: [
+ new UiButton("run", {
+ text: "批量启动",
+ icon: "https://search-operate.cdn.bcebos.com/e8cbce1d53432a6950071bf26b640e2b.gif",
+ }),
+ new UiButton("stop", { text: "批量停止" }),
+ ],
+ });
+ t2.addRow(createRow("p2-1"));
+ t2.addRow(createRow("p2-2"));
+ t2.addRow(createRow("p2-4"));
+
+ const onAction = function (this: VioTable, opKey: string, rowKey: Key) {
+ const index = this.getRowIndexByKey(rowKey);
+ const row = this.getRow(index);
+
+ switch (opKey) {
+ case "run":
+ return new Promise((resolve, reject) => {
+ setTimeout(() => {
+ resolve();
+ this.updateRow({ ...row, status: 1 }, index);
+ }, 1000);
+ });
+ case "stop":
+ this.updateRow({ ...row, status: 0 }, index);
+ break;
+ default:
+ break;
+ }
+ };
+ const onTableAction = function (this: VioTable, opKey: string, selectedKeys: Key[]) {
+ const status = opKey === "run" ? 1 : 0;
+ for (const key of selectedKeys) {
+ const index = this.getRowIndexByKey(key);
+ const row = this.getRow(index);
+ this.updateRow({ ...row, status }, index);
+ }
+ };
+ t1.onRowAction = onAction;
+ t2.onRowAction = onAction;
+
+ t1.onTableAction = onTableAction;
+ t2.onTableAction = onTableAction;
+
+ return { t1, t2 };
+}
+function createRow(id: string): Process {
+ return {
+ args: ["-a"],
+ swapFile: "xxx" + id,
+ createTime: Date.now(),
+ id,
+ status: Math.random() > 0.5 ? 1 : 0,
+ };
+}
diff --git a/vio/examples/run_server.ts b/vio/examples/run_server.ts
index 9ad718a..3c0648b 100644
--- a/vio/examples/run_server.ts
+++ b/vio/examples/run_server.ts
@@ -2,10 +2,12 @@ import vio, { Vio, VioHttpServer } from "@asla/vio";
import { inputSelect } from "./cases/action.ts";
import { intervalOutput } from "./cases/output.ts";
import { memoryChart } from "./cases/chart.ts";
+import { appendTable } from "./cases/table.ts";
export async function startDefaultServer(vio: Vio, port: number = 8887, hostname: string = "127.0.0.1") {
intervalOutput(vio.tty.get(0));
inputSelect(vio.tty.get(1));
memoryChart(vio);
+ appendTable(vio);
const server = new VioHttpServer(vio);
await server.listen(port, hostname);
diff --git a/vio/package.json b/vio/package.json
index 92b7a76..a63ea84 100644
--- a/vio/package.json
+++ b/vio/package.json
@@ -1,6 +1,6 @@
{
"name": "@asla/vio",
- "version": "0.1.1",
+ "version": "0.2.0",
"description": "A web terminal that provides a variety of graphical controls. You can interact with processes in the browser",
"type": "module",
"scripts": {
@@ -15,12 +15,12 @@
"license": "MIT",
"devDependencies": {
"@eavid/lib-node": "^2.1.2",
- "@types/node": "^20.14.10",
+ "@types/node": "^22.0.2",
"evlib": "^2.6.1",
"rollup-plugin-dts": "^6.1.1"
},
"dependencies": {
- "cpcall": "^0.6.0"
+ "cpcall": "^0.6.2"
},
"publishConfig": {
"access": "public",
diff --git a/vio/src/client.ts b/vio/src/client.ts
index d95ef1d..d098ea9 100644
--- a/vio/src/client.ts
+++ b/vio/src/client.ts
@@ -1,2 +1,3 @@
export type * from "./vio/api_type.ts";
-export * from "./vio/classes/VioChart.ts";
+export * from "./vio/vio_object/mod.private.ts";
+export * from "./vio/tty/mod.private.ts";
diff --git a/vio/src/const.ts b/vio/src/const.ts
index ebbfe05..a4c0e87 100644
--- a/vio/src/const.ts
+++ b/vio/src/const.ts
@@ -1,11 +1,5 @@
import path from "node:path";
-export class InstanceDisposedError extends Error {
- constructor(name: string = "Instance") {
- super(`${name} has been disposed`);
- }
-}
-
// 输出目录保持
export const packageDir = (function () {
diff --git a/vio/src/lib/serve/HttpServer.ts b/vio/src/lib/serve/HttpServer.ts
index 00f9cff..02b8745 100644
--- a/vio/src/lib/serve/HttpServer.ts
+++ b/vio/src/lib/serve/HttpServer.ts
@@ -108,6 +108,7 @@ export class HttpServer implements DenoHttpServer {
shutdown(): Promise {
if (this.#closing) return this.finished;
this.#server.close();
+ this.#server.closeAllConnections();
return this.finished;
}
diff --git a/vio/src/mod.node.ts b/vio/src/mod.node.ts
index 356a7b5..c80497d 100644
--- a/vio/src/mod.node.ts
+++ b/vio/src/mod.node.ts
@@ -5,7 +5,7 @@ import { platformApi } from "./server/platform_api.ts";
if (runtimeEngine === "deno") {
// deno 可能导入 npm:@asla/vio
- const mod = await import("./compat/platform_api.deno.ts");
+ const mod = await import("./compat/platform_api.deno.ts" as string); // 跳过类型检查
Object.assign(platformApi, mod.default);
} else {
const mod = await import("./compat/platform_api.node.ts");
diff --git a/vio/src/rpc/ClientObjectApi.ts b/vio/src/rpc/ClientObjectApi.ts
new file mode 100644
index 0000000..587c1ad
--- /dev/null
+++ b/vio/src/rpc/ClientObjectApi.ts
@@ -0,0 +1,38 @@
+import { CpCall, MakeCallers } from "cpcall";
+import type {
+ VioClientExposed,
+ VioObjectCreateDto,
+ ChartUpdateData,
+ ClientObjectExposed,
+ TableChanges,
+} from "../vio/api_type.ts";
+import { Key, TableRow } from "../vio/mod.ts";
+
+export class ClientObjectApi implements ClientObjectExposed {
+ constructor(api: MakeCallers) {
+ this.#api = api.object;
+ }
+ #api?: MakeCallers;
+ createObject(info: VioObjectCreateDto): void {
+ if (!this.#api) return;
+ CpCall.exec(this.#api.createObject, info);
+ }
+ deleteObject(id: number): void {
+ if (!this.#api) return;
+ CpCall.exec(this.#api.deleteObject, id);
+ }
+
+ writeChart(id: number, data: ChartUpdateData): void {
+ if (!this.#api) return;
+ CpCall.exec(this.#api.writeChart, id, data);
+ }
+
+ updateTable(tableId: number): void {
+ if (!this.#api) return;
+ CpCall.exec(this.#api.updateTable, tableId);
+ }
+ tableChange(tableId: number, changes: TableChanges): void {
+ if (!this.#api) return;
+ CpCall.exec(this.#api.tableChange, tableId, changes);
+ }
+}
diff --git a/vio/src/rpc/ClientTtyApi.ts b/vio/src/rpc/ClientTtyApi.ts
new file mode 100644
index 0000000..7200f25
--- /dev/null
+++ b/vio/src/rpc/ClientTtyApi.ts
@@ -0,0 +1,21 @@
+import { CpCall, MakeCallers } from "cpcall";
+import { ClientTtyExposed, TtyInputsReq, TtyOutputsData, VioClientExposed } from "../vio/api_type.ts";
+
+export class ClientTtyApi implements ClientTtyExposed {
+ constructor(api: MakeCallers) {
+ this.#api = api;
+ }
+ #api?: MakeCallers;
+ sendTtyReadRequest(ttyId: number, requestId: number, data: TtyInputsReq) {
+ if (!this.#api) return Promise.reject(new Error("Viewer has been disposed"));
+ CpCall.exec(this.#api.tty.sendTtyReadRequest, ttyId, requestId, data);
+ }
+ writeTty(id: number, data: TtyOutputsData | TtyOutputsData): void {
+ if (!this.#api) throw new Error("Viewer has been disposed");
+ CpCall.exec(this.#api.tty.writeTty, id, data);
+ }
+ ttyReadEnableChange(ttyId: number, enable: boolean): void {
+ if (!this.#api) return;
+ CpCall.exec(this.#api.tty.ttyReadEnableChange, ttyId, enable);
+ }
+}
diff --git a/vio/src/rpc/mod.ts b/vio/src/rpc/mod.ts
new file mode 100644
index 0000000..1023693
--- /dev/null
+++ b/vio/src/rpc/mod.ts
@@ -0,0 +1,3 @@
+export * from "./ClientObjectApi.ts";
+export * from "./ClientTtyApi.ts";
+export * from "./rpc_api.ts";
diff --git a/vio/src/rpc/rpc_api.ts b/vio/src/rpc/rpc_api.ts
index ddde2a5..6f319a6 100644
--- a/vio/src/rpc/rpc_api.ts
+++ b/vio/src/rpc/rpc_api.ts
@@ -1,141 +1,26 @@
-import { CpCall, MakeCallers, createWebSocketCpc } from "cpcall";
-import type {
- VioClientExposed,
- ChartInfo,
- TtyOutputsData,
- VioServerExposed,
- ChartCreateInfo,
- ChartUpdateData,
- TtyInputsReq,
-} from "../vio/api_type.ts";
-import { TtyCenter, ChartCenter, Vio } from "../vio/mod.ts";
-import { RequestUpdateRes, TtyReadResolver, VioChart } from "../vio/classes/mod.ts";
+import { CpCall, createWebSocketCpc } from "cpcall";
+import type { VioClientExposed, VioServerExposed } from "../vio/api_type.ts";
+import { Vio } from "../vio/mod.ts";
import type { WebSocket } from "../lib/deno/http.ts";
-import { MaybePromise } from "../type.ts";
-import { indexRecordToArray } from "../lib/array_like.ts";
-function getChartInfo(chart: VioChart): ChartInfo {
- const cacheData: T[] = new Array(chart.cachedSize);
- const timestamps: number[] = new Array(chart.cachedSize);
- let i = 0;
- for (const item of chart.getCacheDateItem()) {
- cacheData[i] = item.data;
- timestamps[i] = item.timestamp;
- i++;
- }
- return {
- meta: chart.meta,
- dimension: chart.dimension,
- id: chart.id,
- cacheList: Array.from(chart.getCacheDateItem()),
- dimensions: indexRecordToArray(chart.dimensions),
- };
-}
-class RpcServerExposed implements VioServerExposed {
- constructor(vio: { chart: ChartCenter; tty: TtyCenter }, clientApi: RpcClientApi) {
- this.#clientApi = clientApi;
- this.#vio = vio;
- }
- #clientApi: RpcClientApi;
- #vio: { chart: ChartCenter; tty: TtyCenter };
- getCharts(): { list: ChartInfo[] } {
- const list: ChartInfo[] = new Array(this.#vio.chart.chartsNumber);
- let i = 0;
- for (const chart of this.#vio.chart.getAll()) {
- list[i++] = getChartInfo(chart);
- }
- return { list };
- }
- getChartInfo(id: number): ChartInfo | undefined {
- const chart = this.#vio.chart.get(id);
- if (!chart) return;
- return getChartInfo(chart);
- }
- requestUpdateChart(chartId: number): MaybePromise> {
- return this.#vio.chart.requestUpdate(chartId);
- }
+import { RpcServerObjectExposed, VioObjectCenterImpl } from "../vio/vio_object/mod.private.ts";
+import { RpcServerTtyExposed } from "../vio/tty/mod.private.ts";
+import { ClientTtyApi } from "./ClientTtyApi.ts";
+import { ClientObjectApi } from "./ClientObjectApi.ts";
- getTtyCache(id: number): TtyOutputsData[] {
- const tty = this.#vio.tty.getCreated(id);
- if (!tty) return [];
- return Array.from(tty.getCache());
- }
- resolveTtyReadRequest(ttyId: number, requestId: number, res: any): boolean {
- const hd = this.#resolverMap[ttyId];
- if (!hd) return false;
- return hd.resolve(requestId, res);
- }
- rejectTtyReadRequest(ttyId: number, requestId: number, reason: any): boolean {
- const hd = this.#resolverMap[ttyId];
- if (!hd) return false;
- return hd.reject(requestId, reason);
- }
- inputTty(ttyId: number, data: any): boolean {
- const resolver = this.#resolverMap[ttyId];
- if (!resolver) return false;
- return resolver.input(data);
- }
- /** 某个连接中开启读取权的 tty 字典 */
- #resolverMap: Record = {};
- setTtyReadEnable(ttyId: number, enable: boolean): boolean {
- let resolver = this.#resolverMap[ttyId];
- if (enable) {
- if (resolver) return true;
- else {
- resolver = this.#vio.tty.setReader(ttyId, {
- read: (ttyId, requestId, data) => this.#clientApi.sendTtyReadRequest(ttyId, requestId, data),
- dispose: () => {
- if (this.#resolverMap[ttyId]) {
- delete this.#resolverMap[ttyId];
- this.#clientApi.ttyReadEnableChange(ttyId, false);
- }
- },
- });
- }
- this.#resolverMap[ttyId] = resolver;
- } else {
- if (resolver) {
- delete this.#resolverMap[ttyId]; // 主动关闭,dispose 之前删除,
- resolver.dispose();
- }
- }
- return true;
- }
-}
-class RpcClientApi implements VioClientExposed {
- constructor(api: MakeCallers) {
- this.#api = api;
- }
- #api?: MakeCallers;
- sendTtyReadRequest(ttyId: number, requestId: number, data: TtyInputsReq) {
- if (!this.#api) return Promise.reject(new Error("Viewer has been disposed"));
- CpCall.exec(this.#api.sendTtyReadRequest, ttyId, requestId, data);
- }
- writeTty(id: number, data: TtyOutputsData | TtyOutputsData): void {
- if (!this.#api) throw new Error("Viewer has been disposed");
- CpCall.exec(this.#api.writeTty, id, data);
- }
- createChart(chart: ChartCreateInfo): void {
- if (!this.#api) return;
- CpCall.exec(this.#api.createChart, chart);
- }
- deleteChart(id: number): void {
- if (!this.#api) return;
- CpCall.exec(this.#api.deleteChart, id);
- }
- writeChart(id: number, data: ChartUpdateData): void {
- if (!this.#api) return;
- CpCall.exec(this.#api.writeChart, id, data);
- }
- ttyReadEnableChange(ttyId: number, enable: boolean): void {
- if (!this.#api) return;
- CpCall.exec(this.#api.ttyReadEnableChange, ttyId, enable);
- }
-}
+export type RpcClientApi = {
+ tty: ClientTtyApi;
+ object: ClientObjectApi;
+};
-export function initWebsocket(vio: Vio, ws: WebSocket): { cpc: CpCall; clientApi: VioClientExposed } {
+export function initWebsocket(vio: Vio, ws: WebSocket): { cpc: CpCall; clientApi: RpcClientApi } {
const cpc = createWebSocketCpc(ws);
- const clientApi = new RpcClientApi(cpc.genCaller());
- cpc.setObject(new RpcServerExposed(vio, clientApi));
+ const caller = cpc.genCaller();
+ const objectApi = new ClientObjectApi(caller);
+ const ttyApi = new ClientTtyApi(caller);
+ cpc.exposeObject({
+ object: new RpcServerObjectExposed(vio.object as VioObjectCenterImpl),
+ tty: new RpcServerTtyExposed(vio.tty, ttyApi),
+ } satisfies VioServerExposed);
- return { clientApi, cpc };
+ return { clientApi: { object: objectApi, tty: ttyApi }, cpc };
}
diff --git a/vio/src/server/rpc_vio_server.ts b/vio/src/server/rpc_vio_server.ts
index 89d6862..9e8f131 100644
--- a/vio/src/server/rpc_vio_server.ts
+++ b/vio/src/server/rpc_vio_server.ts
@@ -6,7 +6,6 @@ import { packageDir } from "../const.ts";
import path from "node:path";
import { HttpServer, ServeHandlerInfo } from "../lib/deno/http.ts";
import { platformApi } from "./platform_api.ts";
-
/**
* @public
*/
@@ -15,8 +14,17 @@ export interface VioHttpServerOption {
vioStaticDir?: string;
/** 覆盖静态资源响应头 */
staticSetHeaders?: Record;
- /** 自定义处理静态资源请求。如果设置了这个处理函数,将忽略 vioStaticDir 和 staticSetHeaders */
+ /**
+ * @deprecated 改用 requestHandler
+ * 自定义处理请求。请求处理在 api 之后,静态文件服务器之前。
+ */
staticHandler?: (request: Request) => Response | undefined | Promise;
+ /** 自定义处理请求。请求处理在 api 之后,静态文件服务器之前。 */
+ requestHandler?: (request: Request) => Response | undefined | Promise;
+ /** web终端前端配置 */
+ frontendConfig?: object;
+ /** 连接鉴权. 如果不通过,应抛出异常 */
+ rpcAuthenticate?(request: Request): void;
}
/**
* vio http 服务器
@@ -27,43 +35,66 @@ export class VioHttpServer {
private vio: Vio,
opts: VioHttpServerOption = {},
) {
- let { vioStaticDir, staticSetHeaders, staticHandler } = opts;
+ let { vioStaticDir, staticSetHeaders, staticHandler, requestHandler = staticHandler, rpcAuthenticate } = opts;
if (!vioStaticDir && packageDir) {
vioStaticDir = path.resolve(packageDir, "assets/web");
}
- if (staticHandler) {
- this.#staticHandler = staticHandler;
- } else if (vioStaticDir) {
- const staticHandler = new FileServerHandler(vioStaticDir, { setHeaders: staticSetHeaders });
- this.#staticHandler = (request) => {
- return staticHandler.getResponse(new URL(request.url).pathname, request.headers);
- };
+ if (requestHandler) {
+ this.#customHandler = requestHandler;
+ }
+ if (vioStaticDir) {
+ this.#fileServerHandler = new FileServerHandler(vioStaticDir, { setHeaders: staticSetHeaders });
+ }
+ if (opts.frontendConfig) {
+ this.#frontendConfig = Response.json(opts.frontendConfig);
}
+ if (typeof opts.rpcAuthenticate === "function") this.#rpcAuthenticate = opts.rpcAuthenticate;
const router = new Router();
router.set("/api/test", function () {
- const res = { value: "ok" };
- return new Response(JSON.stringify(res));
+ return Response.json({ value: "ok" });
});
router.set("/api/rpc", ({ request: req }) => {
if (req.headers.get("Upgrade") !== "websocket") return new Response(undefined, { status: 400 });
+ if (this.#rpcAuthenticate) {
+ try {
+ this.#rpcAuthenticate(req);
+ } catch (error) {
+ return new Response(null, { status: 403 });
+ }
+ }
+
const { response, socket: websocket } = platformApi.upgradeWebSocket(req);
- this.#ontWebSocketConnect(websocket);
+ this.#onWebSocketConnect(websocket);
return response;
});
this.#router = router;
+ this.#rpcAuthenticate;
}
- #staticHandler: VioHttpServerOption["staticHandler"];
+
+ #customHandler: VioHttpServerOption["requestHandler"];
+ #fileServerHandler?: FileServerHandler;
+ #frontendConfig?: Response;
#handler = async (req: Request, info: ServeHandlerInfo) => {
const context = createRequestContext(req, info);
const pathname = context.url.pathname;
- const handler = this.#router.get(pathname);
- if (handler) {
- return handler(context);
+ if (pathname.startsWith("/api")) {
+ const handler = this.#router.get(pathname);
+ if (handler) {
+ return handler(context);
+ }
+ return new Response(null, { status: 404 });
}
- if (this.#staticHandler) {
- const response = await this.#staticHandler(req);
+ if (pathname === "/config.json" && this.#frontendConfig) {
+ return this.#frontendConfig;
+ }
+ if (this.#customHandler) {
+ const response = await this.#customHandler(req);
+ if (response) return response;
+ }
+ if (this.#fileServerHandler) {
+ const response = await this.#fileServerHandler.getResponse(pathname, req.headers);
if (response) return response;
}
return new Response(null, { status: 404 });
@@ -71,7 +102,9 @@ export class VioHttpServer {
#serve?: HttpServer;
#router: Router;
- async #ontWebSocketConnect(ws: WebSocket): Promise {
+
+ #rpcAuthenticate?: (request: Request) => void;
+ async #onWebSocketConnect(ws: WebSocket): Promise {
if (!this.#serve) throw new Error("unable connect");
if (ws.readyState === ws.CONNECTING) {
let listener: (...args: any[]) => void;
diff --git a/vio/src/vio/api_type.ts b/vio/src/vio/api_type.ts
index 1de6218..48d0017 100644
--- a/vio/src/vio/api_type.ts
+++ b/vio/src/vio/api_type.ts
@@ -1,44 +1,15 @@
-export * from "./api_type/chart.type.ts";
-export * from "./api_type/tty.type.ts";
+export * from "./tty/tty.dto.ts";
+export * from "./vio_object/object.dto.ts";
-import { MaybePromise } from "../type.ts";
-import type { ChartCreateInfo, ChartInfo, ChartUpdateData, RequestUpdateRes } from "./api_type/chart.type.ts";
-import type { TtyOutputsData, TtyInputsReq } from "./api_type/tty.type.ts";
+import type { ServerTtyExposed, ClientTtyExposed } from "./tty/tty.dto.ts";
+import type { ClientObjectExposed, ServerObjectExposed } from "./vio_object/object.dto.ts";
-export interface VioClientExposed extends ChartController {
- /** 在指定 TTY 输出数据 */
- writeTty(ttyId: number, data: TtyOutputsData): void;
- /** 在指定 TTY 发送读取请求 */
- sendTtyReadRequest(ttyId: number, requestId: number, opts: TtyInputsReq): void;
- /** 切换 TTY 读取权限 */
- ttyReadEnableChange(ttyId: number, enable: boolean): void;
-}
-
-export interface ChartController {
- /** 在指定图表输出数据 */
- writeChart(chartId: number, data: Readonly>): void;
- /** 删除指定图表 */
- deleteChart(chartId: number): void;
- /** 创建图表 */
- createChart(chartInfo: ChartCreateInfo): void;
+export interface VioClientExposed {
+ object: ClientObjectExposed;
+ tty: ClientTtyExposed;
}
export interface VioServerExposed {
- /** 获取所有图表的信息 */
- getCharts(): MaybePromise<{ list: ChartInfo[] }>;
- /** 获取指定图表的信息 */
- getChartInfo(chartId: number): MaybePromise;
- /** 主动请求更新图 */
- requestUpdateChart(chartId: number): MaybePromise>;
-
- /** 获取 TTY 输出缓存日志 */
- getTtyCache(ttyId: number): MaybePromise;
- /** 切换 TTY 读取权限 */
- setTtyReadEnable(ttyId: number, enable: boolean): MaybePromise;
- /** 解决 tty 输入请求 */
- resolveTtyReadRequest(ttyId: number, requestId: number, res: any): MaybePromise;
- /** 拒绝 tty 输入请求 */
- rejectTtyReadRequest(ttyId: number, requestId: number, reason?: any): MaybePromise;
-
- inputTty(ttyId: number, data: any): MaybePromise;
+ object: ServerObjectExposed;
+ tty: ServerTtyExposed;
}
diff --git a/vio/src/vio/classes/chart.ts b/vio/src/vio/classes/chart.ts
deleted file mode 100644
index be4b1c8..0000000
--- a/vio/src/vio/classes/chart.ts
+++ /dev/null
@@ -1,181 +0,0 @@
-import type {
- ChartUpdateData,
- ChartUpdateSubData,
- DimensionalityReduction,
- IntersectingDimension,
- RequestUpdateRes,
-} from "../api_type/chart.type.ts";
-import { UniqueKeyMap } from "evlib/data_struct";
-import { deepClone } from "evlib";
-import { ChartUpdateLowerOption, ChartUpdateOption, VioChart, VioChartImpl, ChartCreateOption } from "./VioChart.ts";
-import { indexRecordToArray } from "../../lib/array_like.ts";
-import { ChartController } from "../api_type.ts";
-import { MaybePromise } from "../../type.ts";
-
-/**
- * @public
- * @category Chart
- */
-export class ChartCenter {
- /** 图表默认缓存数量 */
- static TTY_DEFAULT_CACHE_SIZE = 20;
- constructor(private ctrl: ChartController) {}
- #instanceMap = new UniqueKeyMap(2 ** 32);
- /** 获取指定索引的 Chart */
- get(chartId: number): VioChart | undefined {
- return this.#instanceMap.get(chartId);
- }
- /** 获取所有已创建的 Chart */
- getAll(): IterableIterator> {
- return this.#instanceMap.values();
- }
- /** 所有已创建图表的数量 */
- get chartsNumber(): number {
- return this.#instanceMap.size;
- }
-
- /** 创建一维图表 */
- create(dimension: 1, options?: CenterCreateChartOption): VioChart;
- /** 创建二维图表 */
- create(dimension: 2, options?: CenterCreateChartOption): VioChart;
- /** 创建三维图表 */
- create(dimension: 3, options?: CenterCreateChartOption): VioChart;
- create(dimension: number, options?: CenterCreateChartOption): VioChart;
- create(dimension: number, options?: CenterCreateChartOption): VioChart {
- let chartId = this.#instanceMap.allocKeySet(null as any);
- const chart = new ChartCenter.Chart(this, chartId, dimension, options);
- this.#instanceMap.set(chartId, chart);
- this.ctrl.createChart({
- meta: chart.meta,
- dimension: chart.dimension,
- id: chartId,
- dimensions: indexRecordToArray(chart.dimensions),
- });
-
- return chart;
- }
- /** web 端主动请求更新图表,这会触发 chart.onRequestUpdate() */
- requestUpdate(chartId: number): MaybePromise> {
- const chart = this.#instanceMap.get(chartId);
- if (!chart) throw new Error(`Chart '${chartId}' dest not exist`);
- return chart.onUpdate() as MaybePromise>;
- }
- /** 销毁图表 */
- disposeChart(chart: VioChart) {
- if (!(chart instanceof ChartCenter.Chart)) throw new Error("This chart does not belong to the center");
- chart.dispose();
- }
- private static Chart = class RpcVioChart extends VioChartImpl {
- constructor(center: ChartCenter, chartId: number, dimension: number, options: CenterCreateChartOption = {}) {
- const { maxCacheSize = ChartCenter.TTY_DEFAULT_CACHE_SIZE } = options;
- super({
- ...options,
- id: chartId,
- dimension,
- maxCacheSize,
- });
- this.#center = center;
- }
- #lastData?: MaybePromise>;
-
- onUpdate(): MaybePromise> {
- if (!this.onRequestUpdate) throw new Error("Requests for updates are not allowed");
- const timestamp = Date.now();
-
- if (this.#lastData) {
- if (this.#lastData instanceof Promise) return this.#lastData;
- if (timestamp - this.#lastData.timestamp <= this.updateThrottle) return this.#lastData;
- }
-
- const value = this.onRequestUpdate();
- let res: MaybePromise>;
- if (value instanceof Promise)
- res = value.then(
- (value): Readonly> => {
- let res = { data: value, timestamp: timestamp } as const;
- this.pushCache({ data: value, timestamp });
- this.#lastData = res;
- return res;
- },
- (e) => {
- this.#lastData = undefined;
- throw e;
- },
- );
- else {
- res = { data: value, timestamp: timestamp };
- this.pushCache({ data: value, timestamp });
- }
- this.#lastData = res;
- return res;
- }
- #center?: ChartCenter;
- /** @override */
- updateData(data: T, timeName?: string): void {
- if (!this.#center) return;
- let internalData = typeof data === "object" ? deepClone(data) : data;
-
- const timestamp = Date.now();
- this.pushCache({ data: internalData, timestamp, timeName });
-
- const writeData: ChartUpdateData = { data: internalData, timestamp: timestamp, timeAxisName: timeName };
- this.#center.ctrl.writeChart(this.id, writeData);
- }
- updateSubData(updateData: DimensionalityReduction, coord: number, opts?: ChartUpdateLowerOption): void;
- updateSubData(updateData: IntersectingDimension, coord: (number | undefined)[], opts?: ChartUpdateOption): void;
- /** @override */
- updateSubData(
- updateData: IntersectingDimension,
- coord: number | (number | undefined)[],
- opts?: ChartUpdateLowerOption | ChartUpdateOption,
- ): void {
- if (!this.#center) return;
- const timestamp = Date.now();
- this.#center.ctrl.writeChart(this.id, {
- data: updateData,
- coord: coord as any,
- timeAxisName: opts?.timeName,
- timestamp: timestamp,
- } satisfies ChartUpdateSubData);
- if (this.maxCacheSize <= 0) return;
- //@ts-ignore
- super.updateSubData(updateData, coord, opts);
- }
- get disposed() {
- return !this.#center;
- }
- dispose() {
- if (!this.#center) return;
- const center = this.#center;
- this.#center = undefined;
- const id = this.id;
- center.#instanceMap.delete(id);
- center.ctrl.deleteChart(id);
- }
- };
-}
-
-type RpcVioChart = InstanceType<(typeof ChartCenter)["Chart"]>;
-
-export type {
- ChartInfo,
- DimensionalityReduction,
- IntersectingDimension,
- ChartMeta,
- VioChartMeta,
- VioChartType,
- RequestUpdateRes,
- ChartDataItem,
-} from "../api_type/chart.type.ts";
-
-/**
- * VioChart 构造函数的选项
- * @public
- * @category Chart
- */
-export type CenterCreateChartOption = ChartCreateOption & {
- /** 主动请求更新的回调函数 */
- onRequestUpdate?(): MaybePromise;
- /** 请求更新节流。单位毫秒 */
- updateThrottle?: number;
-};
diff --git a/vio/src/vio/classes/mod.ts b/vio/src/vio/classes/mod.ts
deleted file mode 100644
index dea1404..0000000
--- a/vio/src/vio/classes/mod.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-export * from "./vio_tty.ts";
-export * from "./chart.ts";
-export * from "./VioChart.ts";
-export * from "./tty.ts";
diff --git a/vio/src/vio/const.ts b/vio/src/vio/const.ts
new file mode 100644
index 0000000..44783a2
--- /dev/null
+++ b/vio/src/vio/const.ts
@@ -0,0 +1,5 @@
+export class InstanceDisposedError extends Error {
+ constructor(name: string = "Instance") {
+ super(`${name} has been disposed`);
+ }
+}
diff --git a/vio/src/vio/mod.ts b/vio/src/vio/mod.ts
index 3c5498e..3f00412 100644
--- a/vio/src/vio/mod.ts
+++ b/vio/src/vio/mod.ts
@@ -1,16 +1,8 @@
import { Vio, createVio } from "./vio.ts";
export * from "./vio.ts";
-export type {
- ChartCreateOption,
- ChartUpdateLowerOption,
- ChartUpdateOption,
- VioChart,
- VioChartCreateConfig,
-} from "./classes/VioChart.ts";
-export * from "./classes/chart.ts";
-export * from "./classes/vio_tty.ts";
-export type { TTY, TTyWriteTextOption, TtyReadFileOption } from "./classes/tty.ts";
+export * from "./vio_object/mod.ts";
+export * from "./tty/mod.ts";
/**
* @public
*/
diff --git a/vio/src/vio/classes/vio_tty.ts b/vio/src/vio/tty/TtyCenter.ts
similarity index 96%
rename from vio/src/vio/classes/vio_tty.ts
rename to vio/src/vio/tty/TtyCenter.ts
index 83a9a58..90d6f95 100644
--- a/vio/src/vio/classes/vio_tty.ts
+++ b/vio/src/vio/tty/TtyCenter.ts
@@ -1,11 +1,12 @@
import type { TtyInputsReq, TtyOutputsData, VioClientExposed } from "../api_type.ts";
import { LinkedQueue, UniqueKeyMap } from "evlib/data_struct";
-import { CacheTty, TTY } from "./tty.ts";
-import { InstanceDisposedError } from "../../const.ts";
+import { TTY } from "./_TTY.ts";
import { withPromise, WithPromise } from "evlib";
+import { CacheTty } from "./_CacheTty.ts";
+import { InstanceDisposedError } from "../const.ts";
type TtyWriterFn = (ttyId: number, data: TtyOutputsData) => void;
-type TtyReadFn = VioClientExposed["sendTtyReadRequest"];
+type TtyReadFn = VioClientExposed["tty"]["sendTtyReadRequest"];
/**
* @public
@@ -283,12 +284,3 @@ export interface TtyReadResolver {
/** 读取中的数量 */
waitingSize: number;
}
-
-export type {
- EncodedImageData,
- RawImageData,
- VioFileData as FileData,
- SelectItem,
- SelectKey,
- TtyWriteTextType,
-} from "../api_type/tty.type.ts";
diff --git a/vio/src/vio/tty/_CacheTty.ts b/vio/src/vio/tty/_CacheTty.ts
new file mode 100644
index 0000000..ae32296
--- /dev/null
+++ b/vio/src/vio/tty/_CacheTty.ts
@@ -0,0 +1,46 @@
+import { LinkedCacheQueue } from "evlib/data_struct";
+import { TtyOutputsData } from "./tty.dto.ts";
+import { TTY } from "./_TTY.ts";
+
+type WriteTty = (ttyId: number, data: TtyOutputsData) => void;
+export abstract class CacheTty extends TTY {
+ constructor(
+ readonly ttyIndex: number,
+ cacheSize: number,
+ writeTty?: WriteTty,
+ ) {
+ super();
+ this.#writeTty = writeTty;
+ this.#outputCache = new LinkedCacheQueue(cacheSize);
+ }
+ #writeTty?: WriteTty;
+ #outputCache: LinkedCacheQueue<{ data: TtyOutputsData }>;
+ get cachedSize() {
+ return this.#outputCache.size;
+ }
+ get cacheSize() {
+ return this.#outputCache.maxSize;
+ }
+ set cacheSize(size: number) {
+ this.#outputCache.maxSize = size;
+ }
+ *getCache(): Generator {
+ for (const item of this.#outputCache) {
+ yield item.data;
+ }
+ }
+
+ /** @implements */
+ write(data: TtyOutputsData): void {
+ this.#outputCache.push({ data });
+ if (!this.#writeTty) return;
+ this.#writeTty(this.ttyIndex, data);
+ }
+ get disposed() {
+ return !this.#writeTty;
+ }
+ dispose() {
+ // dispose 后保留 cache
+ this.#writeTty = undefined;
+ }
+}
diff --git a/vio/src/vio/classes/tty.ts b/vio/src/vio/tty/_TTY.ts
similarity index 77%
rename from vio/src/vio/classes/tty.ts
rename to vio/src/vio/tty/_TTY.ts
index 148fcf2..74bec25 100644
--- a/vio/src/vio/classes/tty.ts
+++ b/vio/src/vio/tty/_TTY.ts
@@ -1,17 +1,13 @@
-import { LinkedCacheQueue } from "evlib/data_struct";
+import type { TtyOutputData, TtyInputsReq, TtyInputReq, TtyOutputsData } from "./tty.dto.ts";
import type {
SelectItem,
SelectKey,
- TtyOutputData,
- TtyInputsReq,
- TtyInputReq,
- TtyOutputsData,
EncodedImageData,
RawImageData,
TtyWriteTextType,
VioFileData,
-} from "../api_type/tty.type.ts";
-import { VioChart, VioChartImpl } from "./VioChart.ts";
+} from "./type.ts";
+import type { VioObject } from "../vio_object/_object_base.type.ts";
import { createTypeErrorDesc } from "evlib";
/**
@@ -81,8 +77,8 @@ export abstract class TTY {
msgType,
} satisfies TtyOutputData.Text);
}
- writeUiLink(ui: VioChart): void {
- if (!(ui instanceof VioChartImpl)) throw new Error("Unsupported UI object");
+ writeUiLink(ui: VioObject): void {
+ throw new Error("Unsupported UI object");
this.write({ type: "link", uiType: "chart", id: ui.id } satisfies TtyOutputData.UILink);
}
/** 读取任意数据 */
@@ -150,49 +146,6 @@ export abstract class TTY {
}
}
-type WriteTty = (ttyId: number, data: TtyOutputsData) => void;
-export abstract class CacheTty extends TTY {
- constructor(
- readonly ttyIndex: number,
- cacheSize: number,
- writeTty?: WriteTty,
- ) {
- super();
- this.#writeTty = writeTty;
- this.#outputCache = new LinkedCacheQueue(cacheSize);
- }
- #writeTty?: WriteTty;
- #outputCache: LinkedCacheQueue<{ data: TtyOutputsData }>;
- get cachedSize() {
- return this.#outputCache.size;
- }
- get cacheSize() {
- return this.#outputCache.maxSize;
- }
- set cacheSize(size: number) {
- this.#outputCache.maxSize = size;
- }
- *getCache(): Generator {
- for (const item of this.#outputCache) {
- yield item.data;
- }
- }
-
- /** @implements */
- write(data: TtyOutputsData): void {
- this.#outputCache.push({ data });
- if (!this.#writeTty) return;
- this.#writeTty(this.ttyIndex, data);
- }
- get disposed() {
- return !this.#writeTty;
- }
- dispose() {
- // dispose 后保留 cache
- this.#writeTty = undefined;
- }
-}
-
class InvalidInputDataError extends Error {
constructor(msg: string = "invalid input data") {
super(msg);
diff --git a/vio/src/vio/tty/_server_exposed.ts b/vio/src/vio/tty/_server_exposed.ts
new file mode 100644
index 0000000..0d08ff0
--- /dev/null
+++ b/vio/src/vio/tty/_server_exposed.ts
@@ -0,0 +1,58 @@
+import { ClientTtyExposed, ServerTtyExposed, TtyInputsReq, TtyOutputsData } from "./tty.dto.ts";
+import { TtyCenter, TtyReadResolver } from "./TtyCenter.ts";
+
+export class RpcServerTtyExposed implements ServerTtyExposed {
+ constructor(ttyCenter: TtyCenter, clientApi: ClientTtyExposed) {
+ this.#tty = ttyCenter;
+ this.#clientApi = clientApi;
+ }
+ #clientApi: ClientTtyExposed;
+ #tty: TtyCenter;
+ getTtyCache(id: number): TtyOutputsData[] {
+ const tty = this.#tty.getCreated(id);
+ if (!tty) return [];
+ return Array.from(tty.getCache());
+ }
+ resolveTtyReadRequest(ttyId: number, requestId: number, res: any): boolean {
+ const hd = this.#resolverMap[ttyId];
+ if (!hd) return false;
+ return hd.resolve(requestId, res);
+ }
+ rejectTtyReadRequest(ttyId: number, requestId: number, reason: any): boolean {
+ const hd = this.#resolverMap[ttyId];
+ if (!hd) return false;
+ return hd.reject(requestId, reason);
+ }
+ inputTty(ttyId: number, data: any): boolean {
+ const resolver = this.#resolverMap[ttyId];
+ if (!resolver) return false;
+ return resolver.input(data);
+ }
+ /** 某个连接中开启读取权的 tty 字典 */
+ #resolverMap: Record = {};
+ setTtyReadEnable(ttyId: number, enable: boolean): boolean {
+ let resolver = this.#resolverMap[ttyId];
+ if (enable) {
+ if (resolver) return true;
+ else {
+ resolver = this.#tty.setReader(ttyId, {
+ read: (ttyId: number, requestId: number, data: TtyInputsReq) =>
+ this.#clientApi.sendTtyReadRequest(ttyId, requestId, data),
+ dispose: () => {
+ if (this.#resolverMap[ttyId]) {
+ delete this.#resolverMap[ttyId];
+ this.#clientApi.ttyReadEnableChange(ttyId, false);
+ }
+ },
+ });
+ }
+ this.#resolverMap[ttyId] = resolver;
+ } else {
+ if (resolver) {
+ delete this.#resolverMap[ttyId]; // 主动关闭,dispose 之前删除,
+ resolver.dispose();
+ }
+ }
+ return true;
+ }
+}
diff --git a/vio/src/vio/tty/mod.private.ts b/vio/src/vio/tty/mod.private.ts
new file mode 100644
index 0000000..39ab6e3
--- /dev/null
+++ b/vio/src/vio/tty/mod.private.ts
@@ -0,0 +1,5 @@
+export * from "./_TTY.ts";
+export * from "./TtyCenter.ts";
+export * from "./type.ts";
+
+export * from "./_server_exposed.ts";
diff --git a/vio/src/vio/tty/mod.ts b/vio/src/vio/tty/mod.ts
new file mode 100644
index 0000000..bc5bf1a
--- /dev/null
+++ b/vio/src/vio/tty/mod.ts
@@ -0,0 +1,3 @@
+export * from "./_TTY.ts";
+export * from "./TtyCenter.ts";
+export * from "./type.ts";
diff --git a/vio/src/vio/api_type/tty.type.ts b/vio/src/vio/tty/tty.dto.ts
similarity index 63%
rename from vio/src/vio/api_type/tty.type.ts
rename to vio/src/vio/tty/tty.dto.ts
index 09f940b..41172bb 100644
--- a/vio/src/vio/api_type/tty.type.ts
+++ b/vio/src/vio/tty/tty.dto.ts
@@ -1,8 +1,5 @@
-/**
- * @public
- * @category TTY
- */
-export type TtyWriteTextType = "warn" | "log" | "error" | "info";
+import { MaybePromise } from "../../type.ts";
+import { EncodedImageData, RawImageData, SelectItem, TtyWriteTextType, VioFileData } from "./type.ts";
/** 终端输出数据 */
export namespace TtyOutputData {
@@ -74,42 +71,6 @@ export namespace TtyInputReq {
}
}
-/**
- * @public
- * @category TTY
- */
-export type RawImageData = {
- /** 图像宽度 */
- width: number;
- /** 图像高度 */
- height: number;
- /**
- * 图像二进制数据,必须满足 data.length === width * height * channel。
- * 数据由多个 width*height 长度的通道数据组成
- */
- data: Uint8Array;
- /** 图像通道数 */
- channel: number;
- separate?: boolean;
-};
-/**
- * 编码的图像数据,如 jpg、png等
- * @public
- * @category TTY
- */
-export type EncodedImageData = {
- data: Uint8Array;
- mime: string;
-};
-/**
- * @public
- * @category TTY
- */
-export type VioFileData = {
- name: string;
- data: Uint8Array;
- mime: string;
-};
export type TtyOutputsData =
| TtyOutputData.Text
| TtyOutputData.Table
@@ -123,13 +84,24 @@ export type TtyInputsReq =
| TtyInputReq.File
| TtyInputReq.Select
| TtyInputReq.Custom;
-/**
- * @public
- * @category TTY
- */
-export type SelectKey = string | number;
-/**
- * @public
- * @category TTY
- */
-export type SelectItem = { value: T; label?: string };
+
+export interface ServerTtyExposed {
+ /** 获取 TTY 输出缓存日志 */
+ getTtyCache(ttyId: number): MaybePromise