@tachybase/plugin-online-user 0.23.58 → 1.0.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- (function(n,e){typeof exports=="object"&&typeof module!="undefined"?e(exports,require("@tachybase/client"),require("@tachybase/schema"),require("react/jsx-runtime"),require("react"),require("antd"),require("lodash")):typeof define=="function"&&define.amd?define(["exports","@tachybase/client","@tachybase/schema","react/jsx-runtime","react","antd","lodash"],e):(n=typeof globalThis!="undefined"?globalThis:n||self,e(n["@tachybase/plugin-online-user"]={},n["@tachybase/client"],n["@tachybase/schema"],n.jsxRuntime,n.react,n.antd,n.lodash))})(this,function(n,e,t,o,r,d,h){"use strict";var q=Object.defineProperty;var T=Object.getOwnPropertySymbols;var w=Object.prototype.hasOwnProperty,A=Object.prototype.propertyIsEnumerable;var b=(n,e,t)=>e in n?q(n,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):n[e]=t,k=(n,e)=>{for(var t in e||(e={}))w.call(e,t)&&b(n,t,e[t]);if(T)for(var t of T(e))A.call(e,t)&&b(n,t,e[t]);return n};var l=(n,e,t)=>new Promise((o,r)=>{var d=s=>{try{a(t.next(s))}catch(u){r(u)}},h=s=>{try{a(t.throw(s))}catch(u){r(u)}},a=s=>s.done?o(s.value):Promise.resolve(s.value).then(d,h);a((t=t.apply(n,e)).next())});const a="online-user",s=()=>{const{i18n:i}=e.useApp();return{t:(f,y={})=>i.t(f,k({ns:a},y))}},u=()=>{const i=e.useApp(),[c,f]=r.useState([]),y=e.useAPIClient(),{token:S}=e.useToken(),{t:U}=s();return r.useEffect(()=>{i.ws.on("message",m=>{var O;const p=JSON.parse(m.data);if((p==null?void 0:p.type)==="plugin-online-user"){const j=(O=p.payload.users)==null?void 0:O.map(P=>{if(P)return{key:t.uid(),label:P.nickname}});f(j)}})},[i]),r.useEffect(()=>{const m={type:"plugin-online-user:client",payload:{token:y.auth.getToken()}};i.ws.send(JSON.stringify(m))},[]),o.jsx(d.Dropdown,{menu:{items:c},children:o.jsx(d.Button,{style:{width:"auto",color:S.colorTextHeaderMenu},type:"text",children:U("{{num}} people online",{num:h.size(c)})})})},x=i=>o.jsx(e.PinnedPluginListProvider,{items:{ou:{order:230,component:"OnlineUserManger",pin:!0,isPublic:!0,belongTo:"pinnedmenu"}},children:o.jsx(e.SchemaComponentOptions,{components:{OnlineUserManger:u},children:i.children})});class g extends e.Plugin{afterAdd(){return l(this,null,function*(){})}beforeLoad(){return l(this,null,function*(){})}load(){return l(this,null,function*(){this.app.use(x),t.autorun(()=>{if(this.app.ws.connected){const c={type:"plugin-online-user:client",payload:{token:this.app.apiClient.auth.getToken()}};this.app.ws.send(JSON.stringify(c))}})})}}n.PluginOnlineUserClient=g,n.default=g,Object.defineProperties(n,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})});
1
+ (function(n,e){typeof exports=="object"&&typeof module!="undefined"?e(exports,require("@tachybase/client"),require("@tachybase/schema"),require("react/jsx-runtime"),require("react"),require("antd"),require("lodash")):typeof define=="function"&&define.amd?define(["exports","@tachybase/client","@tachybase/schema","react/jsx-runtime","react","antd","lodash"],e):(n=typeof globalThis!="undefined"?globalThis:n||self,e(n["@tachybase/plugin-online-user"]={},n["@tachybase/client"],n["@tachybase/schema"],n.jsxRuntime,n.react,n.antd,n.lodash))})(this,function(n,e,t,r,u,p,f){"use strict";var w=Object.defineProperty;var T=Object.getOwnPropertySymbols;var A=Object.prototype.hasOwnProperty,C=Object.prototype.propertyIsEnumerable;var b=(n,e,t)=>e in n?w(n,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):n[e]=t,k=(n,e)=>{for(var t in e||(e={}))A.call(e,t)&&b(n,t,e[t]);if(T)for(var t of T(e))C.call(e,t)&&b(n,t,e[t]);return n};var h=(n,e,t)=>new Promise((r,u)=>{var p=s=>{try{a(t.next(s))}catch(c){u(c)}},f=s=>{try{a(t.throw(s))}catch(c){u(c)}},a=s=>s.done?r(s.value):Promise.resolve(s.value).then(p,f);a((t=t.apply(n,e)).next())});const a="online-user",s=()=>{const{i18n:i}=e.useApp();return{t:(y,m={})=>i.t(y,k({ns:a},m))}},c=()=>{const i=e.useApp(),[d,y]=u.useState([]),m=e.useAPIClient(),{token:U}=e.useToken(),{t:j}=s();return u.useEffect(()=>{i.ws.on("message",g=>{const l=JSON.parse(g.data);if((l==null?void 0:l.type)==="plugin-online-user"){const O=[];for(const o of l.payload.users){if(!o)continue;const x=O.find(q=>q.userId===o.id);if(x){x.count++;continue}O.push({userId:o.id,name:o.nickname||o.username,count:1})}y(O.map(o=>({key:t.uid(),label:`${o.name}:${o.count}`})))}})},[i]),u.useEffect(()=>{const g={type:"plugin-online-user:client",payload:{token:m.auth.getToken()}};i.ws.send(JSON.stringify(g))},[]),r.jsx(p.Dropdown,{menu:{items:d},children:r.jsx(p.Button,{style:{width:"auto",color:U.colorTextHeaderMenu},type:"text",children:j("{{num}} people online",{num:f.size(d)})})})},S=i=>r.jsx(e.PinnedPluginListProvider,{items:{ou:{order:230,component:"OnlineUserManger",pin:!0,isPublic:!0,belongTo:"pinnedmenu"}},children:r.jsx(e.SchemaComponentOptions,{components:{OnlineUserManger:c},children:i.children})});class P extends e.Plugin{afterAdd(){return h(this,null,function*(){})}beforeLoad(){return h(this,null,function*(){})}load(){return h(this,null,function*(){this.app.use(S),t.autorun(()=>{if(this.app.ws.connected){const d={type:"plugin-online-user:client",payload:{token:this.app.apiClient.auth.getToken()}};this.app.ws.send(JSON.stringify(d))}})})}}n.PluginOnlineUserClient=P,n.default=P,Object.defineProperties(n,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})});
@@ -1,10 +1,10 @@
1
1
  module.exports = {
2
2
  "react": "18.3.1",
3
- "@tachybase/client": "0.23.58",
4
- "@tachybase/schema": "0.23.58",
3
+ "@tachybase/client": "1.0.18",
4
+ "@tachybase/schema": "1.0.18",
5
5
  "antd": "5.22.5",
6
6
  "lodash": "4.17.21",
7
- "@tachybase/server": "0.23.58",
8
- "@tachybase/utils": "0.23.58",
7
+ "@tachybase/server": "1.0.18",
8
+ "@tachybase/utils": "1.0.18",
9
9
  "jsonwebtoken": "8.5.1"
10
10
  };
