bianjiang 1 year ago
parent
commit
6d264e3eea

+ 6 - 1
.eslintrc.js

@@ -19,7 +19,12 @@ module.exports = {
     "@typescript-eslint/no-explicit-any": "off",
     "prettier/prettier": "off",
     "@typescript-eslint/no-var-requires": "off",
-    "@typescript-eslint/no-namespace": "off"
+    "@typescript-eslint/no-namespace": "off",
+    "@typescript-eslint/no-this-alias": "off",
+    "prefer-const": "off",
+    "@typescript-eslint/adjacent-overload-signatures":"off",
+    "no-empty": "off",
+    "no-fallthrough": "off"
   },
   overrides: [
     {

+ 1 - 0
package.json

@@ -51,6 +51,7 @@
     "queenjs": "^1.0.0-beta.69",
     "queentree": "^0.1.86-nocheck",
     "rimraf": "^3.0.2",
+    "rxjs": "^7.8.1",
     "scp2": "^0.5.0",
     "swiper": "^8.4.4",
     "three": "^0.146.0",

+ 24 - 0
src/comm/controllers/appMsgCtrl.ts

@@ -0,0 +1,24 @@
+/**
+ *
+ * 当前应用queen5接受的消息处理。
+ *   目前文件依赖 设计为 当前文件 依赖于modules/ediotor/controllers下的文件,这样避免share页面打包
+ *   当前模块,导致nats.ws文件被引用,引发 部分手机 share页面打不开的bug
+ */
+
+import { Controller } from "../core/controller";
+import { useCtx } from "../ctx";
+import { queenApi } from "queenjs";
+
+export class AppMsgController extends Controller {
+  constructor() {
+    super();
+    this.init();
+  }
+  init() {
+    console.log("controller init");
+  }
+
+  onReady() {
+    console.log("here can call other controller");
+  }
+}

+ 83 - 0
src/comm/controllers/appMsgRecvCtrl.ts

@@ -0,0 +1,83 @@
+/**
+ * 负责app之间的消息的接收模块
+ */
+import { nanoid } from "nanoid";
+import { Controller } from "../core/controller";
+import { useCtx } from "../ctx";
+
+export type AssetSendedCallback = (assetUri:string)=>Promise<boolean>;
+export type AssetType = "empty" | "image"
+const RevcChangeEvent = "app.recv.change"
+class AssetListener {
+    id = "";
+    type = "empty" as AssetType;
+    constructor(public actionName:string, public callback: AssetSendedCallback){
+        this.id = actionName;
+    }
+    
+    toJson() {
+        return {
+            id: this.id,
+            type: this.type,
+            action: this.actionName,
+        }
+    }
+}
+
+
+export class AppMsgRecvController extends Controller {
+
+    listeners = [] as AssetListener[];
+    appGuid = "";
+
+    async onReady() {
+        const {deviceCtrl, natsCtrl} = useCtx();
+
+        this.appGuid = deviceCtrl.profile.appGuid;
+
+        natsCtrl.subscribe(`send.${this.appGuid}`, async (msg:{id:string,fromKey:string, uri:string,name?:string, thumbnail?:string})=>{
+            const listen = this.listeners.find(item=>item.id == msg.id)
+            if (!listen) return {
+                isOk: false,
+                error: "nolistener"
+            }
+            const ok = await listen.callback(msg.uri);
+            return JSON.stringify({ isOk: ok})
+        })
+
+        natsCtrl.subscribe(`recv.actions.${this.appGuid}`, async ()=>{
+            return JSON.stringify(this.listeners.map(item=>item.toJson()))
+        })
+    }
+
+
+    //清除所有监听者
+    clearListeners() {
+        this.listeners = [];
+    }
+
+    emitChange() {
+        const {natsCtrl} = useCtx();
+        natsCtrl.publish(RevcChangeEvent, JSON.stringify({Guid: this.appGuid}))
+    }
+
+    //添加图片监听者
+    addImageListener(actionName:string, handle: AssetSendedCallback) {
+        let listen = this.listeners.find(item=>item.actionName == actionName);
+        if (listen)  {
+            listen.callback = handle;
+            return listen.id;
+        }
+        listen = new AssetListener(actionName, handle)
+        listen.type = "image";
+        this.listeners.push(listen);
+    }
+
+    removeListener(id:string) {
+        let listen = this.listeners.find(item=>item.id == id);
+        if (!listen)  {
+            return;
+        }
+        this.listeners.splice(this.listeners.indexOf(listen), 1);
+    }
+}

+ 9 - 0
src/comm/controllers/appMsgSendCtrl.ts

@@ -0,0 +1,9 @@
+/**
+ * 应用间消息发送模块
+ */
+
+import { Controller } from "../core/controller";
+
+export class AppMsgSendController extends Controller {
+
+}

+ 25 - 0
src/comm/controllers/cmdsvcCtrl.ts

@@ -0,0 +1,25 @@
+import { queenApi } from "queenjs";
+import { connect, StringCodec, Empty, ErrorCode } from "nats.ws";
+import { Controller } from "../core/controller";
+import { useCtx } from "../ctx";
+import { reactive } from "vue";
+
+export class CmdSvcController extends Controller {
+  
+    state = reactive({
+        list: [] as {f:string,m:string}[]
+    })
+
+    async onReady() {
+      const {natsCtrl} = useCtx();
+      const params = new URLSearchParams(decodeURIComponent(location.search));
+      const guid = params.get("guid");
+
+      const state = this.state;
+      console.log("substring", `${guid}.cmdmsg`)
+      natsCtrl.subscribe(`${guid}.cmdmsg`, (msg:any)=>{
+        console.log("cmdMsg=>", msg);
+        state.list.push(msg);
+      }, {queue: "cmdsvc"})
+    }
+}

+ 608 - 0
src/comm/controllers/deviceCtrl.ts

@@ -0,0 +1,608 @@
+import { NormMsg, decodeNormMsg, encodeNormMsg } from "./entity/message";
+import { Controller } from "../core/controller";
+//@ts-ignore
+import { saveAs } from "file-saver";
+
+type UploadItem = {
+  id: string;
+  fpath: string;
+  dir: string;
+  name?: string;
+  url?: string;
+  size?: number;
+};
+
+export type FileInfo = {
+  Fpath: string;
+  Size: number;
+  CreateAt: number; //info.ModTime().Unix()
+};
+
+export class DeviceController extends Controller {
+  ipc: any;
+
+  profile = { wsPort: "", appGuid: "" };
+
+  isEnvOk = false;
+
+  constructor() {
+    super();
+
+    //@ts-ignore
+    if (window.ipc) {
+      //@ts-ignore
+      this.ipc = window.ipc;
+      this.isEnvOk = true;
+    } else {
+      this.ipc = {
+        emit(name: string, ...args: any[]) {
+          console.error("emit msg=>", name, args);
+        },
+        on(name: string) {
+          console.error("on message", name);
+        },
+      };
+    }
+    this.initEvent();
+  }
+  _conn?: WebSocket;
+
+  initEvent() {
+    const scope = this;
+    this.ipc.on("OnDragEnter", function (files: string[]) {
+      scope.emit("onDragEnter", files);
+    });
+    this.ipc.on("downloadsucc", (fpath: string, size: number) => {
+      scope.emit("downloadsucc", fpath, size);
+    });
+
+    this.ipc.emit("GetSocketUri", [], (uri: string) => {
+      let conn = new WebSocket(uri);
+      conn.onopen = (e) => {
+        console.log("bus socket connected!!!");
+      };
+      conn.onclose = (evt) => {
+        console.log("app bus socket conn closed", evt);
+
+        setTimeout(() => {
+          this._conn = new WebSocket(uri);
+        }, 1000);
+      };
+      conn.onmessage = function (evt) {
+        if (!evt.data || evt.data == "") return;
+
+        try {
+          const blob = evt.data as Blob;
+          blob.arrayBuffer().then((buff) => {
+            const msg = decodeNormMsg(new Uint8Array(buff));
+            scope.emit("on" + msg.sub, msg);
+          });
+        } catch (error) {
+          console.error(evt.data, typeof evt.data, error);
+        }
+      };
+      this._conn = conn;
+    });
+  }
+
+  async onReady() {
+    if (!this.isEnvOk) return;
+
+    this.profile = (await this.GetLocalAppProfile()) || {
+      wsPort: "",
+      appGuid: "",
+    };
+
+    console.log("current app profile=>", this.profile);
+  }
+
+  //选择磁盘文件夹
+  SelectDir(): Promise<string> {
+    const sid = Date.now();
+    const cbname = "c" + sid;
+    return new Promise((r) => {
+      const ipc = this.ipc;
+      ipc.emit("SelectDir", [sid], function (ok: boolean) {
+        console.log("call=>", ok);
+        if (!ok) {
+          r("");
+        }
+        ipc.on(cbname, function (dir: string) {
+          r(dir);
+        });
+      });
+    });
+  }
+  IsDirEmpty(dir: string): Promise<boolean> {
+    return new Promise((r) => {
+      const ipc = this.ipc;
+      ipc.emit("IsDirEmpty", [dir], function (ok: boolean) {
+        r(ok);
+      });
+    });
+  }
+  IsFileExit(fpath: string): Promise<boolean> {
+    return new Promise((r) => {
+      const ipc = this.ipc;
+      ipc.emit("IsFileExit", [fpath], function (ok: boolean) {
+        r(ok);
+      });
+    });
+  }
+
+  WriteFileText(dir: string, content: string): Promise<boolean> {
+    return new Promise((r) => {
+      const ipc = this.ipc;
+      ipc.emit("WriteFileText", [dir, content], function (ok: boolean) {
+        r(ok);
+      });
+    });
+  }
+
+  ReadFileText(dir: string): Promise<{ error: string; text: string }> {
+    return new Promise((r) => {
+      const ipc = this.ipc;
+      ipc.emit("ReadFileText", [dir], function (data: any) {
+        r(data);
+      });
+    });
+  }
+
+  //拷贝文件
+  CopyFile(srcFile: string, targetFile: string): Promise<boolean> {
+    const sid = Date.now();
+    const cbname = "copy" + sid;
+    return new Promise((r) => {
+      const ipc = this.ipc;
+      ipc.on(cbname, function (ok: boolean) {
+        r(ok);
+      });
+      ipc.emit("CopyFile", [cbname, srcFile, targetFile]);
+    });
+  }
+
+  //保存应用级的配置
+  SaveSysConfigItem(key: string, value: string): Promise<boolean> {
+    return new Promise((r) => {
+      const ipc = this.ipc;
+      ipc.emit("SaveAppConfigItem", [key, value], function (data: any) {
+        r(data);
+      });
+    });
+  }
+  GetSysConfigItem(key: string): Promise<{ error: string; text: string }> {
+    return new Promise((r) => {
+      const ipc = this.ipc;
+      ipc.emit("GetAppConfigItem", [key], function (data: any) {
+        r(data);
+      });
+    });
+  }
+  RemoveSysConfigItem(key: string): Promise<{ error: string; text: string }> {
+    return new Promise((r) => {
+      const ipc = this.ipc;
+      ipc.emit("RemoveSysConfigItem", [key], function (data: any) {
+        r(data);
+      });
+    });
+  }
+
+  //获取应用的baseUrl
+  GetSysDataBaseUrl() {
+    return new Promise((r) => {
+      const ipc = this.ipc;
+      ipc.emit("GetAppDataBaseUrl", [], function (data: any) {
+        r(data);
+      });
+    });
+  }
+
+  //获取应用的baseUrl
+  CopyFileToAppData(src: string, targetRelativePath: string): Promise<string> {
+    return new Promise((r) => {
+      const ipc = this.ipc;
+      ipc.emit(
+        "CopyFileToAppData",
+        [src, targetRelativePath],
+        function (data: any) {
+          r(data);
+        }
+      );
+    });
+  }
+
+  RemoveAppData(targetRelativePath: string): Promise<boolean> {
+    return new Promise((r) => {
+      const ipc = this.ipc;
+      ipc.emit("RemoveAppData", [targetRelativePath], function (data: any) {
+        r(data);
+      });
+    });
+  }
+
+  //打开文件所在的目录
+  OpenDir(dir: string): Promise<boolean> {
+    return new Promise((r) => {
+      const ipc = this.ipc;
+      ipc.emit("OpenDir", [dir], function (data: any) {
+        r(data);
+      });
+    });
+  }
+
+  SelectOneFilePath(title: string, filters: string): Promise<string> {
+    const sid = Date.now();
+
+    return new Promise((r) => {
+      const ipc = this.ipc;
+      const cbname = "c" + sid;
+      ipc.emit(
+        "SelectOneFilePath",
+        [sid, title, filters],
+        function (ok: boolean) {
+          if (!ok) {
+            r("");
+          }
+          ipc.on(cbname, function (dir: string) {
+            r(dir);
+          });
+        }
+      );
+    });
+  }
+
+  //开启projects 的httpserver
+  StartHttpServer(prjFile: string): Promise<string> {
+    const sid = Date.now();
+    return new Promise((r) => {
+      const ipc = this.ipc;
+      ipc.emit("StartProjectHttpServer", [prjFile], function (data: any) {
+        r(data);
+      });
+    });
+  }
+
+  //下在线文件 默认超时时间20分钟
+  DownloadFile(
+    url: string,
+    fpath: string,
+    timeoutSecend: number = 60 * 20,
+    cb?: (event: "start" | "progress" | "error" | "succ", p1: NormMsg) => void
+  ): Promise<boolean> {
+    const sid = "d" + Date.now();
+    return new Promise((r) => {
+      console.log(url, "==>", fpath);
+      const cancel = this.OnMsg(sid, (data) => {
+        cb && cb(data.type as any, data);
+        if (data.type == "succ") {
+          r(true);
+          cancel.unbind();
+          return;
+        }
+        if (data.type == "error") {
+          r(false);
+          cancel.unbind();
+        }
+      });
+
+      const ipc = this.ipc;
+      ipc.emit(
+        "DownloadFile",
+        [sid, fpath, url, timeoutSecend],
+        function (err: string) {
+          console.log("DownloadFile callback");
+          if (err) {
+            console.error(err);
+            r(false);
+          }
+        }
+      );
+    });
+  }
+
+  Unzip(
+    fpath: string,
+    distDir: string,
+    cb?: (event: "progress" | "error" | "succ", p1: NormMsg) => void
+  ) {
+    const sid = "unzip" + Date.now();
+
+    return new Promise((r) => {
+      const cancel = this.OnMsg(sid, (data) => {
+        cb && cb(data.type as any, data);
+        if (data.type == "succ") {
+          r(true);
+          cancel.unbind();
+          return;
+        }
+        if (data.type == "error") {
+          r(false);
+          cancel.unbind();
+        }
+      });
+      const ipc = this.ipc;
+      ipc.emit("Unzip", [sid, fpath, distDir]);
+    });
+  }
+
+  //获取文件夹的大小
+  //文件目录不要太大,不然会卡死程序
+  GetDirSize(dir: string): Promise<number> {
+    return new Promise((r) => {
+      const ipc = this.ipc;
+      ipc.emit("GetDirSize", [dir], function (size: number) {
+        r(size);
+      });
+    });
+  }
+
+  //获取系统数据目录
+  GetAppDataDir(): Promise<string> {
+    return new Promise((r) => {
+      const ipc = this.ipc;
+      ipc.emit("GetAppDataDir", [], function (dir: string) {
+        r(dir);
+      });
+    });
+  }
+
+  OpenAssetWindow(projectPath: string) {
+    return new Promise((r) => {
+      const ipc = this.ipc;
+      ipc.emit(
+        "openAssetsWindow",
+        [
+          `${location.host}/library.html?path=${projectPath}`,
+          "资源管理库",
+          1280,
+          960,
+        ],
+        function (ok: boolean) {
+          r(ok);
+        }
+      );
+    });
+  }
+
+  SendMsg(subject: string, msg: string) {
+    const conn = this._conn;
+    if (!conn) {
+      console.error("应用socket异常");
+      return;
+    }
+    conn.send(encodeNormMsg({ sub: subject, msg: msg }));
+  }
+
+  OnMsg(subject: string, cb: (msg: NormMsg) => any) {
+    return this.on("on" + subject, cb);
+  }
+
+  //同步阻塞当前进程
+  UploadSync(files: UploadItem[]): Promise<UploadItem[]> {
+    return new Promise((r) => {
+      const ipc = this.ipc;
+      ipc.emit("UploadSync", [files], function (ret: any) {
+        r(ret);
+      });
+    });
+  }
+
+  //异步上传不会阻塞当前进程
+  UploadASync(files: UploadItem[]): Promise<UploadItem[]> {
+    const sid = "u" + Date.now();
+    return new Promise((r) => {
+      const ipc = this.ipc;
+      ipc.emit("UploadASync", [sid, files], function (ok: boolean) {
+        if (!ok) {
+          r([]);
+          return;
+        }
+        ipc.on(sid, (items: any) => {
+          r(items);
+        });
+      });
+    });
+  }
+
+  //异步上传不会阻塞当前进程
+  UploadDir(dir: string, targetDir: string): Promise<UploadItem[]> {
+    const sid = "u" + Date.now();
+    return new Promise((r) => {
+      const ipc = this.ipc;
+      ipc.emit("UploadDir", [sid, dir, targetDir], function (ok: any) {
+        if (!ok) {
+          r([]);
+          return;
+        }
+        ipc.on(sid, (items: any) => {
+          r(items);
+        });
+      });
+    });
+  }
+
+  GetFilesInDir(dir: string, filter: string): Promise<FileInfo[]> {
+    return new Promise((r) => {
+      const ipc = this.ipc;
+      ipc.emit("GetFilesInDir", [dir, filter], function (ret: any) {
+        r(ret);
+      });
+    });
+  }
+
+  GetSubDirNames(dir: string): Promise<string[]> {
+    return new Promise((r) => {
+      const ipc = this.ipc;
+      ipc.emit("GetSubDirNames", [dir], function (ret: any) {
+        r(ret);
+      });
+    });
+  }
+
+  RemoveFile(fpath: string): Promise<boolean> {
+    return new Promise((r) => {
+      const ipc = this.ipc;
+      ipc.emit("RemoveFile", [fpath], function (ok: boolean) {
+        r(ok);
+      });
+    });
+  }
+
+  GetSaveFile(title: string, filter = "图片(*.png;*.jpg)"): Promise<string> {
+    const sid = "sf" + Date.now();
+    return new Promise((r) => {
+      const ipc = this.ipc;
+      ipc.emit("GetSaveFile", [sid, title, filter], function (ok: boolean) {
+        if (ok) {
+          ipc.on(sid, (ret: any) => {
+            r(ret);
+          });
+          return;
+        }
+        r("");
+      });
+    });
+  }
+
+  OpenOneFile(title: string, filter = "图片(*.png;*.jpg)"): Promise<string> {
+    const sid = "sf" + Date.now();
+    return new Promise((r) => {
+      const ipc = this.ipc;
+      ipc.emit("OpenOneFile", [sid, title, filter], function (file: any) {
+        setTimeout(() => {
+          r(file);
+        }, 0);
+      });
+    });
+  }
+
+  GetImageMeta(
+    fpath: string
+  ): Promise<{ Width: number; Height: number; Size: number }> {
+    return new Promise((r) => {
+      const img = new Image();
+      img.onload = () => {
+        r({ Width: img.width, Height: img.height, Size: 0 });
+      };
+      img.src = fpath;
+
+      // const ipc = this.ipc;
+      // ipc.emit("GetImageMeta", [fpath], function (meta) {
+      //     r(meta);
+      // });
+    });
+  }
+
+  SetMainTitle(title: string): Promise<boolean> {
+    return new Promise((r) => {
+      const ipc = this.ipc;
+      ipc.emit("SetMainTitle", [title], function (ok: any) {
+        r(ok);
+      });
+    });
+  }
+
+  SaveFile(fpath: string, buff: any): Promise<string> {
+    return new Promise((r) => {
+      const ipc = this.ipc;
+      ipc.emit("SaveFile", [fpath, Array.from(buff)], function (err: string) {
+        r(err);
+      });
+    });
+  }
+
+  OpenQueen5(url: string, title: any): Promise<boolean> {
+    return new Promise((r) => {
+      const ipc = this.ipc;
+      ipc.emit("OpenQueen5", [url, title], function (err: any) {
+        r(err);
+      });
+    });
+  }
+  OpenQueen5Play(url: string, title: any): Promise<boolean> {
+    return new Promise((r) => {
+      const ipc = this.ipc;
+      ipc.emit("OpenQueen5Play", [url, title], function (err: any) {
+        r(err);
+      });
+    });
+  }
+
+  SaveClipboard(blob: Blob) {
+    return navigator.clipboard.write([
+      new ClipboardItem({
+        "image/png": blob,
+      }),
+    ]);
+  }
+
+  GetNatsProfile(): Promise<{ apiPort: string; wsPort: string; ip: string }> {
+    return new Promise((r) => {
+      const ipc = this.ipc;
+      ipc.emit("NatsProfile", [], function (data: any) {
+        r(data);
+      });
+    });
+  }
+
+  GetLocalAppProfile(): Promise<{ wsPort: string; appGuid: string }> {
+    return new Promise((r) => {
+      const ipc = this.ipc;
+      ipc.emit("LocalAppProfile", [], function (data: any) {
+        r(data || { wsPort: "", appGuid: "" });
+      });
+    });
+  }
+
+  OpenWeb(
+    url: string,
+    title: string,
+    width = 1280,
+    height = 720
+  ): Promise<boolean> {
+    return new Promise((r) => {
+      const ipc = this.ipc;
+      ipc.emit("OpenWeb", [url, title, width, height], function (data: any) {
+        r(data);
+      });
+    });
+  }
+
+  RunNativeApp(guid: string, entry: string, params: string[]): Promise<string> {
+    return new Promise((r) => {
+      const ipc = this.ipc;
+      ipc.emit("RunNativeApp", [guid, entry, params], function (err: string) {
+        console.log("xxxx", err);
+        r(err);
+      });
+    });
+  }
+
+  RunCmdSvcApp(
+    guid: string,
+    webUrl: string,
+    entry: string,
+    params: string[]
+  ): Promise<string> {
+    return new Promise((r) => {
+      const ipc = this.ipc;
+      ipc.emit(
+        "RunCmdSvcApp",
+        [guid, webUrl, entry, params],
+        function (err: string) {
+          console.log("xxxx", err);
+          r(err);
+        }
+      );
+    });
+  }
+
+  StopNativeApp(guid: string): Promise<boolean> {
+    return new Promise((r) => {
+      const ipc = this.ipc;
+      ipc.emit("StopNativeApp", [guid], function (ok: boolean) {
+        r(ok);
+      });
+    });
+  }
+}

+ 637 - 0
src/comm/controllers/entity/message.ts

@@ -0,0 +1,637 @@
+export interface NormMsg {
+  sub?: string;
+  msg?: string;
+  error?: string;
+  type?: string;
+  fva11?: number;
+  fva12?: number;
+  iva11?: number;
+  iva12?: number;
+}
+
+export function encodeNormMsg(message: NormMsg): Uint8Array {
+  let bb = popByteBuffer();
+  _encodeNormMsg(message, bb);
+  return toUint8Array(bb);
+}
+
+function _encodeNormMsg(message: NormMsg, bb: ByteBuffer): void {
+  // optional string sub = 1;
+  let $sub = message.sub;
+  if ($sub !== undefined) {
+    writeVarint32(bb, 10);
+    writeString(bb, $sub);
+  }
+
+  // optional string msg = 2;
+  let $msg = message.msg;
+  if ($msg !== undefined) {
+    writeVarint32(bb, 18);
+    writeString(bb, $msg);
+  }
+
+  // optional string error = 3;
+  let $error = message.error;
+  if ($error !== undefined) {
+    writeVarint32(bb, 26);
+    writeString(bb, $error);
+  }
+
+  // optional string type = 4;
+  let $type = message.type;
+  if ($type !== undefined) {
+    writeVarint32(bb, 34);
+    writeString(bb, $type);
+  }
+
+  // optional float fva11 = 5;
+  let $fva11 = message.fva11;
+  if ($fva11 !== undefined) {
+    writeVarint32(bb, 45);
+    writeFloat(bb, $fva11);
+  }
+
+  // optional float fva12 = 6;
+  let $fva12 = message.fva12;
+  if ($fva12 !== undefined) {
+    writeVarint32(bb, 53);
+    writeFloat(bb, $fva12);
+  }
+
+  // optional int32 iva11 = 7;
+  let $iva11 = message.iva11;
+  if ($iva11 !== undefined) {
+    writeVarint32(bb, 56);
+    writeVarint64(bb, intToLong($iva11));
+  }
+
+  // optional int32 iva12 = 8;
+  let $iva12 = message.iva12;
+  if ($iva12 !== undefined) {
+    writeVarint32(bb, 64);
+    writeVarint64(bb, intToLong($iva12));
+  }
+}
+
+export function decodeNormMsg(binary: Uint8Array): NormMsg {
+  return _decodeNormMsg(wrapByteBuffer(binary));
+}
+
+function _decodeNormMsg(bb: ByteBuffer): NormMsg {
+  let message: NormMsg = {} as any;
+
+  end_of_message: while (!isAtEnd(bb)) {
+    let tag = readVarint32(bb);
+
+    switch (tag >>> 3) {
+      case 0:
+        break end_of_message;
+
+      // optional string sub = 1;
+      case 1: {
+        message.sub = readString(bb, readVarint32(bb));
+        break;
+      }
+
+      // optional string msg = 2;
+      case 2: {
+        message.msg = readString(bb, readVarint32(bb));
+        break;
+      }
+
+      // optional string error = 3;
+      case 3: {
+        message.error = readString(bb, readVarint32(bb));
+        break;
+      }
+
+      // optional string type = 4;
+      case 4: {
+        message.type = readString(bb, readVarint32(bb));
+        break;
+      }
+
+      // optional float fva11 = 5;
+      case 5: {
+        message.fva11 = readFloat(bb);
+        break;
+      }
+
+      // optional float fva12 = 6;
+      case 6: {
+        message.fva12 = readFloat(bb);
+        break;
+      }
+
+      // optional int32 iva11 = 7;
+      case 7: {
+        message.iva11 = readVarint32(bb);
+        break;
+      }
+
+      // optional int32 iva12 = 8;
+      case 8: {
+        message.iva12 = readVarint32(bb);
+        break;
+      }
+
+      default:
+        skipUnknownField(bb, tag & 7);
+    }
+  }
+
+  return message;
+}
+
+export interface Long {
+  low: number;
+  high: number;
+  unsigned: boolean;
+}
+
+interface ByteBuffer {
+  bytes: Uint8Array;
+  offset: number;
+  limit: number;
+}
+
+function pushTemporaryLength(bb: ByteBuffer): number {
+  let length = readVarint32(bb);
+  let limit = bb.limit;
+  bb.limit = bb.offset + length;
+  return limit;
+}
+
+function skipUnknownField(bb: ByteBuffer, type: number): void {
+  switch (type) {
+    case 0: while (readByte(bb) & 0x80) { } break;
+    case 2: skip(bb, readVarint32(bb)); break;
+    case 5: skip(bb, 4); break;
+    case 1: skip(bb, 8); break;
+    default: throw new Error("Unimplemented type: " + type);
+  }
+}
+
+function stringToLong(value: string): Long {
+  return {
+    low: value.charCodeAt(0) | (value.charCodeAt(1) << 16),
+    high: value.charCodeAt(2) | (value.charCodeAt(3) << 16),
+    unsigned: false,
+  };
+}
+
+function longToString(value: Long): string {
+  let low = value.low;
+  let high = value.high;
+  return String.fromCharCode(
+    low & 0xFFFF,
+    low >>> 16,
+    high & 0xFFFF,
+    high >>> 16);
+}
+
+// The code below was modified from https://github.com/protobufjs/bytebuffer.js
+// which is under the Apache License 2.0.
+
+let f32 = new Float32Array(1);
+let f32_u8 = new Uint8Array(f32.buffer);
+
+let f64 = new Float64Array(1);
+let f64_u8 = new Uint8Array(f64.buffer);
+
+function intToLong(value: number): Long {
+  value |= 0;
+  return {
+    low: value,
+    high: value >> 31,
+    unsigned: value >= 0,
+  };
+}
+
+let bbStack: ByteBuffer[] = [];
+
+function popByteBuffer(): ByteBuffer {
+  const bb = bbStack.pop();
+  if (!bb) return { bytes: new Uint8Array(64), offset: 0, limit: 0 };
+  bb.offset = bb.limit = 0;
+  return bb;
+}
+
+function pushByteBuffer(bb: ByteBuffer): void {
+  bbStack.push(bb);
+}
+
+function wrapByteBuffer(bytes: Uint8Array): ByteBuffer {
+  return { bytes, offset: 0, limit: bytes.length };
+}
+
+function toUint8Array(bb: ByteBuffer): Uint8Array {
+  let bytes = bb.bytes;
+  let limit = bb.limit;
+  return bytes.length === limit ? bytes : bytes.subarray(0, limit);
+}
+
+function skip(bb: ByteBuffer, offset: number): void {
+  if (bb.offset + offset > bb.limit) {
+    throw new Error('Skip past limit');
+  }
+  bb.offset += offset;
+}
+
+function isAtEnd(bb: ByteBuffer): boolean {
+  return bb.offset >= bb.limit;
+}
+
+function grow(bb: ByteBuffer, count: number): number {
+  let bytes = bb.bytes;
+  let offset = bb.offset;
+  let limit = bb.limit;
+  let finalOffset = offset + count;
+  if (finalOffset > bytes.length) {
+    let newBytes = new Uint8Array(finalOffset * 2);
+    newBytes.set(bytes);
+    bb.bytes = newBytes;
+  }
+  bb.offset = finalOffset;
+  if (finalOffset > limit) {
+    bb.limit = finalOffset;
+  }
+  return offset;
+}
+
+function advance(bb: ByteBuffer, count: number): number {
+  let offset = bb.offset;
+  if (offset + count > bb.limit) {
+    throw new Error('Read past limit');
+  }
+  bb.offset += count;
+  return offset;
+}
+
+function readBytes(bb: ByteBuffer, count: number): Uint8Array {
+  let offset = advance(bb, count);
+  return bb.bytes.subarray(offset, offset + count);
+}
+
+function writeBytes(bb: ByteBuffer, buffer: Uint8Array): void {
+  let offset = grow(bb, buffer.length);
+  bb.bytes.set(buffer, offset);
+}
+
+function readString(bb: ByteBuffer, count: number): string {
+  // Sadly a hand-coded UTF8 decoder is much faster than subarray+TextDecoder in V8
+  let offset = advance(bb, count);
+  let fromCharCode = String.fromCharCode;
+  let bytes = bb.bytes;
+  let invalid = '\uFFFD';
+  let text = '';
+
+  for (let i = 0; i < count; i++) {
+    let c1 = bytes[i + offset], c2: number, c3: number, c4: number, c: number;
+
+    // 1 byte
+    if ((c1 & 0x80) === 0) {
+      text += fromCharCode(c1);
+    }
+
+    // 2 bytes
+    else if ((c1 & 0xE0) === 0xC0) {
+      if (i + 1 >= count) text += invalid;
+      else {
+        c2 = bytes[i + offset + 1];
+        if ((c2 & 0xC0) !== 0x80) text += invalid;
+        else {
+          c = ((c1 & 0x1F) << 6) | (c2 & 0x3F);
+          if (c < 0x80) text += invalid;
+          else {
+            text += fromCharCode(c);
+            i++;
+          }
+        }
+      }
+    }
+
+    // 3 bytes
+    else if ((c1 & 0xF0) == 0xE0) {
+      if (i + 2 >= count) text += invalid;
+      else {
+        c2 = bytes[i + offset + 1];
+        c3 = bytes[i + offset + 2];
+        if (((c2 | (c3 << 8)) & 0xC0C0) !== 0x8080) text += invalid;
+        else {
+          c = ((c1 & 0x0F) << 12) | ((c2 & 0x3F) << 6) | (c3 & 0x3F);
+          if (c < 0x0800 || (c >= 0xD800 && c <= 0xDFFF)) text += invalid;
+          else {
+            text += fromCharCode(c);
+            i += 2;
+          }
+        }
+      }
+    }
+
+    // 4 bytes
+    else if ((c1 & 0xF8) == 0xF0) {
+      if (i + 3 >= count) text += invalid;
+      else {
+        c2 = bytes[i + offset + 1];
+        c3 = bytes[i + offset + 2];
+        c4 = bytes[i + offset + 3];
+        if (((c2 | (c3 << 8) | (c4 << 16)) & 0xC0C0C0) !== 0x808080) text += invalid;
+        else {
+          c = ((c1 & 0x07) << 0x12) | ((c2 & 0x3F) << 0x0C) | ((c3 & 0x3F) << 0x06) | (c4 & 0x3F);
+          if (c < 0x10000 || c > 0x10FFFF) text += invalid;
+          else {
+            c -= 0x10000;
+            text += fromCharCode((c >> 10) + 0xD800, (c & 0x3FF) + 0xDC00);
+            i += 3;
+          }
+        }
+      }
+    }
+
+    else text += invalid;
+  }
+
+  return text;
+}
+
+function writeString(bb: ByteBuffer, text: string): void {
+  // Sadly a hand-coded UTF8 encoder is much faster than TextEncoder+set in V8
+  let n = text.length;
+  let byteCount = 0;
+
+  // Write the byte count first
+  for (let i = 0; i < n; i++) {
+    let c = text.charCodeAt(i);
+    if (c >= 0xD800 && c <= 0xDBFF && i + 1 < n) {
+      c = (c << 10) + text.charCodeAt(++i) - 0x35FDC00;
+    }
+    byteCount += c < 0x80 ? 1 : c < 0x800 ? 2 : c < 0x10000 ? 3 : 4;
+  }
+  writeVarint32(bb, byteCount);
+
+  let offset = grow(bb, byteCount);
+  let bytes = bb.bytes;
+
+  // Then write the bytes
+  for (let i = 0; i < n; i++) {
+    let c = text.charCodeAt(i);
+    if (c >= 0xD800 && c <= 0xDBFF && i + 1 < n) {
+      c = (c << 10) + text.charCodeAt(++i) - 0x35FDC00;
+    }
+    if (c < 0x80) {
+      bytes[offset++] = c;
+    } else {
+      if (c < 0x800) {
+        bytes[offset++] = ((c >> 6) & 0x1F) | 0xC0;
+      } else {
+        if (c < 0x10000) {
+          bytes[offset++] = ((c >> 12) & 0x0F) | 0xE0;
+        } else {
+          bytes[offset++] = ((c >> 18) & 0x07) | 0xF0;
+          bytes[offset++] = ((c >> 12) & 0x3F) | 0x80;
+        }
+        bytes[offset++] = ((c >> 6) & 0x3F) | 0x80;
+      }
+      bytes[offset++] = (c & 0x3F) | 0x80;
+    }
+  }
+}
+
+function writeByteBuffer(bb: ByteBuffer, buffer: ByteBuffer): void {
+  let offset = grow(bb, buffer.limit);
+  let from = bb.bytes;
+  let to = buffer.bytes;
+
+  // This for loop is much faster than subarray+set on V8
+  for (let i = 0, n = buffer.limit; i < n; i++) {
+    from[i + offset] = to[i];
+  }
+}
+
+function readByte(bb: ByteBuffer): number {
+  return bb.bytes[advance(bb, 1)];
+}
+
+function writeByte(bb: ByteBuffer, value: number): void {
+  let offset = grow(bb, 1);
+  bb.bytes[offset] = value;
+}
+
+function readFloat(bb: ByteBuffer): number {
+  let offset = advance(bb, 4);
+  let bytes = bb.bytes;
+
+  // Manual copying is much faster than subarray+set in V8
+  f32_u8[0] = bytes[offset++];
+  f32_u8[1] = bytes[offset++];
+  f32_u8[2] = bytes[offset++];
+  f32_u8[3] = bytes[offset++];
+  return f32[0];
+}
+
+function writeFloat(bb: ByteBuffer, value: number): void {
+  let offset = grow(bb, 4);
+  let bytes = bb.bytes;
+  f32[0] = value;
+
+  // Manual copying is much faster than subarray+set in V8
+  bytes[offset++] = f32_u8[0];
+  bytes[offset++] = f32_u8[1];
+  bytes[offset++] = f32_u8[2];
+  bytes[offset++] = f32_u8[3];
+}
+
+function readDouble(bb: ByteBuffer): number {
+  let offset = advance(bb, 8);
+  let bytes = bb.bytes;
+
+  // Manual copying is much faster than subarray+set in V8
+  f64_u8[0] = bytes[offset++];
+  f64_u8[1] = bytes[offset++];
+  f64_u8[2] = bytes[offset++];
+  f64_u8[3] = bytes[offset++];
+  f64_u8[4] = bytes[offset++];
+  f64_u8[5] = bytes[offset++];
+  f64_u8[6] = bytes[offset++];
+  f64_u8[7] = bytes[offset++];
+  return f64[0];
+}
+
+function writeDouble(bb: ByteBuffer, value: number): void {
+  let offset = grow(bb, 8);
+  let bytes = bb.bytes;
+  f64[0] = value;
+
+  // Manual copying is much faster than subarray+set in V8
+  bytes[offset++] = f64_u8[0];
+  bytes[offset++] = f64_u8[1];
+  bytes[offset++] = f64_u8[2];
+  bytes[offset++] = f64_u8[3];
+  bytes[offset++] = f64_u8[4];
+  bytes[offset++] = f64_u8[5];
+  bytes[offset++] = f64_u8[6];
+  bytes[offset++] = f64_u8[7];
+}
+
+function readInt32(bb: ByteBuffer): number {
+  let offset = advance(bb, 4);
+  let bytes = bb.bytes;
+  return (
+    bytes[offset] |
+    (bytes[offset + 1] << 8) |
+    (bytes[offset + 2] << 16) |
+    (bytes[offset + 3] << 24)
+  );
+}
+
+function writeInt32(bb: ByteBuffer, value: number): void {
+  let offset = grow(bb, 4);
+  let bytes = bb.bytes;
+  bytes[offset] = value;
+  bytes[offset + 1] = value >> 8;
+  bytes[offset + 2] = value >> 16;
+  bytes[offset + 3] = value >> 24;
+}
+
+function readInt64(bb: ByteBuffer, unsigned: boolean): Long {
+  return {
+    low: readInt32(bb),
+    high: readInt32(bb),
+    unsigned,
+  };
+}
+
+function writeInt64(bb: ByteBuffer, value: Long): void {
+  writeInt32(bb, value.low);
+  writeInt32(bb, value.high);
+}
+
+function readVarint32(bb: ByteBuffer): number {
+  let c = 0;
+  let value = 0;
+  let b: number;
+  do {
+    b = readByte(bb);
+    if (c < 32) value |= (b & 0x7F) << c;
+    c += 7;
+  } while (b & 0x80);
+  return value;
+}
+
+function writeVarint32(bb: ByteBuffer, value: number): void {
+  value >>>= 0;
+  while (value >= 0x80) {
+    writeByte(bb, (value & 0x7f) | 0x80);
+    value >>>= 7;
+  }
+  writeByte(bb, value);
+}
+
+function readVarint64(bb: ByteBuffer, unsigned: boolean): Long {
+  let part0 = 0;
+  let part1 = 0;
+  let part2 = 0;
+  let b: number;
+
+  b = readByte(bb); part0 = (b & 0x7F); if (b & 0x80) {
+    b = readByte(bb); part0 |= (b & 0x7F) << 7; if (b & 0x80) {
+      b = readByte(bb); part0 |= (b & 0x7F) << 14; if (b & 0x80) {
+        b = readByte(bb); part0 |= (b & 0x7F) << 21; if (b & 0x80) {
+
+          b = readByte(bb); part1 = (b & 0x7F); if (b & 0x80) {
+            b = readByte(bb); part1 |= (b & 0x7F) << 7; if (b & 0x80) {
+              b = readByte(bb); part1 |= (b & 0x7F) << 14; if (b & 0x80) {
+                b = readByte(bb); part1 |= (b & 0x7F) << 21; if (b & 0x80) {
+
+                  b = readByte(bb); part2 = (b & 0x7F); if (b & 0x80) {
+                    b = readByte(bb); part2 |= (b & 0x7F) << 7;
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
+  return {
+    low: part0 | (part1 << 28),
+    high: (part1 >>> 4) | (part2 << 24),
+    unsigned,
+  };
+}
+
+function writeVarint64(bb: ByteBuffer, value: Long): void {
+  let part0 = value.low >>> 0;
+  let part1 = ((value.low >>> 28) | (value.high << 4)) >>> 0;
+  let part2 = value.high >>> 24;
+
+  // ref: src/google/protobuf/io/coded_stream.cc
+  let size =
+    part2 === 0 ?
+      part1 === 0 ?
+        part0 < 1 << 14 ?
+          part0 < 1 << 7 ? 1 : 2 :
+          part0 < 1 << 21 ? 3 : 4 :
+        part1 < 1 << 14 ?
+          part1 < 1 << 7 ? 5 : 6 :
+          part1 < 1 << 21 ? 7 : 8 :
+      part2 < 1 << 7 ? 9 : 10;
+
+  let offset = grow(bb, size);
+  let bytes = bb.bytes;
+
+  switch (size) {
+    case 10: bytes[offset + 9] = (part2 >>> 7) & 0x01;
+    case 9: bytes[offset + 8] = size !== 9 ? part2 | 0x80 : part2 & 0x7F;
+    case 8: bytes[offset + 7] = size !== 8 ? (part1 >>> 21) | 0x80 : (part1 >>> 21) & 0x7F;
+    case 7: bytes[offset + 6] = size !== 7 ? (part1 >>> 14) | 0x80 : (part1 >>> 14) & 0x7F;
+    case 6: bytes[offset + 5] = size !== 6 ? (part1 >>> 7) | 0x80 : (part1 >>> 7) & 0x7F;
+    case 5: bytes[offset + 4] = size !== 5 ? part1 | 0x80 : part1 & 0x7F;
+    case 4: bytes[offset + 3] = size !== 4 ? (part0 >>> 21) | 0x80 : (part0 >>> 21) & 0x7F;
+    case 3: bytes[offset + 2] = size !== 3 ? (part0 >>> 14) | 0x80 : (part0 >>> 14) & 0x7F;
+    case 2: bytes[offset + 1] = size !== 2 ? (part0 >>> 7) | 0x80 : (part0 >>> 7) & 0x7F;
+    case 1: bytes[offset] = size !== 1 ? part0 | 0x80 : part0 & 0x7F;
+  }
+}
+
+function readVarint32ZigZag(bb: ByteBuffer): number {
+  let value = readVarint32(bb);
+
+  // ref: src/google/protobuf/wire_format_lite.h
+  return (value >>> 1) ^ -(value & 1);
+}
+
+function writeVarint32ZigZag(bb: ByteBuffer, value: number): void {
+  // ref: src/google/protobuf/wire_format_lite.h
+  writeVarint32(bb, (value << 1) ^ (value >> 31));
+}
+
+function readVarint64ZigZag(bb: ByteBuffer): Long {
+  let value = readVarint64(bb, /* unsigned */ false);
+  let low = value.low;
+  let high = value.high;
+  let flip = -(low & 1);
+
+  // ref: src/google/protobuf/wire_format_lite.h
+  return {
+    low: ((low >>> 1) | (high << 31)) ^ flip,
+    high: (high >>> 1) ^ flip,
+    unsigned: false,
+  };
+}
+
+function writeVarint64ZigZag(bb: ByteBuffer, value: Long): void {
+  let low = value.low;
+  let high = value.high;
+  let flip = high >> 31;
+
+  // ref: src/google/protobuf/wire_format_lite.h
+  writeVarint64(bb, {
+    low: (low << 1) ^ flip,
+    high: ((high << 1) | (low >>> 31)) ^ flip,
+    unsigned: false,
+  });
+}

+ 13 - 0
src/comm/controllers/index.ts

@@ -0,0 +1,13 @@
+import { DeviceController } from "./deviceCtrl";
+import { NatsController } from "./natsCtrl";
+import { CmdSvcController } from "./cmdsvcCtrl";
+import { AppMsgRecvController } from "./appMsgRecvCtrl";
+import { AppMsgController } from "./appMsgCtrl";
+
+export {
+  DeviceController,
+  NatsController,
+  CmdSvcController,
+  AppMsgRecvController,
+  AppMsgController
+};

+ 60 - 41
src/controllers/natsController.ts → src/comm/controllers/natsCtrl.ts

@@ -1,24 +1,33 @@
-import { Empty, StringCodec, connect } from "nats.ws";
 import { queenApi } from "queenjs";
+import { connect, StringCodec, Empty, ErrorCode } from "nats.ws";
+import { Controller } from "../core/controller";
+import { useCtx } from "../ctx";
 
-export class BusController {
-  _params = new URLSearchParams(decodeURIComponent(location.search));
-
+export class NatsController extends Controller {
+ 
   _conn: any;
   _isConned = false;
   _startInit = false;
 
-  getQuery(name: string): string {
-    return this._params.get(name) as string;
+  async onReady() {
+      await this.initConn();
   }
 
-  async init(host?: string) {
+  async initConn() {
     if (this._startInit) return;
     this._startInit = true;
 
-    queenApi.showLoading("服务连接中...");
-    const wsHost = host ? host : this.getQuery("host");
+    const {deviceCtrl} = useCtx()
+
+    const profile = deviceCtrl.profile;
+
+    if (!profile.wsPort) return;
+    
+    
+    const wsHost = `nats://localhost:${profile.wsPort}`
+    console.log("ws host=>", wsHost);
     let ret = false;
+
     try {
       this._conn = await connect({ servers: wsHost });
       this._isConned = !!this._conn;
@@ -26,40 +35,39 @@ export class BusController {
       ret = true;
     } catch (error) {
       console.log(error);
-      queenApi.messageError("连接失败!");
     }
-    queenApi.hideLoading();
-    this._startInit = false;
 
+    this._startInit = false;
     return ret;
   }
+  
+  async GetConn() {
+    return this._conn;
+  }
 
-  async subscribe(subject: string, callback: any) {
-    if (!this._isConned) {
-      await this.init();
-    }
-
+  async subscribe(subject: string, callback: any, options:any = {}) {
+  
     if (!this._isConned) {
       console.error("建立连接失败");
       return;
     }
 
     const sc = StringCodec();
-    const sub = this._conn.subscribe(subject);
-    console.log(sub);
+    const sub = this._conn.subscribe(subject, options);
 
     (async () => {
       for await (const m of sub) {
-        console.log(sub);
-
         const ret = sc.decode(m.data);
         console.log(subject, "=>recieved");
         try {
           if (ret) {
             const msg = JSON.parse(ret);
-            callback(msg);
+            const out = await callback(msg);
+            m.respond(sc.encode(out))
+
           } else {
-            callback(ret);
+            const out = await callback(ret);
+            m.respond(sc.encode(out))
           }
         } catch (error) {
           console.log(subject, "=>recieved json parse eror", ret);
@@ -72,27 +80,38 @@ export class BusController {
       sub.unsubscribe();
     };
   }
+
   async requestApi(subject: string, data?: any, timeout?: number) {
-     const ret = await this.request(subject, data, timeout)
-     if (ret.error || ret.result.ErrorNo != 200 ) {
-       queenApi.messageError(ret.error || ret.result.ErrorDesc );
-       return
-     }
-     try {
-        const retJson = ret.result.Result;
-        if (!retJson) return;
-        if (retJson[0] != '{' && retJson[0] != '[') return retJson;
-        return JSON.parse(retJson)
-     } catch (error) {
-        console.log( ret );
-        console.error(error);
-     }
+    if (!this._isConned) return;
+
+    const ret = await this.request(subject, data, timeout);
+    console.log("request api=>", ret);
+
+    if (ret.error || (ret.result.ErrorNo && ret.result.ErrorNo != 200)) {
+      queenApi.messageError(ret.error || ret.result.ErrorDesc);
+      return;
+    }
+    try {
+      const retJson = ret.result.Result;
+      if (!retJson) return;
+      if (retJson[0] != "{" && retJson[0] != "[") return retJson;
+      return JSON.parse(retJson);
+    } catch (error) {
+      console.log(ret);
+      console.error(error);
+    }
   }
   
+  async publish(subject:string, value:string) {
+    
+    if (!this._isConned) {return}
+
+    const sc = StringCodec();
+
+    return await this._conn.publish(subject,  sc.encode(value));
+  }
+
   async request(subject: string, data?: any, timeout?: number) {
-    if (!this._isConned) {
-      await this.init();
-    }
 
     const ret: { error: string; result: any } = { error: "", result: null };
     if (!this._isConned) {
@@ -135,7 +154,7 @@ export class BusController {
       ret.error = error.message || "请求" + subject + "出错";
       if (ret.error == "503") {
         //NoResponders
-        ret.error = "网路异常,请重服务";
+        ret.error = "网路异常,请重服务";
       }
     }
     return ret;

+ 15 - 0
src/comm/core/controller.ts

@@ -0,0 +1,15 @@
+import { Events } from "queenjs";
+
+export class Controller extends Events {
+    constructor(){
+        super();
+        this.init();
+    }
+    init() {
+        console.log("controller init");
+    }
+    
+    onReady() {
+        console.log("here can call other controller");
+    }
+}

+ 98 - 0
src/comm/core/historyCtrl.ts

@@ -0,0 +1,98 @@
+import {reactive,  computed} from "vue"
+import { ValueSnap } from "./rxValue";
+
+export class HistoryController {
+  enable = false;
+  state = reactive({
+        currLen: 0, //操作栈的长度
+        maxLen: 100, //操作栈总长度
+        opIndex: -1, //操作栈的指针
+  });
+
+  refCanUndo = computed(() => {
+    return this.state.opIndex >= 0;
+  });
+  refCanRedo = computed(() => {
+    return this.state.opIndex < this.state.currLen - 1;
+  });
+
+  queues: Map<string, ValueSnap>[] = [];
+  cacheSnapValues = new Map<string , ValueSnap>();
+
+  changeCbs:((flag:number)=>void)[]  = [];
+
+  // 添加缓存记录
+  record(snap: ValueSnap) {
+    if ( !this.enable ) return;
+
+    const first = this.cacheSnapValues.get(snap.Id)
+    if (first) {
+        snap.OldValue = first.OldValue;
+    }
+    this.cacheSnapValues.set(snap.Id, snap);
+  }
+
+  // 保存缓存记录到历史栈中
+  submit(change:(flag:number)=>void=(flag)=>{console.log("default history changed ", flag)}) {
+    if (this.cacheSnapValues.size < 1  || !this.enable) return;
+
+    console.log("submiting history=>", this.cacheSnapValues.size);
+    const state = this.state;
+    const queue = this.queues;
+
+    // 将缓存操作记录保存到当前指针的下一栈中
+    const index = ++state.opIndex;
+    queue[index] = this.cacheSnapValues;
+    this.changeCbs[index] = change;
+
+    // 重置缓存记录
+    this.cacheSnapValues = new Map<string, ValueSnap>();
+
+    // 设置栈的长度为指针的长度,舍弃后面的记录
+    queue.length = state.opIndex + 1;
+    // 若栈长度超过上限, 舍弃之前的记录
+    if (queue.length > state.maxLen) {
+      queue.splice(0, queue.length - state.maxLen);
+      state.opIndex = state.maxLen - 1;
+    }
+    // 更新当前长度状态
+    state.currLen = queue.length;
+  }
+
+  undo() {
+    if (!this.refCanUndo.value || !this.enable ) return;
+
+    this.cacheSnapValues = new Map<string, ValueSnap>();
+    const index = this.state.opIndex--;
+    const snaps = this.queues[index]
+
+    snaps.forEach((vn)=>vn.undo())
+
+    const cb = this.changeCbs[index];
+  
+    cb && cb(1);
+  }
+
+  redo() {
+    if (!this.refCanRedo.value  || !this.enable) return;
+
+    this.cacheSnapValues = new Map<string, ValueSnap>();
+    const index = ++this.state.opIndex;
+    const snaps = this.queues[index];
+    snaps.forEach(vn=>vn.redo());
+
+    const cb = this.changeCbs[index];
+    cb && cb(2);
+  }
+
+  //清除操作
+  clear() {
+    if ( !this.enable ) return;
+
+    this.queues = [];
+    this.changeCbs = [];
+    this.state.currLen = 0;
+    this.state.opIndex = -1;
+    this.cacheSnapValues = new Map<string, ValueSnap>();
+  }
+}

+ 171 - 0
src/comm/core/rxValue.ts

@@ -0,0 +1,171 @@
+import { cloneDeep } from "lodash";
+import { HistoryController } from "./historyCtrl";
+import {BehaviorSubject, Subscription} from "rxjs";
+import { reactive, toRaw } from  "vue";
+
+export class ValueSnap {
+    Id:string;
+    Value: any;
+    OldValue: any;
+
+    Rx: BehaviorSubject<any>;
+    constructor(id:string, value:any, oldValue:any, rx: BehaviorSubject<any>) {
+        this.Id = id;
+        this.Value = value;
+        this.OldValue = oldValue;
+        this.Rx = rx
+    }
+    redo() {
+        this.Rx.next({value: this.Value, _hstry:false});
+    }
+    undo() {
+        this.Rx.next({value: this.OldValue, _hstry:false});
+    }
+
+    clone() {
+        return new ValueSnap(this.Id, this.Value,this.OldValue, this.Rx);
+    }
+}
+
+export type RxValueType<T> = {
+    value: T,
+    _hstry?: boolean
+}
+
+
+function createRxValue<T>(value: T, histry:boolean) {
+    return new BehaviorSubject< RxValueType<T> >({value:value,  _hstry: histry})
+}
+
+let _valueIndex = 0;
+export function createValueSnap(value:any, oldValue:any, rx:BehaviorSubject<any>) {
+  let i = _valueIndex + 1;
+  _valueIndex +=1;
+  return new ValueSnap(i+"", value, oldValue, rx);
+}
+
+class RxValue {
+   static create<T extends {[key:string]: any}>(_fields:T, histroy?: HistoryController ) {
+        let obj = {__rx:true} as any;
+        
+        obj._historySnap = {} as any;
+        obj._historySub = {} as any;
+        obj._rxs = {} as any;
+        obj._fields = _fields;
+        obj._history = histroy;
+        obj._refs = {} as any;
+
+        const names = Object.keys(_fields);
+        
+        names.forEach(name=>{
+
+
+               const currName = name;
+               const initValue = _fields[currName]
+
+               const isRxField = typeof initValue == "object" && initValue.__rx
+               if (isRxField) {
+                    obj[currName] = initValue;
+                    return;
+               }
+
+                const f = createRxValue(initValue, !!histroy);
+                obj._rxs[name] = f;
+
+                const snap = createValueSnap(initValue, initValue, f);
+                obj._historySnap[name] = snap;
+                const rxc =  reactive({value: initValue});
+
+                Object.defineProperty(obj, currName, {
+                    get: function(){
+                        return rxc.value;
+                    },
+                    set: function(v) {
+                        f.next({value: v});
+                    },
+                    configurable: true,
+                    enumerable: true
+                })
+
+                const CamName = currName.slice(0,1).toUpperCase() +currName.slice(1);
+                
+                obj["set"+CamName] = function(value:T, nohistory = false){
+                    f.next({value, _hstry: !nohistory});
+                }
+        
+                obj["on"+CamName + "Changed"] = function(subscribe: (value:T, oldValue:T)=>void){
+                    return f.subscribe((v:any)=>{
+                        //if (CamName == "Transform") console.log("history 2222222222222222222222222222")
+                            subscribe(v.value, snap.OldValue)
+                        }
+                    )
+                }
+                
+                obj._historySub[name] = f.subscribe((v:any)=>{
+                    //if (CamName == "Transform") console.log("history 11111111111111111111111111")
+                    snap.OldValue = rxc.value;
+                    rxc.value = v.value;
+                    if (obj._history && obj._history.enable) {
+                        if (!v._hstry) return;
+                        const s = snap.clone();
+                        s.Value = v.value;
+                        obj._history.record(s);
+                    }
+                })
+        });
+
+
+        obj["setHistory"] = function(h: HistoryController){
+            obj._history = h;
+        }
+        obj["toJson"] = function() {
+            const out:any = {};
+            const names = Object.keys(_fields);
+
+            names.forEach(name=>{
+                const initV = _fields[name]
+                const isRxField = typeof initV == "object" && initV.__rx
+                if (isRxField) {
+                    out[name] = obj[name].toJson();
+                    return;
+                }
+                out[name] =cloneDeep(toRaw(obj._rxs[name].getValue().value));
+            })
+            return out;
+        }
+        obj["fromJson"] = function(json:any) {
+            const out:any = {};
+            const names = Object.keys(_fields);
+            names.forEach(name=>{
+                const initV = _fields[name]
+                const isRxField = typeof initV == "object" && initV.__rx
+                if (isRxField ) {
+                    if(json[name]) out[name] = obj[name].fromJson( json[name] );
+                    return;
+                }
+                obj._rxs[name].next({value: json[name] != undefined ? json[name] : cloneDeep(initV) , _hstry: false})
+            })
+            return out;
+        }
+        
+        type setHistoryType = (history: HistoryController)=>void;
+        type toJSONType = ()=>typeof _fields;
+        type fromJsonType = (json:typeof _fields)=>void;
+
+        return obj as typeof _fields & {
+            [K in keyof typeof _fields as `set${Capitalize<string & K>}`]: (value: typeof _fields[K], nohistory?:boolean) => void;
+        } & {
+            [K in keyof typeof _fields as `on${Capitalize<string & K>}Changed`]: (subscribe: (value: typeof _fields[K], oldValue:typeof _fields[K])=>void) => Subscription;
+        } & 
+        // {
+        //     [K in keyof typeof _fields as `ref${Capitalize<string & K>}`]: () => typeof _fields[K];
+        // } &
+        {
+            setHistory: setHistoryType
+            toJson:toJSONType
+            fromJson:fromJsonType
+        }
+    }
+}
+
+export {RxValue};

+ 9 - 0
src/comm/ctx/config.ts

@@ -0,0 +1,9 @@
+import * as Controls from "../controllers";
+const ctx = {
+  deviceCtrl: new Controls.DeviceController(),
+  natsCtrl: new Controls.NatsController(),
+  recvCtrl: new Controls.AppMsgRecvController(),
+  msgCtrl:new Controls.AppMsgController(),
+}
+
+export {ctx};

+ 51 - 0
src/comm/ctx/index.ts

@@ -0,0 +1,51 @@
+import {ctx} from "./config";
+
+let initOrder =  ["deviceCtrl", "natsCtrl", "prjCtrl", "installCtrl", "runCtrl"];
+export async function SetInitOrder<K extends keyof typeof ctx>(ctrls: K[]) {
+    initOrder = ctrls;
+}
+
+let OnReadyCtrls = Object.keys(ctx);
+export async function SetOnReadyCtrls<K extends keyof typeof ctx>(ctrls: K[]) {
+    OnReadyCtrls = ctrls;
+}
+
+let excludeCtrls = [] as string[];
+export async function SetExcludeCtrls<K extends keyof typeof ctx>(ctrls: K[]) {
+    excludeCtrls = ctrls;
+}
+
+
+export async function InitControllers() {
+    console.log("begin init ");
+    let n = initOrder.length;
+    for(let i= 0; i<n; i++) {
+        //@ts-ignore
+        await ctx[initOrder[i]].onReady();
+    }
+    const keys = OnReadyCtrls.slice(0)
+    while(n--) {
+        if (initOrder.indexOf(keys[n]) != -1 ) {
+            keys.splice(n, 1);
+        }
+    }
+    
+    if (excludeCtrls.length > 0 ) {
+        n = keys.length;
+        while(n--) {
+            if (excludeCtrls.indexOf(keys[n]) != -1 ) {
+                keys.splice(n, 1);
+            }
+        }
+    }
+
+    keys.forEach(k=>{
+        //@ts-ignore
+        ctx[k].onReady();
+    })
+    console.log("end init");
+}
+
+export function useCtx() {
+    return ctx;
+}

+ 5 - 5
src/modules/list/actions/list.ts

@@ -2,11 +2,11 @@ import ListModule from "..";
 
 export default ListModule.action({
   async getProfileData() {
-    const data = await this.controls.bus.requestApi("queentree.local.profile");
-    console.log(data);
-    localStorage.setItem("token", JSON.stringify(data.Token));
-    this.store.baseURL = data.BaseUrl;
-    this.actions.initExpStorage(data.BaseUrl);
+    // const data = await this.controls.bus.requestApi("queentree.local.profile");
+    // console.log(data);
+    // localStorage.setItem("token", JSON.stringify(data.Token));
+    // this.store.baseURL = data.BaseUrl;
+    // this.actions.initExpStorage(data.BaseUrl);
   },
   async getAssetList(dbId: string, defineId: string) {
     const res = await this.https.loadAsset(dbId, defineId, this.store.baseURL);

+ 2 - 3
src/modules/list/actions/load.ts

@@ -1,7 +1,6 @@
 import ListModule from "..";
-import { ItemObject } from "../objects/item";
-import { DialogItem, ShowItem } from "../objects/item";
-const artifacts = "./Artifacts.json";
+import { ItemObject, ShowItem } from "../objects/item";
+
 const defaultImage = require("@/assets/default.png");
 function loadImg(url: string, id: string, name: string): Promise<ShowItem> {
   return new Promise((resolve, reject) => {

+ 2 - 2
src/modules/list/index.ts

@@ -3,7 +3,7 @@ import { ModuleRoot } from "queenjs";
 import actions from "./actions";
 import { https } from "./http";
 import { stores } from "./stores";
-import { BusController } from "@/controllers/natsController";
+import { NatsController } from "@/comm/controllers/natsCtrl";
 
 export default class ListModule extends ModuleRoot {
   config = this.setConfig({
@@ -15,7 +15,7 @@ export default class ListModule extends ModuleRoot {
   actions = this.createActions(actions);
   https = this.createHttps([https]);
   controls = {
-    bus: new BusController(),
+    // bus: new NatsController(),
     backendList: new PageListController(this.config.httpConfig),
   };
 

+ 1 - 1
src/modules/queen3d/index.ts

@@ -1,5 +1,5 @@
 import { Application } from "queen3d";
-import { ModuleRoot, RequestConfig } from "queenjs";
+import { ModuleRoot } from "queenjs";
 
 export default class Queen3dCtrl extends ModuleRoot {
   queen3d = new Application();

+ 7 - 0
yarn.lock

@@ -6640,6 +6640,13 @@ run-parallel@^1.1.9:
   dependencies:
     queue-microtask "^1.2.2"
 
+rxjs@^7.8.1:
+  version "7.8.1"
+  resolved "http://124.70.149.18:4873/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543"
+  integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==
+  dependencies:
+    tslib "^2.1.0"
+
 safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
   version "5.1.2"
   resolved "http://124.70.149.18:4873/safe-buffer/-/safe-buffer-5.1.2.tgz"