@demox-site/mcp-server 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.
@@ -0,0 +1,316 @@
1
+ import { loadConfig, logger } from "../utils/config.js";
2
+ import cloudbase from "@cloudbase/node-sdk";
3
+ /**
4
+ * Demox API 客户端
5
+ * 通过 mcp-api 云函数调用其他云函数
6
+ */
7
+ export class DemoxClient {
8
+ cloudFunctionUrl;
9
+ constructor(accessToken) {
10
+ const config = loadConfig();
11
+ this.cloudFunctionUrl = config.cloudFunctionUrl;
12
+ }
13
+ /**
14
+ * 调用云函数(通过 mcp-api HTTP 代理)
15
+ */
16
+ async callFunction(name, data, accessToken) {
17
+ try {
18
+ logger.debug(`调用云函数: ${name}`);
19
+ // 使用配置的云函数 URL
20
+ logger.debug(`API URL: ${this.cloudFunctionUrl}`);
21
+ const response = await fetch(this.cloudFunctionUrl, {
22
+ method: 'POST',
23
+ headers: {
24
+ 'Content-Type': 'application/json',
25
+ 'Authorization': `Bearer ${accessToken}`,
26
+ },
27
+ body: JSON.stringify({
28
+ functionName: name,
29
+ data,
30
+ }),
31
+ });
32
+ if (!response.ok) {
33
+ const errorText = await response.text();
34
+ throw new Error(`HTTP ${response.status}: ${errorText}`);
35
+ }
36
+ const responseData = await response.json();
37
+ // 检查错误
38
+ if (responseData && responseData.error) {
39
+ const error = responseData.error;
40
+ throw new Error(`[${error.code}] ${error.message}${error.suggestion ? `\n建议:${error.suggestion}` : ""}`);
41
+ }
42
+ return responseData;
43
+ }
44
+ catch (error) {
45
+ logger.error(`云函数调用失败 (${name}):`, error.message);
46
+ throw error;
47
+ }
48
+ }
49
+ /**
50
+ * 部署网站
51
+ */
52
+ async deployWebsite(params, accessToken) {
53
+ logger.info(`正在部署网站: ${params.fileName}`);
54
+ // 处理输入路径(文件、目录或 URL)
55
+ let zipFile = params.zipFile;
56
+ let isLocalFile = false;
57
+ let localFilePath = null;
58
+ if (zipFile.startsWith("http://") || zipFile.startsWith("https://")) {
59
+ // URL: 下载 ZIP 文件
60
+ logger.debug("检测到 ZIP URL,正在下载...");
61
+ const buffer = await this.downloadZipFileToBuffer(zipFile);
62
+ isLocalFile = true;
63
+ localFilePath = await this.saveBufferToTempFile(buffer);
64
+ }
65
+ else if (!this.isBase64(zipFile)) {
66
+ // 本地路径:文件或目录
67
+ logger.debug(`检测到本地路径: ${zipFile}`);
68
+ const stat = await this.getPathStat(zipFile);
69
+ if (stat.isDirectory) {
70
+ // 目录:打包成 ZIP
71
+ logger.debug(`检测到目录: ${zipFile},正在打包...`);
72
+ localFilePath = await this.zipDirectoryToFile(zipFile);
73
+ isLocalFile = true;
74
+ }
75
+ else if (zipFile.endsWith(".zip")) {
76
+ // ZIP 文件:直接使用
77
+ localFilePath = zipFile;
78
+ isLocalFile = true;
79
+ }
80
+ else {
81
+ throw new Error(`不支持的文件类型,请提供 .zip 文件或目录`);
82
+ }
83
+ }
84
+ // 检查文件大小(如果已转换为本地文件)
85
+ if (isLocalFile && localFilePath) {
86
+ const fileSize = await this.getFileSize(localFilePath);
87
+ const maxSize = 5 * 1024 * 1024; // 5MB
88
+ if (fileSize > maxSize) {
89
+ // 文件过大,使用 CloudBase Storage 上传
90
+ logger.info(`文件较大 (${(fileSize / 1024 / 1024).toFixed(2)}MB),使用 CloudBase Storage 上传`);
91
+ const fileId = await this.uploadToCloudBaseStorage(localFilePath, accessToken);
92
+ const result = await this.callFunction("deploy-website", {
93
+ action: "upload_and_deploy",
94
+ fileId,
95
+ websiteId: params.websiteId,
96
+ fileName: params.fileName,
97
+ }, accessToken);
98
+ logger.info(`网站部署成功: ${result.url}`);
99
+ return result;
100
+ }
101
+ else {
102
+ // 文件较小,读取为 base64 并直接上传
103
+ logger.debug(`文件较小 (${(fileSize / 1024).toFixed(2)}KB),使用 base64 上传`);
104
+ const base64 = await this.readFileAsBase64(localFilePath);
105
+ zipFile = base64;
106
+ }
107
+ }
108
+ const result = await this.callFunction("deploy-website", {
109
+ action: "upload_and_deploy",
110
+ fileContentBase64: zipFile,
111
+ websiteId: params.websiteId,
112
+ fileName: params.fileName,
113
+ }, accessToken);
114
+ logger.info(`网站部署成功: ${result.url}`);
115
+ return result;
116
+ }
117
+ /**
118
+ * 获取路径状态信息
119
+ */
120
+ async getPathStat(filePath) {
121
+ const fs = await import("fs");
122
+ if (!fs.existsSync(filePath)) {
123
+ throw new Error(`路径不存在: ${filePath}`);
124
+ }
125
+ const stat = fs.statSync(filePath);
126
+ return {
127
+ isFile: stat.isFile(),
128
+ isDirectory: stat.isDirectory(),
129
+ size: stat.size,
130
+ };
131
+ }
132
+ /**
133
+ * 获取文件大小
134
+ */
135
+ async getFileSize(filePath) {
136
+ const stat = await this.getPathStat(filePath);
137
+ return stat.size;
138
+ }
139
+ /**
140
+ * 读取文件为 base64
141
+ */
142
+ async readFileAsBase64(filePath) {
143
+ const fs = await import("fs");
144
+ try {
145
+ const buffer = fs.readFileSync(filePath);
146
+ const base64 = buffer.toString("base64");
147
+ logger.debug(`文件读取成功,大小: ${buffer.length} 字节`);
148
+ return base64;
149
+ }
150
+ catch (error) {
151
+ throw new Error(`读取文件失败: ${error.message}`);
152
+ }
153
+ }
154
+ /**
155
+ * 将目录打包成 ZIP 文件
156
+ */
157
+ async zipDirectoryToFile(dirPath) {
158
+ const fs = await import("fs");
159
+ const pathModule = await import("path");
160
+ const os = await import("os");
161
+ const AdmZip = await import("adm-zip");
162
+ try {
163
+ const zip = new AdmZip.default();
164
+ zip.addLocalFolder(dirPath);
165
+ // 保存到临时文件
166
+ const tempFile = pathModule.join(os.tmpdir(), `demox-deploy-${Date.now()}.zip`);
167
+ zip.writeZip(tempFile);
168
+ logger.debug(`目录打包成功: ${dirPath} -> ${tempFile}`);
169
+ return tempFile;
170
+ }
171
+ catch (error) {
172
+ throw new Error(`打包目录失败: ${error.message}`);
173
+ }
174
+ }
175
+ /**
176
+ * 上传文件到 CloudBase Storage
177
+ */
178
+ async uploadToCloudBaseStorage(filePath, accessToken) {
179
+ const config = loadConfig();
180
+ // 初始化 CloudBase
181
+ const app = cloudbase.init({
182
+ env: config.serverEnv,
183
+ accessToken,
184
+ });
185
+ try {
186
+ const fs = await import("fs");
187
+ const pathModule = await import("path");
188
+ const fileName = pathModule.basename(filePath);
189
+ const cloudPath = `mcp-uploads/${Date.now()}-${fileName}`;
190
+ logger.info(`正在上传文件到 CloudBase Storage: ${cloudPath}`);
191
+ // 上传文件
192
+ const result = await app.uploadFile({
193
+ cloudPath,
194
+ fileContent: fs.createReadStream(filePath),
195
+ });
196
+ logger.info(`文件上传成功: ${result.fileID}`);
197
+ return result.fileID;
198
+ }
199
+ catch (error) {
200
+ logger.error("上传文件到 CloudBase Storage 失败:", error.message);
201
+ throw new Error(`上传文件失败: ${error.message}`);
202
+ }
203
+ }
204
+ /**
205
+ * 下载 ZIP 文件并保存为 Buffer
206
+ */
207
+ async downloadZipFileToBuffer(url) {
208
+ try {
209
+ const response = await fetch(url);
210
+ if (!response.ok) {
211
+ throw new Error(`下载失败: ${response.statusText}`);
212
+ }
213
+ const buffer = Buffer.from(await response.arrayBuffer());
214
+ logger.debug(`ZIP 文件下载成功,大小: ${buffer.length} 字节`);
215
+ return buffer;
216
+ }
217
+ catch (error) {
218
+ logger.error("下载 ZIP 文件失败:", error.message);
219
+ throw error;
220
+ }
221
+ }
222
+ /**
223
+ * 保存 Buffer 到临时文件
224
+ */
225
+ async saveBufferToTempFile(buffer) {
226
+ const fs = await import("fs");
227
+ const pathModule = await import("path");
228
+ const os = await import("os");
229
+ const tempFile = pathModule.join(os.tmpdir(), `demox-download-${Date.now()}.zip`);
230
+ fs.writeFileSync(tempFile, buffer);
231
+ logger.debug(`Buffer 已保存到临时文件: ${tempFile}`);
232
+ return tempFile;
233
+ }
234
+ /**
235
+ * 检查字符串是否是 base64 编码
236
+ */
237
+ isBase64(str) {
238
+ // 简单的 base64 检测
239
+ try {
240
+ return btoa(atob(str)) === str;
241
+ }
242
+ catch (e) {
243
+ // 如果不是 base64,检查是否是本地路径
244
+ return !str.includes("/") && !str.includes("\\") && str.length > 100;
245
+ }
246
+ }
247
+ /**
248
+ * 列出所有网站
249
+ */
250
+ async listWebsites(accessToken) {
251
+ logger.debug("获取网站列表");
252
+ const result = await this.callFunction("deploy-website", {
253
+ action: "list",
254
+ }, accessToken);
255
+ // 云函数返回 { files: [...], count: n }
256
+ return result.files || [];
257
+ }
258
+ /**
259
+ * 删除网站
260
+ */
261
+ async deleteWebsite(websiteId, accessToken) {
262
+ logger.info(`正在删除网站: ${websiteId}`);
263
+ await this.callFunction("deploy-website", {
264
+ action: "delete",
265
+ websiteId,
266
+ }, accessToken);
267
+ logger.info("网站已删除");
268
+ }
269
+ /**
270
+ * 获取网站详情
271
+ */
272
+ async getWebsite(websiteId, accessToken) {
273
+ logger.debug(`获取网站详情: ${websiteId}`);
274
+ const result = await this.callFunction("deploy-website", {
275
+ action: "get",
276
+ websiteId,
277
+ }, accessToken);
278
+ return result.website || null;
279
+ }
280
+ /**
281
+ * 下载 ZIP 文件并转换为 base64
282
+ */
283
+ async downloadZipFile(url) {
284
+ try {
285
+ const response = await fetch(url);
286
+ if (!response.ok) {
287
+ throw new Error(`下载失败: ${response.statusText}`);
288
+ }
289
+ const buffer = await response.arrayBuffer();
290
+ const base64 = Buffer.from(buffer).toString("base64");
291
+ logger.debug(`ZIP 文件下载成功,大小: ${buffer.byteLength} 字节`);
292
+ return base64;
293
+ }
294
+ catch (error) {
295
+ logger.error("下载 ZIP 文件失败:", error.message);
296
+ throw error;
297
+ }
298
+ }
299
+ /**
300
+ * 验证 Token 有效性
301
+ */
302
+ async verifyToken(accessToken) {
303
+ try {
304
+ await this.callFunction("oauth-token-manager", {
305
+ action: "verify_token",
306
+ accessToken,
307
+ }, accessToken);
308
+ return true;
309
+ }
310
+ catch (error) {
311
+ logger.error("Token 验证失败:", error);
312
+ return false;
313
+ }
314
+ }
315
+ }
316
+ //# sourceMappingURL=DemoxClient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DemoxClient.js","sourceRoot":"","sources":["../../src/api/DemoxClient.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Token 数据结构
3
+ */
4
+ export interface TokenData {
5
+ accessToken: string;
6
+ refreshToken: string;
7
+ expiresAt: number;
8
+ scopes: string[];
9
+ userId: string;
10
+ clientId: string;
11
+ }
12
+ /**
13
+ * OAuth 认证管理器
14
+ */
15
+ export declare class OAuthManager {
16
+ private config;
17
+ private tokenPath;
18
+ private currentToken;
19
+ constructor();
20
+ /**
21
+ * 确保已认证
22
+ * 如果 Token 不存在或过期,自动触发登录流程
23
+ */
24
+ ensureAuthenticated(): Promise<string>;
25
+ /**
26
+ * 启动 OAuth 授权流程(公开方法,供 CLI 使用)
27
+ */
28
+ authorize(): Promise<string>;
29
+ /**
30
+ * 启动本地 HTTP 服务器接收回调
31
+ */
32
+ private startLocalServer;
33
+ /**
34
+ * 从本地加载 Token
35
+ */
36
+ private loadToken;
37
+ /**
38
+ * 保存 Token 到本地
39
+ */
40
+ private saveToken;
41
+ /**
42
+ * 检查 Token 是否过期
43
+ */
44
+ private isTokenExpired;
45
+ /**
46
+ * 生成随机 state
47
+ */
48
+ private generateRandomState;
49
+ /**
50
+ * 创建超时 Promise
51
+ */
52
+ private createTimeout;
53
+ /**
54
+ * 撤销当前 Token(登出)
55
+ */
56
+ revokeToken(): Promise<void>;
57
+ /**
58
+ * 获取当前 Token
59
+ */
60
+ getCurrentToken(): TokenData | null;
61
+ }
62
+ //# sourceMappingURL=OAuthManager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OAuthManager.d.ts","sourceRoot":"","sources":["../../src/auth/OAuthManager.ts"],"names":[],"mappings":"AAQA;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAgC;IAC9C,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,YAAY,CAA0B;;IAO9C;;;OAGG;IACG,mBAAmB,IAAI,OAAO,CAAC,MAAM,CAAC;IA0B5C;;OAEG;IACG,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC;IAqDlC;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAuGxB;;OAEG;YACW,SAAS;IAiBvB;;OAEG;YACW,SAAS;IAuBvB;;OAEG;IACH,OAAO,CAAC,cAAc;IAQtB;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAK3B;;OAEG;IACH,OAAO,CAAC,aAAa;IAMrB;;OAEG;IACG,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;IAclC;;OAEG;IACH,eAAe,IAAI,SAAS,GAAG,IAAI;CAGpC"}
@@ -0,0 +1,270 @@
1
+ import http from "http";
2
+ import { URL } from "url";
3
+ import open from "open";
4
+ import { promises as fs } from "fs";
5
+ import { existsSync } from "fs";
6
+ import { dirname } from "path";
7
+ import { loadConfig, getTokenPath, logger } from "../utils/config.js";
8
+ /**
9
+ * OAuth 认证管理器
10
+ */
11
+ export class OAuthManager {
12
+ config;
13
+ tokenPath;
14
+ currentToken = null;
15
+ constructor() {
16
+ this.config = loadConfig();
17
+ this.tokenPath = getTokenPath();
18
+ }
19
+ /**
20
+ * 确保已认证
21
+ * 如果 Token 不存在或过期,自动触发登录流程
22
+ */
23
+ async ensureAuthenticated() {
24
+ // 尝试从本地加载 Token
25
+ const tokenData = await this.loadToken();
26
+ if (tokenData && !this.isTokenExpired(tokenData)) {
27
+ this.currentToken = tokenData;
28
+ logger.debug("使用本地缓存的 Token");
29
+ // 检查是否即将过期(3天内)
30
+ const daysLeft = Math.floor((tokenData.expiresAt - Date.now()) / (1000 * 60 * 60 * 24));
31
+ if (daysLeft <= 3) {
32
+ logger.warn(`Token 将在 ${daysLeft} 天后过期,建议重新登录`);
33
+ }
34
+ return tokenData.accessToken;
35
+ }
36
+ // Token 不存在或过期,触发登录
37
+ logger.info("Token 不存在或已过期,需要登录");
38
+ return await this.authorize();
39
+ }
40
+ /**
41
+ * 启动 OAuth 授权流程(公开方法,供 CLI 使用)
42
+ */
43
+ async authorize() {
44
+ logger.info("正在启动登录流程...");
45
+ // 生成随机 state
46
+ const state = this.generateRandomState();
47
+ // 构建授权 URL(手动构建以支持 hash 路由)
48
+ const params = new URLSearchParams();
49
+ params.set("client_id", this.config.clientId);
50
+ params.set("redirect_uri", "http://localhost:39897/callback");
51
+ params.set("response_type", "code");
52
+ params.set("state", state);
53
+ params.set("scope", "website:deploy website:list website:delete website:update");
54
+ const authUrl = `${this.config.authUrl}?${params.toString()}`;
55
+ // 展示 URL 给用户(在启动服务器之前)
56
+ console.error("\n" + "=".repeat(70));
57
+ console.error("🔐 请在浏览器中访问以下 URL 完成登录:");
58
+ console.error("=".repeat(70));
59
+ console.error("\n" + authUrl + "\n");
60
+ console.error("=".repeat(70));
61
+ console.error("💡 提示:复制上面的 URL 到浏览器中打开");
62
+ console.error("⏳ 等待您在浏览器中完成登录...\n");
63
+ // 尝试打开浏览器(可选)
64
+ try {
65
+ await open(authUrl);
66
+ }
67
+ catch (error) {
68
+ // 如果打开浏览器失败,忽略错误,用户可以手动访问
69
+ logger.debug("无法自动打开浏览器,请手动访问上述 URL");
70
+ }
71
+ try {
72
+ // 等待回调(超时 5 分钟)- 直接返回 Token 数据
73
+ const tokenData = await Promise.race([
74
+ this.startLocalServer(state),
75
+ this.createTimeout(300000),
76
+ ]);
77
+ // 保存 Token(无需交换)
78
+ await this.saveToken(tokenData);
79
+ logger.info("✅ 登录成功!");
80
+ console.error(`Token 已保存到: ${this.tokenPath}\n`);
81
+ return tokenData.accessToken;
82
+ }
83
+ catch (error) {
84
+ logger.error("登录失败:", error.message);
85
+ throw error;
86
+ }
87
+ }
88
+ /**
89
+ * 启动本地 HTTP 服务器接收回调
90
+ */
91
+ startLocalServer(expectedState) {
92
+ return new Promise((resolve, reject) => {
93
+ const server = http.createServer((req, res) => {
94
+ const url = new URL(req.url || "", `http://${req.headers.host}`);
95
+ const accessToken = url.searchParams.get("access_token");
96
+ const refreshToken = url.searchParams.get("refresh_token");
97
+ const userId = url.searchParams.get("user_id");
98
+ const state = url.searchParams.get("state");
99
+ const error = url.searchParams.get("error");
100
+ if (error) {
101
+ // 用户取消或出错
102
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
103
+ res.end(`
104
+ <!DOCTYPE html>
105
+ <html>
106
+ <head>
107
+ <title>登录取消</title>
108
+ <meta charset="utf-8">
109
+ <style>
110
+ body { font-family: sans-serif; text-align: center; padding: 50px; }
111
+ .error { color: #ef4444; font-size: 24px; }
112
+ </style>
113
+ </head>
114
+ <body>
115
+ <div class="error">❌ 登录取消</div>
116
+ <p>${error}</p>
117
+ <p>您可以关闭此页面并返回编辑器。</p>
118
+ </body>
119
+ </html>
120
+ `);
121
+ server.close();
122
+ reject(new Error(`OAuth 授权失败: ${error}`));
123
+ return;
124
+ }
125
+ if (accessToken && state) {
126
+ // 验证 state(防止 CSRF 攻击)
127
+ if (state !== expectedState) {
128
+ res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
129
+ res.end("State 不匹配");
130
+ server.close();
131
+ reject(new Error("OAuth state 不匹配,可能存在安全风险"));
132
+ return;
133
+ }
134
+ // 授权成功 - 直接接收 Token
135
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
136
+ res.end(`
137
+ <!DOCTYPE html>
138
+ <html>
139
+ <head>
140
+ <title>登录成功</title>
141
+ <meta charset="utf-8">
142
+ <style>
143
+ body { font-family: sans-serif; text-align: center; padding: 50px; }
144
+ .success { color: #10b981; font-size: 24px; }
145
+ </style>
146
+ </head>
147
+ <body>
148
+ <div class="success">✅ 登录成功!</div>
149
+ <p>您可以关闭此页面并返回编辑器了。</p>
150
+ <p style="color: #666; font-size: 14px;">
151
+ 凭证已保存在本地 (~/.demox/token.json)<br>
152
+ 有效期:30 天
153
+ </p>
154
+ </body>
155
+ </html>
156
+ `);
157
+ server.close();
158
+ // 直接返回 Token 数据(无需交换)
159
+ const tokenData = {
160
+ accessToken,
161
+ refreshToken: refreshToken || accessToken,
162
+ expiresAt: Date.now() + 30 * 24 * 60 * 60 * 1000, // 30 天
163
+ scopes: ["website:deploy", "website:list", "website:delete", "website:update"],
164
+ userId: userId || "",
165
+ clientId: this.config.clientId,
166
+ };
167
+ resolve(tokenData);
168
+ }
169
+ else {
170
+ res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
171
+ res.end("缺少必要的参数");
172
+ server.close();
173
+ reject(new Error("缺少必要的 OAuth 参数"));
174
+ }
175
+ });
176
+ server.listen(39897, () => {
177
+ logger.debug("本地服务器已启动,监听端口 39897");
178
+ });
179
+ // 超时处理
180
+ setTimeout(() => {
181
+ server.close();
182
+ reject(new Error("登录超时(5 分钟)"));
183
+ }, 300000);
184
+ });
185
+ }
186
+ /**
187
+ * 从本地加载 Token
188
+ */
189
+ async loadToken() {
190
+ try {
191
+ if (!existsSync(this.tokenPath)) {
192
+ return null;
193
+ }
194
+ const content = await fs.readFile(this.tokenPath, "utf-8");
195
+ const tokenData = JSON.parse(content);
196
+ logger.debug("成功加载本地 Token");
197
+ return tokenData;
198
+ }
199
+ catch (error) {
200
+ logger.error("加载本地 Token 失败:", error);
201
+ return null;
202
+ }
203
+ }
204
+ /**
205
+ * 保存 Token 到本地
206
+ */
207
+ async saveToken(tokenData) {
208
+ try {
209
+ const dir = dirname(this.tokenPath);
210
+ // 确保目录存在
211
+ if (!existsSync(dir)) {
212
+ await fs.mkdir(dir, { recursive: true });
213
+ }
214
+ await fs.writeFile(this.tokenPath, JSON.stringify(tokenData, null, 2), "utf-8");
215
+ this.currentToken = tokenData;
216
+ logger.debug("Token 已保存到本地");
217
+ }
218
+ catch (error) {
219
+ logger.error("保存 Token 失败:", error);
220
+ throw error;
221
+ }
222
+ }
223
+ /**
224
+ * 检查 Token 是否过期
225
+ */
226
+ isTokenExpired(tokenData) {
227
+ const now = Date.now();
228
+ const expiresAt = tokenData.expiresAt;
229
+ // 提前 5 分钟判断为过期,避免临界时间
230
+ return now >= expiresAt - 5 * 60 * 1000;
231
+ }
232
+ /**
233
+ * 生成随机 state
234
+ */
235
+ generateRandomState() {
236
+ return Math.random().toString(36).substring(2, 15) +
237
+ Math.random().toString(36).substring(2, 15);
238
+ }
239
+ /**
240
+ * 创建超时 Promise
241
+ */
242
+ createTimeout(ms) {
243
+ return new Promise((_, reject) => {
244
+ setTimeout(() => reject(new Error("操作超时")), ms);
245
+ });
246
+ }
247
+ /**
248
+ * 撤销当前 Token(登出)
249
+ */
250
+ async revokeToken() {
251
+ try {
252
+ if (existsSync(this.tokenPath)) {
253
+ await fs.unlink(this.tokenPath);
254
+ logger.info("已删除本地 Token");
255
+ }
256
+ this.currentToken = null;
257
+ }
258
+ catch (error) {
259
+ logger.error("删除 Token 失败:", error);
260
+ throw error;
261
+ }
262
+ }
263
+ /**
264
+ * 获取当前 Token
265
+ */
266
+ getCurrentToken() {
267
+ return this.currentToken;
268
+ }
269
+ }
270
+ //# sourceMappingURL=OAuthManager.js.map