@@ -1 +1 @@
1
- {"name":"redis","description":"A modern, high performance Redis client","version":"4.7.0","license":"MIT","main":"./dist/index.js","types":"./dist/index.d.ts","files":["dist/"],"workspaces":["./packages/*"],"scripts":{"test":"npm run test -ws --if-present","build:client":"npm run build -w ./packages/client","build:test-utils":"npm run build -w ./packages/test-utils","build:tests-tools":"npm run build:client && npm run build:test-utils","build:modules":"find ./packages -mindepth 1 -maxdepth 1 -type d ! -name 'client' ! -name 'test-utils' -exec npm run build -w {} \\;","build":"tsc","build-all":"npm run build:client && npm run build:test-utils && npm run build:modules && npm run build","documentation":"npm run documentation -ws --if-present","gh-pages":"gh-pages -d ./documentation -e ./documentation -u 'documentation-bot <documentation@bot>'"},"dependencies":{"@redis/bloom":"1.2.0","@redis/client":"1.6.0","@redis/graph":"1.1.1","@redis/json":"1.0.7","@redis/search":"1.2.0","@redis/time-series":"1.1.0"},"devDependencies":{"@tsconfig/node14":"^14.1.0","gh-pages":"^6.0.0","release-it":"^16.1.5","typescript":"^5.2.2"},"repository":{"type":"git","url":"git://github.com/redis/node-redis.git"},"bugs":{"url":"https://github.com/redis/node-redis/issues"},"homepage":"https://github.com/redis/node-redis","keywords":["redis"],"_lastModified":"2025-03-06T10:02:38.276Z"}
1
+ {"name":"redis","description":"A modern, high performance Redis client","version":"4.7.0","license":"MIT","main":"./dist/index.js","types":"./dist/index.d.ts","files":["dist/"],"workspaces":["./packages/*"],"scripts":{"test":"npm run test -ws --if-present","build:client":"npm run build -w ./packages/client","build:test-utils":"npm run build -w ./packages/test-utils","build:tests-tools":"npm run build:client && npm run build:test-utils","build:modules":"find ./packages -mindepth 1 -maxdepth 1 -type d ! -name 'client' ! -name 'test-utils' -exec npm run build -w {} \\;","build":"tsc","build-all":"npm run build:client && npm run build:test-utils && npm run build:modules && npm run build","documentation":"npm run documentation -ws --if-present","gh-pages":"gh-pages -d ./documentation -e ./documentation -u 'documentation-bot <documentation@bot>'"},"dependencies":{"@redis/bloom":"1.2.0","@redis/client":"1.6.0","@redis/graph":"1.1.1","@redis/json":"1.0.7","@redis/search":"1.2.0","@redis/time-series":"1.1.0"},"devDependencies":{"@tsconfig/node14":"^14.1.0","gh-pages":"^6.0.0","release-it":"^16.1.5","typescript":"^5.2.2"},"repository":{"type":"git","url":"git://github.com/redis/node-redis.git"},"bugs":{"url":"https://github.com/redis/node-redis/issues"},"homepage":"https://github.com/redis/node-redis","keywords":["redis"],"_lastModified":"2025-04-18T20:37:29.030Z"}
@@ -1 +1 @@
1
- {"name":"ws","version":"8.18.0","description":"Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js","keywords":["HyBi","Push","RFC-6455","WebSocket","WebSockets","real-time"],"homepage":"https://github.com/websockets/ws","bugs":"https://github.com/websockets/ws/issues","repository":{"type":"git","url":"git+https://github.com/websockets/ws.git"},"author":"Einar Otto Stangvik <einaros@gmail.com> (http://2x.io)","license":"MIT","main":"index.js","exports":{".":{"browser":"./browser.js","import":"./wrapper.mjs","require":"./index.js"},"./package.json":"./package.json"},"browser":"browser.js","engines":{"node":">=10.0.0"},"files":["browser.js","index.js","lib/*.js","wrapper.mjs"],"scripts":{"test":"nyc --reporter=lcov --reporter=text mocha --throw-deprecation test/*.test.js","integration":"mocha --throw-deprecation test/*.integration.js","lint":"eslint . && prettier --check --ignore-path .gitignore \"**/*.{json,md,yaml,yml}\""},"peerDependencies":{"bufferutil":"^4.0.1","utf-8-validate":">=5.0.2"},"peerDependenciesMeta":{"bufferutil":{"optional":true},"utf-8-validate":{"optional":true}},"devDependencies":{"benchmark":"^2.1.4","bufferutil":"^4.0.1","eslint":"^9.0.0","eslint-config-prettier":"^9.0.0","eslint-plugin-prettier":"^5.0.0","globals":"^15.0.0","mocha":"^8.4.0","nyc":"^15.0.0","prettier":"^3.0.0","utf-8-validate":"^6.0.0"},"_lastModified":"2025-03-06T10:02:38.519Z"}
1
+ {"name":"ws","version":"8.18.0","description":"Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js","keywords":["HyBi","Push","RFC-6455","WebSocket","WebSockets","real-time"],"homepage":"https://github.com/websockets/ws","bugs":"https://github.com/websockets/ws/issues","repository":{"type":"git","url":"git+https://github.com/websockets/ws.git"},"author":"Einar Otto Stangvik <einaros@gmail.com> (http://2x.io)","license":"MIT","main":"index.js","exports":{".":{"browser":"./browser.js","import":"./wrapper.mjs","require":"./index.js"},"./package.json":"./package.json"},"browser":"browser.js","engines":{"node":">=10.0.0"},"files":["browser.js","index.js","lib/*.js","wrapper.mjs"],"scripts":{"test":"nyc --reporter=lcov --reporter=text mocha --throw-deprecation test/*.test.js","integration":"mocha --throw-deprecation test/*.integration.js","lint":"eslint . && prettier --check --ignore-path .gitignore \"**/*.{json,md,yaml,yml}\""},"peerDependencies":{"bufferutil":"^4.0.1","utf-8-validate":">=5.0.2"},"peerDependenciesMeta":{"bufferutil":{"optional":true},"utf-8-validate":{"optional":true}},"devDependencies":{"benchmark":"^2.1.4","bufferutil":"^4.0.1","eslint":"^9.0.0","eslint-config-prettier":"^9.0.0","eslint-plugin-prettier":"^5.0.0","globals":"^15.0.0","mocha":"^8.4.0","nyc":"^15.0.0","prettier":"^3.0.0","utf-8-validate":"^6.0.0"},"_lastModified":"2025-04-18T20:37:29.290Z"}
@@ -21,6 +21,7 @@ __export(plugin_exports, {
21
21
  default: () => plugin_default
22
22
  });
