mpaas-jsapi-proxy 1.0.0 → 1.0.2

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.
package/README.md CHANGED
@@ -2,13 +2,6 @@
2
2
 
3
3
  mPaaS 小程序 JSAPI 代理服务器,提供 WebSocket 连接和设备管理功能。
4
4
 
5
- ## 安装
6
-
7
- ```bash
8
- npm install mpaas-jsapi-proxy
9
- # 或
10
- pnpm add mpaas-jsapi-proxy
11
- ```
12
5
 
13
6
  ## 功能特性
14
7
 
@@ -29,63 +22,6 @@ npm install -g mpaas-jsapi-proxy
29
22
  # 启动服务器
30
23
  mpaas-jsapi-proxy
31
24
  ```
32
-
33
- ### 作为模块使用
34
-
35
- ```javascript
36
- const { ProxyServer } = require('mpaas-jsapi-proxy');
37
-
38
- // 创建服务器实例
39
- const server = new ProxyServer({
40
- port: 7521,
41
- wsPath: '/ws',
42
- heartbeatInterval: 30000
43
- });
44
-
45
- // 启动服务器
46
- server.start();
47
-
48
- // 优雅退出
49
- process.on('SIGINT', () => {
50
- server.stop();
51
- process.exit(0);
52
- });
53
- ```
54
-
55
- ### 自定义配置
56
-
57
- ```javascript
58
- const { ProxyServer } = require('mpaas-jsapi-proxy');
59
-
60
- const server = new ProxyServer({
61
- port: 7521, // WebSocket 服务端口
62
- wsPath: '/websocket', // WebSocket 路径
63
- heartbeatInterval: 60000 // 心跳间隔(毫秒)
64
- });
65
-
66
- server.start();
67
- ```
68
-
69
- ## API
70
-
71
- ### ProxyServer
72
-
73
- #### 构造函数
74
-
75
- ```javascript
76
- new ProxyServer(config)
77
- ```
78
-
79
- **参数:**
80
- - `config.port` (number): WebSocket 服务端口,默认 3000
81
- - `config.wsPath` (string): WebSocket 路径,默认 '/ws'
82
- - `config.heartbeatInterval` (number): 心跳间隔(毫秒),默认 30000
83
-
84
- #### 方法
85
-
86
- - `start()`: 启动 WebSocket 服务器
87
- - `stop()`: 停止服务器并断开所有连接
88
-
89
25
  ## 开发
90
26
 
91
27
  ```bash
