# GoTunnel 插件开发指南 本文档介绍如何为 GoTunnel 开发 JS 插件。JS 插件基于 [goja](https://github.com/dop251/goja) 运行时,运行在客户端上。 ## 目录 - [快速开始](#快速开始) - [插件结构](#插件结构) - [API 参考](#api-参考) - [示例插件](#示例插件) - [插件签名](#插件签名) - [发布到商店](#发布到商店) --- ## 快速开始 ### 最小插件示例 ```javascript // 必须:定义插件元数据 function metadata() { return { name: "my-plugin", version: "1.0.0", type: "app", description: "My first plugin", author: "Your Name" }; } // 可选:插件启动时调用 function start() { log("Plugin started"); } // 必须:处理连接 function handleConn(conn) { // 处理连接逻辑 conn.Close(); } // 可选:插件停止时调用 function stop() { log("Plugin stopped"); } ``` --- ## 插件结构 ### 生命周期函数 | 函数 | 必须 | 说明 | |------|------|------| | `metadata()` | 否 | 返回插件元数据,不定义则使用默认值 | | `start()` | 否 | 插件启动时调用 | | `handleConn(conn)` | 是 | 处理每个连接 | | `stop()` | 否 | 插件停止时调用 | ### 元数据字段 ```javascript function metadata() { return { name: "plugin-name", // 插件名称 version: "1.0.0", // 版本号 type: "app", // 类型: "app" (应用插件) description: "描述", // 插件描述 author: "作者" // 作者名称 }; } ``` --- ## API 参考 ### 基础 API #### `log(message)` 输出日志信息。 ```javascript log("Hello, World!"); // 输出: [JS:plugin-name] Hello, World! ``` #### `config(key)` 获取插件配置值。 ```javascript var port = config("port"); var host = config("host") || "127.0.0.1"; ``` --- ### 连接 API (conn) `handleConn` 函数接收的 `conn` 对象提供以下方法: #### `conn.Read(size)` 读取数据,返回字节数组,失败返回 `null`。 ```javascript var data = conn.Read(1024); if (data) { log("Received " + data.length + " bytes"); } ``` #### `conn.Write(data)` 写入数据,返回写入的字节数。 ```javascript var written = conn.Write(data); log("Wrote " + written + " bytes"); ``` #### `conn.Close()` 关闭连接。 ```javascript conn.Close(); ``` --- ### 文件系统 API (fs) 所有文件操作都在沙箱中执行,有路径和大小限制。 #### `fs.readFile(path)` 读取文件内容。 ```javascript var result = fs.readFile("/path/to/file.txt"); if (result.error) { log("Error: " + result.error); } else { log("Content: " + result.data); } ``` #### `fs.writeFile(path, content)` 写入文件内容。 ```javascript var result = fs.writeFile("/path/to/file.txt", "Hello"); if (result.ok) { log("File written"); } else { log("Error: " + result.error); } ``` #### `fs.readDir(path)` 读取目录内容。 ```javascript var result = fs.readDir("/path/to/dir"); if (!result.error) { for (var i = 0; i < result.entries.length; i++) { var entry = result.entries[i]; log(entry.name + " - " + (entry.isDir ? "DIR" : entry.size + " bytes")); } } ``` #### `fs.stat(path)` 获取文件信息。 ```javascript var result = fs.stat("/path/to/file"); if (!result.error) { log("Name: " + result.name); log("Size: " + result.size); log("IsDir: " + result.isDir); log("ModTime: " + result.modTime); } ``` #### `fs.exists(path)` 检查文件是否存在。 ```javascript var result = fs.exists("/path/to/file"); if (result.exists) { log("File exists"); } ``` #### `fs.mkdir(path)` 创建目录。 ```javascript var result = fs.mkdir("/path/to/new/dir"); if (result.ok) { log("Directory created"); } ``` #### `fs.remove(path)` 删除文件或目录。 ```javascript var result = fs.remove("/path/to/file"); if (result.ok) { log("Removed"); } ``` --- ### HTTP API (http) 用于构建简单的 HTTP 服务。 #### `http.serve(conn, handler)` 处理 HTTP 请求。 ```javascript function handleConn(conn) { http.serve(conn, function(req) { return { status: 200, contentType: "application/json", body: http.json({ message: "Hello", path: req.path }) }; }); } ``` **请求对象 (req):** | 字段 | 类型 | 说明 | |------|------|------| | `method` | string | HTTP 方法 (GET, POST, etc.) | | `path` | string | 请求路径 | | `body` | string | 请求体 | **响应对象:** | 字段 | 类型 | 说明 | |------|------|------| | `status` | number | HTTP 状态码 (默认 200) | | `contentType` | string | Content-Type (默认 application/json) | | `body` | string | 响应体 | #### `http.json(data)` 将对象序列化为 JSON 字符串。 ```javascript var jsonStr = http.json({ name: "test", value: 123 }); // 返回: '{"name":"test","value":123}' ``` #### `http.sendFile(conn, filePath)` 发送文件作为 HTTP 响应。 ```javascript function handleConn(conn) { http.sendFile(conn, "/path/to/index.html"); } ``` --- ### 增强 API (Enhanced APIs) GoTunnel v2.0+ 提供了更多强大的 API 能力。 #### `logger` (日志) 推荐使用结构化日志替代简单的 `log()`。 - `logger.info(msg)` - `logger.warn(msg)` - `logger.error(msg)` ```javascript logger.info("Server started"); logger.error("Connection failed"); ``` #### `config` (配置) 增强的配置获取方式。 - `config.get(key)`: 获取配置值 - `config.getAll()`: 获取所有配置 ```javascript var all = config.getAll(); var port = config.get("port"); ``` #### `storage` (持久化存储) 简单的 Key-Value 存储,数据保存在客户端本地。 - `storage.get(key, default)` - `storage.set(key, value)` - `storage.delete(key)` - `storage.keys()` ```javascript storage.set("last_run", Date.now()); var last = storage.get("last_run", 0); ``` #### `event` (事件总线) 插件内部或插件间的事件通信。 - `event.on(name, callback)` - `event.emit(name, data)` - `event.off(name)` ```javascript event.on("user_login", function(user) { logger.info("User logged in: " + user); }); event.emit("user_login", "admin"); ``` #### `request` (HTTP 请求) 发起外部 HTTP 请求。 - `request.get(url)` - `request.post(url, contentType, body)` ```javascript var res = request.get("https://api.ipify.org"); logger.info("My IP: " + res.body); ``` #### `notify` (通知) 发送系统通知。 - `notify.send(title, message)` ```javascript notify.send("Download Complete", "File saved to disk"); ``` --- ## 示例插件 ### Echo 服务 ```javascript function metadata() { return { name: "echo", version: "1.0.0", type: "app", description: "Echo back received data" }; } function handleConn(conn) { while (true) { var data = conn.Read(4096); if (!data || data.length === 0) { break; } conn.Write(data); } conn.Close(); } ``` ### HTTP 文件服务器 ```javascript function metadata() { return { name: "file-server", version: "1.0.0", type: "app", description: "Simple HTTP file server" }; } var rootDir = ""; function start() { rootDir = config("root") || "/tmp"; log("Serving files from: " + rootDir); } function handleConn(conn) { http.serve(conn, function(req) { if (req.method === "GET") { var filePath = rootDir + req.path; if (req.path === "/") { filePath = rootDir + "/index.html"; } var stat = fs.stat(filePath); if (stat.error) { return { status: 404, body: "Not Found" }; } if (stat.isDir) { return listDirectory(filePath); } var file = fs.readFile(filePath); if (file.error) { return { status: 500, body: file.error }; } return { status: 200, contentType: "text/html", body: file.data }; } return { status: 405, body: "Method Not Allowed" }; }); } function listDirectory(path) { var result = fs.readDir(path); if (result.error) { return { status: 500, body: result.error }; } var html = "