23
23
  module.exports = __toCommonJS(plugin_exports);
24
+ var import_node_worker_threads = require("node:worker_threads");
24
25
  var import_server = require("@tachybase/server");
25
26
  var import_utils = require("@tachybase/utils");
26
27
  var import_connection_manager = require("./services/connection-manager");
@@ -30,6 +31,9 @@ class PluginOnlineUserServer extends import_server.Plugin {
30
31
  async beforeLoad() {
31
32
  }
32
33
  async load() {
34
+ if (!import_node_worker_threads.isMainThread) {
35
+ return;
36
+ }
33
37
  await import_utils.Container.get(import_connection_manager.ConnectionManager).load();
34
38
  }
35
39
  async install() {
@@ -1,6 +1,15 @@
1
1
  export declare class ConnectionManager {
2
2
  private app;
3
+ private registeredHandlers;
3
4
  unload(): Promise<void>;
4
5
  load(): Promise<void>;
6
+ /**
7
+ * 取消注册所有 WebSocket 事件处理函数
8
+ */
9
+ private unregisterWSEventHandlers;
10
+ /**
11
+ * 注册 WebSocket 事件处理函数,并保存引用以便后续清理
12
+ */
13
+ private registerWSEventHandler;
5
14
  loadWsServer(): Promise<void>;
6
15
  }
@@ -2,7 +2,6 @@ var __create = Object.create;
2
2
  var __defProp = Object.defineProperty;
3
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __getProtoOf = Object.getPrototypeOf;
6
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
7
6
  var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : Symbol.for("Symbol." + name);
8
7
  var __typeError = (msg) => {
@@ -22,14 +21,6 @@ var __copyProps = (to, from, except, desc) => {
22
21
  }
23
22
  return to;
24
23
  };
25
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
26
- // If the importer is in node compatibility mode or this is not an ESM
27
- // file that has been converted to a CommonJS file using a Babel-
28
- // compatible transform (i.e. "__esModule" has not been set), then set
29
- // "default" to the CommonJS "module.exports" for node compatibility.
30
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
31
- mod
32
- ));
33
24
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
34
25
  var __decoratorStart = (base) => [, , , __create((base == null ? void 0 : base[__knownSymbol("metadata")]) ?? null)];