package/bin/index.js ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ require('../dist/index.js');
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- var u=Object.create,f=Object.defineProperty,I=Object.getOwnPropertyDescriptor,b=Object.getOwnPropertyNames,w=Object.getPrototypeOf,m=Object.prototype.hasOwnProperty,S=(e,t,r,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(var n=b(t),l=0,o=n.length,i;l<o;l++)i=n[l],!m.call(e,i)&&i!==r&&f(e,i,{get:(p=>t[p]).bind(null,i),enumerable:!(s=I(t,i))||s.enumerable});return e},g=(e,t,r)=>(r=e!=null?u(w(e)):{},S(t||!e||!e.__esModule?f(r,"default",{value:e,enumerable:!0}):r,e));let c=require("ws");c=g(c);let h=require("qrcode-terminal");h=g(h);let d=require("os");d=g(d);var a=class{static getLocalIP(){const e=d.default.networkInterfaces();for(const t of Object.keys(e))for(const r of e[t])if(r.family==="IPv4"&&!r.internal)return r.address;return"localhost"}static formatServerUrl(e,t,r){return`ws://${e}:${t}${r}`}static log(e,t="info"){const r=new Date().toLocaleTimeString("zh-CN");console.log(`[${r}] ${{info:"ℹ️",success:"✅",error:"❌",warn:"⚠️"}[t]||"ℹ️"} ${e}`)}},D=class{constructor(){this.devices=new Map}registerDevice(e,t,r){if(this.devices.has(e)){const s=this.devices.get(e);s.ws=t,s.deviceInfo=r,s.lastHeartbeat=Date.now(),a.log(`设备更新连接: ${e}`,"info")}else this.devices.set(e,{ws:t,simulatorWS:null,deviceInfo:r,lastHeartbeat:Date.now()}),a.log(`设备注册成功: ${e}`,"success")}disconnect(e){for(const[t,r]of this.devices)if(r.ws===e)return this.devices.delete(t),a.log(`设备断开连接: ${t}`,"warn"),!0;return!1}getDevice(e){return this.devices.get(e)}isDeviceReady(e){const t=this.devices.get(e);return!!t&&!!t.ws}updateHeartbeat(e){const t=this.devices.get(e);t&&(t.lastHeartbeat=Date.now())}getAllDevices(){return Array.from(this.devices.entries()).map(([e,t])=>({deviceId:e,hasMiniapp:!!t.ws,lastHeartbeat:t.lastHeartbeat}))}get devicesMap(){return this.devices}},_=class{constructor(e){this.httpServer=null,this.deviceManager=e,this.pendingCalls=new Map}setHttpServer(e){this.httpServer=e}handleMessage(e,t){const{type:r,msgId:s,deviceId:n}=e;if(!r||!s){this.sendError(t,s,"INVALID_MESSAGE","消息格式错误");return}switch(r){case"client_call":this.handleClientCall(e,t);break;case"register":this.handleRegister(e,t);break;case"response":this.handleResponse(e,t);break;case"heartbeat":this.handleHeartbeat(e);break;default:this.sendError(t,s,"UNKNOWN_MESSAGE_TYPE",`未知消息类型: ${r}`)}}handleRegister(e,t){const{msgId:r,deviceId:s,deviceInfo:n}=e;if(!s){this.sendError(t,r,"INVALID_DEVICE_ID","设备ID不能为空");return}this.deviceManager.registerDevice(s,t,n||{}),t.send(JSON.stringify({type:"register",msgId:r,deviceId:s,success:!0,timestamp:Date.now()}))}handleClientCall(e,t){const{msgId:r,deviceId:s,api:n,params:l}=e;if(!s){this.sendError(t,r,"INVALID_DEVICE_ID","设备ID不能为空");return}this.pendingCalls.set(r,{ws:t,deviceId:s,timestamp:Date.now()});const o=this.deviceManager.getDevice(s);if(!o||!o.ws){this.pendingCalls.delete(r),this.sendError(t,r,"DEVICE_OFFLINE","代理小程序未连接");return}try{o.ws.send(JSON.stringify(e)),a.log(`转发调用: ${n} → ${s} -> ${r}`,"info")}catch(i){this.pendingCalls.delete(r),this.sendError(t,r,"SEND_FAILED",`发送失败: ${i.message}`)}}handleResponse(e,t){const{msgId:r}=e,s=this.pendingCalls.get(r);if(!s){a.log(`未找到对应的请求: ${r}`,"warn");return}a.log(`转发响应 → msgId: ${r}`,"info");try{s.ws.send(JSON.stringify(e))}catch(n){a.log(`转发响应失败: ${n.message}`,"error")}finally{this.pendingCalls.delete(r)}}handleHeartbeat(e){const{deviceId:t}=e;t&&this.deviceManager.updateHeartbeat(t)}sendError(e,t,r,s){e&&e.readyState===1&&e.send(JSON.stringify({type:"error",msgId:t,timestamp:Date.now(),error:{code:r,message:s}}))}clearPendingCallsForWS(e){for(const[t,r]of this.pendingCalls)r.ws===e&&this.pendingCalls.delete(t)}},C=class{constructor(e){const t={port:3e3,wsPath:"/ws",heartbeatInterval:3e4};this.config={...t,...e},this.deviceManager=new D,this.messageRouter=new _(this.deviceManager),this.wss=null,this.heartbeatTimer=null}start(){this.wss=new c.default.Server({port:this.config.port,path:this.config.wsPath}),this.wss.on("connection",(e,t)=>{this.handleConnection(e,t)}),this.wss.on("error",e=>{a.log(`服务器错误: ${e.message}`,"error")}),this.startHeartbeatCheck(),a.log("代理服务器启动成功","success"),a.log(`监听端口: ${this.config.port}`,"info"),this.displayConnectionInfo()}handleConnection(e,t){const r=new URL(t.url,`http://${t.headers.host}`).searchParams.get("type");a.log(`新连接: ${r||"unknown"}`,"info"),e.on("message",s=>{try{const n=JSON.parse(s.toString());this.messageRouter.handleMessage(n,e)}catch(n){a.log(`消息解析失败: ${n.message}`,"error")}}),e.on("close",()=>{this.deviceManager.disconnect(e),this.messageRouter.clearPendingCallsForWS(e)}),e.on("error",s=>{a.log(`WebSocket错误: ${s.message}`,"error")})}displayConnectionInfo(){const e=a.getLocalIP(),t=a.formatServerUrl(e,this.config.port,"");console.log(`
1
+ var b=Object.create,v=Object.defineProperty,m=Object.getOwnPropertyDescriptor,w=Object.getOwnPropertyNames,S=Object.getPrototypeOf,D=Object.prototype.hasOwnProperty,_=(e,t,r,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(var n=w(t),l=0,o=n.length,i;l<o;l++)i=n[l],!D.call(e,i)&&i!==r&&v(e,i,{get:(I=>t[I]).bind(null,i),enumerable:!(s=m(t,i))||s.enumerable});return e},p=(e,t,r)=>(r=e!=null?b(S(e)):{},_(t||!e||!e.__esModule?v(r,"default",{value:e,enumerable:!0}):r,e));let C=require("ws"),h=require("qrcode-terminal");h=p(h);let d=require("os");d=p(d);let g=require("minimist");g=p(g);var a=class{static getLocalIP(){const e=d.default.networkInterfaces();for(const t of Object.keys(e))for(const r of e[t])if(r.family==="IPv4"&&!r.internal)return r.address;return"localhost"}static formatServerUrl(e,t,r){return`ws://${e}:${t}${r}`}static log(e,t="info"){const r=new Date().toLocaleTimeString("zh-CN");console.log(`[${r}] ${{info:"ℹ️",success:"✅",error:"❌",warn:"⚠️"}[t]||"ℹ️"} ${e}`)}},y=class{constructor(){this.devices=new Map}registerDevice(e,t,r){if(this.devices.has(e)){const s=this.devices.get(e);s.ws=t,s.deviceInfo=r,s.lastHeartbeat=Date.now(),a.log(`设备更新连接: ${e}`,"info")}else this.devices.set(e,{ws:t,simulatorWS:null,deviceInfo:r,lastHeartbeat:Date.now()}),a.log(`设备注册成功: ${e}`,"success")}disconnect(e){for(const[t,r]of this.devices)if(r.ws===e)return this.devices.delete(t),a.log(`设备断开连接: ${t}`,"warn"),!0;return!1}getDevice(e){return this.devices.get(e)}isDeviceReady(e){const t=this.devices.get(e);return!!t&&!!t.ws}updateHeartbeat(e){const t=this.devices.get(e);t&&(t.lastHeartbeat=Date.now())}getAllDevices(){return Array.from(this.devices.entries()).map(([e,t])=>({deviceId:e,hasMiniapp:!!t.ws,lastHeartbeat:t.lastHeartbeat}))}get devicesMap(){return this.devices}},P=class{constructor(e){this.httpServer=null,this.deviceManager=e,this.pendingCalls=new Map}setHttpServer(e){this.httpServer=e}handleMessage(e,t){const{type:r,msgId:s,deviceId:n}=e;if(!r||!s){this.sendError(t,s,"INVALID_MESSAGE","消息格式错误");return}switch(r){case"client_call":this.handleClientCall(e,t);break;case"register":this.handleRegister(e,t);break;case"response":this.handleResponse(e,t);break;case"heartbeat":this.handleHeartbeat(e);break;default:this.sendError(t,s,"UNKNOWN_MESSAGE_TYPE",`未知消息类型: ${r}`)}}handleRegister(e,t){const{msgId:r,deviceId:s,deviceInfo:n}=e;if(!s){this.sendError(t,r,"INVALID_DEVICE_ID","设备ID不能为空");return}this.deviceManager.registerDevice(s,t,n||{}),t.send(JSON.stringify({type:"register",msgId:r,deviceId:s,success:!0,timestamp:Date.now()}))}handleClientCall(e,t){const{msgId:r,deviceId:s,api:n,params:l}=e;if(!s){this.sendError(t,r,"INVALID_DEVICE_ID","设备ID不能为空");return}this.pendingCalls.set(r,{ws:t,deviceId:s,timestamp:Date.now()});const o=this.deviceManager.getDevice(s);if(!o||!o.ws){this.pendingCalls.delete(r),this.sendError(t,r,"DEVICE_OFFLINE","代理小程序未连接");return}try{o.ws.send(JSON.stringify(e)),a.log(`转发调用: ${n} → ${s} -> ${r}`,"info")}catch(i){this.pendingCalls.delete(r),this.sendError(t,r,"SEND_FAILED",`发送失败: ${i.message}`)}}handleResponse(e,t){const{msgId:r}=e,s=this.pendingCalls.get(r);if(!s){a.log(`未找到对应的请求: ${r}`,"warn");return}a.log(`转发响应 → msgId: ${r}`,"info");try{s.ws.send(JSON.stringify(e))}catch(n){a.log(`转发响应失败: ${n.message}`,"error")}finally{this.pendingCalls.delete(r)}}handleHeartbeat(e){const{deviceId:t}=e;t&&this.deviceManager.updateHeartbeat(t)}sendError(e,t,r,s){e&&e.readyState===1&&e.send(JSON.stringify({type:"error",msgId:t,timestamp:Date.now(),error:{code:r,message:s}}))}clearPendingCallsForWS(e){for(const[t,r]of this.pendingCalls)r.ws===e&&this.pendingCalls.delete(t)}},f={port:7521,wsPath:"/ws",heartbeatInterval:3e4,appId:"2026052614145188"},E=class{constructor(e){const t={port:3e3,wsPath:"/ws",heartbeatInterval:3e4};this.config={...t,...e},this.deviceManager=new y,this.messageRouter=new P(this.deviceManager),this.wss=null,this.heartbeatTimer=null}start(){this.wss=new C.WebSocketServer({port:this.config.port,path:this.config.wsPath}),this.wss.on("connection",(e,t)=>{this.handleConnection(e,t)}),this.wss.on("error",e=>{a.log(`服务器错误: ${e.message}`,"error")}),this.startHeartbeatCheck(),a.log("代理服务器启动成功","success"),a.log(`监听端口: ${this.config.port}`,"info"),this.displayConnectionInfo()}handleConnection(e,t){const r=new URL(t.url,`http://${t.headers.host}`).searchParams.get("type");a.log(`新连接: ${r||"unknown"}`,"info"),e.on("message",s=>{try{const n=JSON.parse(s.toString());this.messageRouter.handleMessage(n,e)}catch(n){a.log(`消息解析失败: ${n.message}`,"error")}}),e.on("close",()=>{this.deviceManager.disconnect(e),this.messageRouter.clearPendingCallsForWS(e)}),e.on("error",s=>{a.log(`WebSocket错误: ${s.message}`,"error")})}displayConnectionInfo(){const e=a.getLocalIP(),t=a.formatServerUrl(e,this.config.port,"");console.log(`
2
2
  ╔════════════════════════════════════════════════════════════╗
3
3
  ║ mPaaS JSAPI 代理服务器 ║
4
4
  ╠════════════════════════════════════════════════════════════╣
@@ -8,5 +8,5 @@ var u=Object.create,f=Object.defineProperty,I=Object.getOwnPropertyDescriptor,b=
8
8
  ║ ║
9
9
  ║ 或扫描下方二维码快速配置: ║
10
10
  ╚════════════════════════════════════════════════════════════╝
11
- `),console.log("");const r=`url=${encodeURIComponent(t)}`;h.default.generate(r,{small:!0})}startHeartbeatCheck(){this.heartbeatTimer=setInterval(()=>{const e=Date.now(),t=this.config.heartbeatInterval*2;for(const[r,s]of this.deviceManager.devicesMap)e-s.lastHeartbeat>t&&(a.log(`设备心跳超时: ${r}`,"warn"),s.ws&&s.ws.close(),s.simulatorWS&&s.simulatorWS.close(),this.deviceManager.devicesMap.delete(r))},this.config.heartbeatInterval)}stop(){this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null),this.wss&&(this.wss.close(),a.log("服务器已关闭","info"))}},y={port:7521,wsPath:"/ws",heartbeatInterval:3e4},v=new C(y);v.start();process.on("SIGINT",()=>{console.log(`
12
- 正在关闭服务器...`),v.stop(),process.exit(0)});
11
+ `),console.log("");const r=`xlmpaas://${f.appId}?url=${encodeURIComponent(t)}`;h.default.generate(r,{small:!0})}startHeartbeatCheck(){this.heartbeatTimer=setInterval(()=>{const e=Date.now(),t=this.config.heartbeatInterval*2;for(const[r,s]of this.deviceManager.devicesMap)e-s.lastHeartbeat>t&&(a.log(`设备心跳超时: ${r}`,"warn"),s.ws&&s.ws.close(),s.simulatorWS&&s.simulatorWS.close(),this.deviceManager.devicesMap.delete(r))},this.config.heartbeatInterval)}stop(){this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null),this.wss&&(this.wss.close(),a.log("服务器已关闭","info"))}},c=(0,g.default)(process.argv.slice(2)),u=new E({...f,...c.p!==void 0&&{port:Number(c.p)},...c.port!==void 0&&{port:Number(c.port)}});u.start();process.on("SIGINT",()=>{console.log(`
12
+ 正在关闭服务器...`),u.stop(),process.exit(0)});
package/package.json CHANGED
@@ -1,16 +1,19 @@
1
1
  {
2
2
  "name": "mpaas-jsapi-proxy",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "mPaaS JSAPI 代理服务器",
5
5
  "main": "dist/index.js",
6
+ "bin": {
7
+ "mpaas-jsapi-proxy": "bin/index.js"
8
+ },
6
9
  "files": [
10
+ "bin",
7
11
  "dist"
8
12
  ],
9
13
  "scripts": {
10
14
  "start": "node dist/index.js",
11
15
  "dev": "tsx watch src/index.ts",
12
16
  "build": "vite build",
13
- "prepublishOnly": "npm run build",
14
17
  "type-check": "tsc --noEmit"
15
18
  },
16
19
  "keywords": [
@@ -30,11 +33,14 @@
30
33
  },
31
34
  "homepage": "https://github.com/your-username/mpaas-jsapi-proxy#readme",
32
35
  "dependencies": {
36
+ "minimist": "^1.2.8",
33
37
  "qrcode-terminal": "^0.12.0",
34
38
  "ws": "^8.14.0"
35
39
  },
36
40
  "devDependencies": {
41
+ "@types/minimist": "^1.2.5",
37
42
  "@types/node": "^25.9.1",
43
+ "@types/qrcode-terminal": "^0.12.2",
38
44
  "@types/ws": "^8.18.1",
39
45
  "esbuild": "^0.28.0",
40
46
  "nodemon": "^3.0.0",