mpaas-jsapi-proxy 1.0.0
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/LICENSE +21 -0
- package/README.md +134 -0
- package/dist/index.js +12 -0
- package/package.json +51 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# mPaaS JSAPI Proxy Server
|
|
2
|
+
|
|
3
|
+
mPaaS 小程序 JSAPI 代理服务器,提供 WebSocket 连接和设备管理功能。
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install mpaas-jsapi-proxy
|
|
9
|
+
# 或
|
|
10
|
+
pnpm add mpaas-jsapi-proxy
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## 功能特性
|
|
14
|
+
|
|
15
|
+
- WebSocket 服务器,支持 JSAPI 消息代理
|
|
16
|
+
- 设备连接管理
|
|
17
|
+
- 消息路由和分发
|
|
18
|
+
- 心跳检测机制
|
|
19
|
+
- 二维码终端显示
|
|
20
|
+
|
|
21
|
+
## 快速开始
|
|
22
|
+
|
|
23
|
+
### 直接运行
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# 全局安装
|
|
27
|
+
npm install -g mpaas-jsapi-proxy
|
|
28
|
+
|
|
29
|
+
# 启动服务器
|
|
30
|
+
mpaas-jsapi-proxy
|
|
31
|
+
```
|
|
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
|
+
## 开发
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
# 克隆项目
|
|
93
|
+
git clone <repository-url>
|
|
94
|
+
|
|
95
|
+
# 安装依赖
|
|
96
|
+
pnpm install
|
|
97
|
+
|
|
98
|
+
# 开发模式(热重载)
|
|
99
|
+
pnpm run dev
|
|
100
|
+
|
|
101
|
+
# 构建
|
|
102
|
+
pnpm run build
|
|
103
|
+
|
|
104
|
+
# 类型检查
|
|
105
|
+
pnpm run type-check
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## 发布到 npm
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
# 构建
|
|
112
|
+
pnpm run build
|
|
113
|
+
|
|
114
|
+
# 预览包内容
|
|
115
|
+
npm pack --dry-run
|
|
116
|
+
|
|
117
|
+
# 发布
|
|
118
|
+
npm publish
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## 技术栈
|
|
122
|
+
|
|
123
|
+
- **TypeScript** - 类型安全的开发体验
|
|
124
|
+
- **Vite** - 快速的构建工具
|
|
125
|
+
- **WebSocket** - 实时双向通信
|
|
126
|
+
- **qrcode-terminal** - 终端二维码显示
|
|
127
|
+
|
|
128
|
+
## 许可证
|
|
129
|
+
|
|
130
|
+
MIT
|
|
131
|
+
|
|
132
|
+
## 贡献
|
|
133
|
+
|
|
134
|
+
欢迎提交 Issue 和 Pull Request!
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
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(`
|
|
2
|
+
╔════════════════════════════════════════════════════════════╗
|
|
3
|
+
║ mPaaS JSAPI 代理服务器 ║
|
|
4
|
+
╠════════════════════════════════════════════════════════════╣
|
|
5
|
+
║ 请在代理小程序中输入以下地址进行注册 ║
|
|
6
|
+
║ ║
|
|
7
|
+
║ ${t.padEnd(58)}║
|
|
8
|
+
║ ║
|
|
9
|
+
║ 或扫描下方二维码快速配置: ║
|
|
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)});
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mpaas-jsapi-proxy",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "mPaaS JSAPI 代理服务器",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"files": [
|
|
7
|
+
"dist"
|
|
8
|
+
],
|
|
9
|
+
"scripts": {
|
|
10
|
+
"start": "node dist/index.js",
|
|
11
|
+
"dev": "tsx watch src/index.ts",
|
|
12
|
+
"build": "vite build",
|
|
13
|
+
"prepublishOnly": "npm run build",
|
|
14
|
+
"type-check": "tsc --noEmit"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"mpaas",
|
|
18
|
+
"proxy",
|
|
19
|
+
"websocket",
|
|
20
|
+
"jsapi"
|
|
21
|
+
],
|
|
22
|
+
"author": "",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "https://github.com/your-username/mpaas-jsapi-proxy.git"
|
|
27
|
+
},
|
|
28
|
+
"bugs": {
|
|
29
|
+
"url": "https://github.com/your-username/mpaas-jsapi-proxy/issues"
|
|
30
|
+
},
|
|
31
|
+
"homepage": "https://github.com/your-username/mpaas-jsapi-proxy#readme",
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"qrcode-terminal": "^0.12.0",
|
|
34
|
+
"ws": "^8.14.0"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/node": "^25.9.1",
|
|
38
|
+
"@types/ws": "^8.18.1",
|
|
39
|
+
"esbuild": "^0.28.0",
|
|
40
|
+
"nodemon": "^3.0.0",
|
|
41
|
+
"tsx": "^4.22.3",
|
|
42
|
+
"typescript": "^6.0.3",
|
|
43
|
+
"vite": "^8.0.14"
|
|
44
|
+
},
|
|
45
|
+
"volta": {
|
|
46
|
+
"node": "18.20.8"
|
|
47
|
+
},
|
|
48
|
+
"engines": {
|
|
49
|
+
"node": ">=18.0.0"
|
|
50
|
+
}
|
|
51
|
+
}
|