35
26
  var __decoratorStrings = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"];
@@ -76,7 +67,6 @@ __export(connection_manager_exports, {
76
67
  module.exports = __toCommonJS(connection_manager_exports);
77
68
  var import_server = require("@tachybase/server");
78
69
  var import_utils = require("@tachybase/utils");
79
- var import_jsonwebtoken = __toESM(require("jsonwebtoken"));
80
70
  var import_redis = require("redis");
81
71
  var _app_dec, _ConnectionManager_decorators, _init;
82
72
  const KEY_ONLINE_USERS = "online_users";
@@ -84,6 +74,8 @@ _ConnectionManager_decorators = [(0, import_utils.Service)()], _app_dec = [(0, i
84
74
  class ConnectionManager {
85
75
  constructor() {
86
76
  this.app = __runInitializers(_init, 8, this), __runInitializers(_init, 11, this);
77
+ // 存储注册的 WebSocket 事件处理函数,便于清理
78
+ this.registeredHandlers = /* @__PURE__ */ new Map();
87
79
  }
88
80
  async unload() {
89
81
  for (const client of [this.app.online.all, this.app.online.pub, this.app.online.sub]) {
@@ -107,6 +99,7 @@ class ConnectionManager {
107
99
  }
108
100
  this.app.on("afterStop", () => {
109
101
  this.unload();
102
+ this.unregisterWSEventHandlers();
110
103
  });
111
104
  if (this.app.online.all.isOpen) {
112
105
  return;
@@ -122,10 +115,33 @@ class ConnectionManager {
122
115
  }
123
116
  await this.loadWsServer();
124
117
  }
118
+ /**
119
+ * 取消注册所有 WebSocket 事件处理函数
120
+ */
121
+ unregisterWSEventHandlers() {
122
+ this.app.logger.info("Unregistering WebSocket event handlers for online-user plugin");
123
+ this.registeredHandlers.forEach((handler, eventType) => {
124
+ this.app.emit("ws:removeEventHandler", {
125
+ eventType,
126
+ handler
127
+ });
128
+ });
129
+ this.registeredHandlers.clear();
130
+ }
131
+ /**
132
+ * 注册 WebSocket 事件处理函数,并保存引用以便后续清理
133
+ */
134
+ registerWSEventHandler(eventType, handler) {
135
+ this.registeredHandlers.set(eventType, handler);
136
+ this.app.emit("ws:registerEventHandler", {
137
+ eventType,
138
+ handler
139
+ });
140
+ }
125
141
  async loadWsServer() {
126
142
  const appName = this.app.name;
127
143
  const gateway = import_server.Gateway.getInstance();
128
- const ws = gateway["wsServer"];
144
+ const wss = gateway["wsServer"];
129
145
  await this.app.online.sub.SUBSCRIBE(KEY_ONLINE_USERS + appName, async (num) => {
130
146
  if (num !== (0, import_utils.currentProcessNum)()) {
131
147
  await notifyAllClients(false);
@@ -133,13 +149,13 @@ class ConnectionManager {
133
149
  });
134
150
  const notifyAllClients = async (broadcast = true) => {
135
151
  const users = (await this.app.online.all.HVALS(KEY_ONLINE_USERS + appName)).map((u) => JSON.parse(u));
136
- ws.sendToConnectionsByTag("app", appName, {
152
+ wss.sendToConnectionsByTag("app", appName, {
137
153
  type: "plugin-online-user",
138
154
  payload: {
139
155
  users
140
156
  }
141
157
  });
142
- if (broadcast) {
158
+ if (broadcast && this.app.online.pub.isOpen) {
143
159
  await this.app.online.pub.PUBLISH(KEY_ONLINE_USERS + appName, (0, import_utils.currentProcessNum)());
144
160
  }
145
161
  };
@@ -150,31 +166,50 @@ class ConnectionManager {
150
166
  }
151
167
  });
152
168
  };
153
- ws == null ? void 0 : ws.wss.on("connection", (ws2) => {
154
- ws2.on("error", async () => {
155
- await notifyAllClients();
156
- });
157
- ws2.on("close", async () => {
158
- await this.app.online.all.HDEL(KEY_ONLINE_USERS + appName, ws2.id);
159
- await notifyAllClients();
160
- });
161
- ws2.on("message", async (data) => {
162
- if (data.toString() !== "ping") {
163
- const userMeg = JSON.parse(data.toString());
164
- if (userMeg.type === "plugin-online-user:client") {
165
- try {
166
- const analysis = import_jsonwebtoken.default.verify(userMeg.payload.token, process.env.APP_KEY);
167
- const userId = analysis.userId;
168
- const user = await getUserById(userId);
169
- await this.app.online.all.HSET(KEY_ONLINE_USERS + appName, ws2.id, JSON.stringify(user));
170
- await notifyAllClients();
171
- } catch (error) {
172
- console.warn(error.message);
173
- }
169
+ const closeHandler = async (ws) => {
170
+ var _a, _b, _c;
171
+ if (!((_c = (_b = (_a = this.app) == null ? void 0 : _a.online) == null ? void 0 : _b.all) == null ? void 0 : _c.isOpen)) {
172
+ return;
173
+ }
174
+ await this.app.online.all.HDEL(KEY_ONLINE_USERS + appName, ws.id);
175
+ await notifyAllClients();
176
+ };
177
+ const errorHandler = async () => {
178
+ await notifyAllClients();
179
+ };
180
+ const messageHandler = async (ws, data) => {
181
+ var _a, _b;
182
+ if (data.toString() !== "ping") {
183
+ const userMeg = JSON.parse(data.toString());
184
+ if (userMeg.type === "plugin-online-user:client") {
185
+ if (!userMeg.payload.token) {
186
+ return;
187
+ }
188
+ if (this.app.db.closed()) {
189
+ this.app.logger.warn("online user connect warn, db is closed");
190
+ return;
191
+ }
192
+ try {
193
+ const analysis = await ((_b = (_a = this.app.authManager) == null ? void 0 : _a.jwt) == null ? void 0 : _b.verifyToken(userMeg.payload.token));
194
+ const userId = analysis.userId;
195
+ const user = await getUserById(userId);
196
+ await this.app.online.all.HSET(KEY_ONLINE_USERS + appName, ws.id, JSON.stringify(user));
197
+ await notifyAllClients();
198
+ } catch (error) {
199
+ this.app.logger.warn("online user connect error", error);
174
200
  }
175
201
  }
176
- });
177
- });
202
+ }
203
+ };
204
+ this.registerWSEventHandler("message", messageHandler);
205
+ const connectionHandler = async (ws) => {
206
+ this.app.logger.debug(`New WebSocket connection established: ${ws.id}`);
207
+ };
208
+ this.unregisterWSEventHandlers();
209
+ this.registerWSEventHandler("close", closeHandler);
210
+ this.registerWSEventHandler("error", errorHandler);
211
+ this.registerWSEventHandler("message", messageHandler);
212
+ this.registerWSEventHandler("connection", connectionHandler);
178
213
  }
179
214
  }
180
215
  _init = __decoratorStart(null);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tachybase/plugin-online-user",
3
- "version": "0.23.58",
3
+ "version": "1.0.18",
4
4
  "main": "dist/server/index.js",
5
5
  "dependencies": {},
6
6
  "devDependencies": {
@@ -12,11 +12,11 @@
12
12
  "ws": "^8.18.0"
13
13
  },
14
14
  "peerDependencies": {
15
- "@tachybase/client": "0.23.58",
16
- "@tachybase/test": "0.23.58",
17
- "@tachybase/utils": "0.23.58",
18
- "@tachybase/server": "0.23.58",
19
- "@tachybase/schema": "0.23.58"
15
+ "@tachybase/client": "1.0.18",
16
+ "@tachybase/schema": "1.0.18",
17
+ "@tachybase/server": "1.0.18",
18
+ "@tachybase/test": "1.0.18",
19
+ "@tachybase/utils": "1.0.18"
20
20
  },
21
21
  "description.zh-CN": "在线用户管理(建设中)",
22
22
  "displayName.zh-CN": "在线用户管理(建设中)",