qdmp-cli 0.1.24 → 0.1.27

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
@@ -61,3 +61,35 @@ qdmp-cli upload -e dev //上传开发环境
61
61
  qdmp-cli upload -d "新增订单查询功能" //指定版本描述
62
62
  ```
63
63
  直接运行上传命令时,会交互式提示输入选填的版本描述(200字以内)。
64
+
65
+ ### 生成 OpenAPI 权限申请地址
66
+
67
+ Agent 在项目根目录维护 `.qdmp/openapi-usage.json`,只记录代码实际调用的 OpenAPI:
68
+
69
+ ```json
70
+ {
71
+ "schemaVersion": 1,
72
+ "openapis": [
73
+ { "method": "GET", "path": "/spu/v1/detail" },
74
+ { "showCode": "tag.search" }
75
+ ]
76
+ }
77
+ ```
78
+
79
+ 生成申请地址:
80
+
81
+ ```bash
82
+ qdmp-cli openapi apply-url
83
+ qdmp-cli openapi apply-url --env dev --json
84
+ ```
85
+
86
+ 命令会分页复用 `GET /qdmp-web/v1/developer/capabilities?type=openapi` 获取完整目录,将清单严格解析为
87
+ 资源 ID,并排除已开通、审核中或无需权限的接口。需要手动申请的 ID 会生成 16 位短令牌,最终地址形如:
88
+
89
+ ```text
90
+ https://open.qiandao.com/apps/<appId>/capabilities?requiredApis=<16位queryKey>
91
+ ```
92
+
93
+ 本地联调可设置 `QDMP_API_BASE_URL=http://127.0.0.1:8082`,并通过
94
+ `--open-platform-url http://local.qiandao.com:8081` 指向本地前端。
95
+ CI 或隔离测试环境可通过 `QDMP_TOKEN` 临时注入登录 Token;日常交互仍优先使用 `qdmp-cli login`。
package/actions.js CHANGED
@@ -5,11 +5,9 @@ import { download } from "./utils/gitClone.js";
5
5
  import { printLogo } from "./utils/logHandler.js";
6
6
  import chalk from "chalk";
7
7
  import table from "table";
8
- import {
9
- inquirerAccountPassword,
10
- inquirerVersionDescription,
11
- } from "./utils/interactive.js";
12
- import { login, getDeveloperId, getUserInfo } from "./api.js";
8
+ import { inquirerVersionDescription } from "./utils/interactive.js";
9
+ import { ensureInteractiveAuth, interactiveLogin } from "./utils/authFlow.js";
10
+ import { getDeveloperId, getUserInfo } from "./api.js";
13
11
  import fs from "fs";
14
12
  import path from "path";
15
13
  import { createRequire } from "module";
@@ -81,19 +79,22 @@ export const listAction = () => {
81
79
 
82
80
  export const loginAction = async (option) => {
83
81
  try {
84
- const { account, password } = await inquirerAccountPassword();
85
- const result = await login(account, password, option.env);
86
- if (result.success) {
87
- success("登录成功");
88
- } else {
89
- error("登录失败");
82
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
83
+ throw new Error("qdmp login 需要在支持交互输入的终端中执行,或使用 QDMP_TOKEN");
90
84
  }
85
+ await interactiveLogin(option.env);
86
+ success("登录成功");
91
87
  } catch (e) {
92
88
  error(e.message);
89
+ } finally {
90
+ // Inquirer may leave stdin in a flowing state after the localhost callback
91
+ // resolves. This command has no follow-up prompt, so let Node exit cleanly.
92
+ if (process.stdin.isTTY) process.stdin.pause();
93
93
  }
94
94
  };
95
95
  export const getMyInfoAction = async (option) => {
96
96
  try {
97
+ await ensureInteractiveAuth(option.env);
97
98
  const nickname = await getUserInfo(option.env);
98
99
  const appId = await getAppId(option.env);
99
100
  const appInfo = await getAppInfo(appId, option.env);
@@ -106,6 +107,7 @@ export const getMyInfoAction = async (option) => {
106
107
 
107
108
  export async function uploadAction(option) {
108
109
  try {
110
+ await ensureInteractiveAuth(option.env);
109
111
  const did = await getDeveloperId(option.env);
110
112
  const appId = await getAppId(option.env);
111
113
  if (!appId) {
@@ -155,6 +157,7 @@ export async function uploadAction(option) {
155
157
  }
156
158
  export const initConfigAction = async (option) => {
157
159
  try {
160
+ await ensureInteractiveAuth(option.env);
158
161
  const qdmpJsonPath = `${process.cwd()}/qdmp.json`;
159
162
  if (fs.existsSync(qdmpJsonPath)) {
160
163
  return error(
@@ -163,7 +166,7 @@ export const initConfigAction = async (option) => {
163
166
  }
164
167
  const appId = option.appId;
165
168
  if (!appId) return error("未配置应用ID,请使用-a参数指定应用ID");
166
- const appInfo = await getAppInfo(appId);
169
+ const appInfo = await getAppInfo(appId, option.env);
167
170
  const config = {
168
171
  name: appInfo.name,
169
172
  appId: appInfo.appId,
package/api.js CHANGED
@@ -1,15 +1,28 @@
1
- import { getToken, saveToken } from "./utils/authorized.js";
1
+ import { getToken, saveToken, clearToken } from "./utils/authorized.js";
2
2
  import fs from "fs";
3
3
  import path from "path";
4
4
  import { promisify } from "util";
5
+ import { startLocalhostLogin } from "./utils/localhostLogin.js";
6
+ import { signQiandaoRequest } from "./utils/pigeon.js";
5
7
 
6
8
  const readFile = promisify(fs.readFile);
7
9
 
10
+ const resolveAPIBaseURL = (env) => {
11
+ const override = process.env.QDMP_API_BASE_URL?.trim().replace(/\/+$/, "");
12
+ if (override) return override;
13
+ return env === "prod"
14
+ ? "https://api.qiandao.com"
15
+ : "https://dev-api.qiandao.com";
16
+ };
17
+
8
18
  // API 基础配置
9
19
  const API_CONFIG = {
10
20
  timeout: 10000,
11
21
  headers: {
12
22
  "content-type": "application/json",
23
+ "x-request-package-id": "1036",
24
+ "x-request-version": "1.0.0",
25
+ "x-echoing-env": "test-z",
13
26
  },
14
27
  };
15
28
 
@@ -27,30 +40,40 @@ export const request = async (
27
40
  env = "prod",
28
41
  needAuth = true
29
42
  ) => {
43
+ const { cookieJar, ...requestOptions } = options;
30
44
  const config = {
31
- baseURL:
32
- env === "prod"
33
- ? "https://api.qiandao.com"
34
- : "https://dev-api.qiandao.com",
45
+ baseURL: resolveAPIBaseURL(env),
35
46
  method: "GET",
36
47
  headers: { ...API_CONFIG.headers },
37
- ...options,
48
+ credentials: "include",
49
+ ...requestOptions,
38
50
  };
51
+ if (cookieJar?.header()) config.headers.cookie = cookieJar.header();
39
52
  // 添加认证头(如果需要且如果有 token)
40
53
  if (needAuth) {
41
- const token = getToken();
42
- if (token) {
43
- config.headers["authorization"] = `Bearer ${token}`;
54
+ const token = getToken(env);
55
+ if (!token) {
56
+ throw Object.assign(new Error("未授权,执行 qdmp-cli login 完成登录"), {
57
+ code: "AUTH_REQUIRED",
58
+ });
44
59
  }
60
+ config.headers["authorization"] = `Bearer ${token}`;
45
61
  }
46
62
 
63
+ const requestURL = `${config.baseURL}${url}`;
64
+ const signedHeaders = await signQiandaoRequest(requestURL);
65
+ config.headers = { ...config.headers, ...signedHeaders };
66
+
47
67
  try {
48
- const response = await fetch(`${config.baseURL}${url}`, config);
68
+ const response = await fetch(requestURL, config);
69
+ cookieJar?.update(response.headers);
49
70
 
50
71
  // 检查响应状态
51
72
  if (!response.ok) {
52
73
  if (response.status === 401) {
53
- throw new Error("未授权,执行 qdmp-cli login 完成登录");
74
+ throw Object.assign(new Error("未授权,执行 qdmp-cli login 完成登录"), {
75
+ code: "AUTH_REQUIRED",
76
+ });
54
77
  }
55
78
  if (response.status === 403) {
56
79
  throw new Error("你没有该小程序的开发权限,请联系管理员开通后重试");
@@ -76,6 +99,85 @@ export const request = async (
76
99
  }
77
100
  };
78
101
 
102
+ const assertSuccessResponse = (result, action) => {
103
+ const code = Number(result?.code || 0);
104
+ if (Number.isFinite(code) && code !== 0) {
105
+ throw new Error(result?.message || `${action}失败`);
106
+ }
107
+ };
108
+
109
+ /**
110
+ * 复用开发者能力列表接口,分页获取当前应用的完整 OpenAPI 目录。
111
+ */
112
+ export const getAllOpenAPIResources = async (
113
+ appId,
114
+ env = "prod",
115
+ { pageSize = 200, maxPages = 100 } = {},
116
+ ) => {
117
+ const resources = [];
118
+ const seen = new Set();
119
+ let offset = 0;
120
+
121
+ for (let page = 0; page < maxPages; page += 1) {
122
+ const query = new URLSearchParams({
123
+ app_id: appId,
124
+ type: "openapi",
125
+ offset: String(offset),
126
+ limit: String(pageSize),
127
+ });
128
+ const result = await request(
129
+ `/qdmp-web/v1/developer/capabilities?${query.toString()}`,
130
+ {},
131
+ env,
132
+ );
133
+ assertSuccessResponse(result, "查询 OpenAPI 列表");
134
+ const pageResources = Array.isArray(result?.data?.resources)
135
+ ? result.data.resources
136
+ : [];
137
+ let added = 0;
138
+ for (const item of pageResources) {
139
+ const resourceId = String(item?.resource?.resourceId || "");
140
+ if (!resourceId || seen.has(resourceId)) continue;
141
+ seen.add(resourceId);
142
+ resources.push(item);
143
+ added += 1;
144
+ }
145
+
146
+ const total = Number(result?.data?.total || 0);
147
+ offset += pageResources.length;
148
+ if (
149
+ pageResources.length === 0 ||
150
+ pageResources.length < pageSize ||
151
+ (Number.isFinite(total) && total > 0 && offset >= total) ||
152
+ added === 0
153
+ ) {
154
+ return resources;
155
+ }
156
+ }
157
+
158
+ throw new Error(`OpenAPI 列表超过安全分页上限(${maxPages} 页)`);
159
+ };
160
+
161
+ export const createOpenAPIApplicationLink = async (
162
+ appId,
163
+ resourceIds,
164
+ env = "prod",
165
+ ) => {
166
+ const result = await request(
167
+ `/qdmp-web/v1/developer/apps/${encodeURIComponent(appId)}/openapi-application-links`,
168
+ {
169
+ method: "POST",
170
+ body: JSON.stringify({ resourceIds }),
171
+ },
172
+ env,
173
+ );
174
+ assertSuccessResponse(result, "生成 OpenAPI 权限申请链接");
175
+ if (!result?.data?.queryKey || !Array.isArray(result?.data?.resourceIds)) {
176
+ throw new Error("生成 OpenAPI 权限申请链接失败:返回数据不完整");
177
+ }
178
+ return result.data;
179
+ };
180
+
79
181
  /**
80
182
  * 用户登录
81
183
  * @param {string} username - 用户名
@@ -94,7 +196,7 @@ export const login = async (username, password, env = "prod") => {
94
196
  false // 登录时不需要认证头
95
197
  );
96
198
  if (result?.data?.token) {
97
- saveToken(result.data.token); // 保存token
199
+ saveToken(result.data.token, env); // 保存指定环境 token
98
200
  return { success: true };
99
201
  } else {
100
202
  throw new Error(result.error || "登录失败");
@@ -103,6 +205,139 @@ export const login = async (username, password, env = "prod") => {
103
205
  throw new Error(error.message);
104
206
  }
105
207
  };
208
+
209
+ const unwrapData = (result) => result?.data || result;
210
+
211
+ const qrcodeLoginURL = (qrcodeId) =>
212
+ `https://qiandao.com/login/scanning/confirm?qrcodeId=${encodeURIComponent(qrcodeId)}`;
213
+
214
+ export const generateLoginQrcode = async (env = "prod") => {
215
+ const result = await request(
216
+ "/signin/login/scan/qrcode/generate-id",
217
+ { method: "POST", body: JSON.stringify({}) },
218
+ env,
219
+ false,
220
+ );
221
+ const data = unwrapData(result);
222
+ if (!data?.qrcodeId) throw new Error("生成登录二维码失败");
223
+ return { qrcodeId: String(data.qrcodeId), url: qrcodeLoginURL(data.qrcodeId) };
224
+ };
225
+
226
+ export const getLoginQrcodeStatus = async (qrcodeId, env = "prod") => {
227
+ const result = await request(
228
+ "/signin/login/scan/qrcode/status",
229
+ { method: "POST", body: JSON.stringify({ qrcodeId }) },
230
+ env,
231
+ false,
232
+ );
233
+ const data = unwrapData(result);
234
+ return {
235
+ ...data,
236
+ status: data?.qrcodeStatus || data?.status || "pending",
237
+ token: data?.accessToken || data?.token,
238
+ };
239
+ };
240
+
241
+ export const loginByPlatformURL = async (env = "prod", onUpdate = () => {}) => {
242
+ const session = await startLocalhostLogin({
243
+ env,
244
+ acceptToken: async (token, isActive) => {
245
+ const result = await request("/mp/v1/user/me", {
246
+ headers: { ...API_CONFIG.headers, authorization: `Bearer ${token}` },
247
+ signal: AbortSignal.timeout(10000),
248
+ }, env, false);
249
+ assertSuccessResponse(result, "验证网页登录态");
250
+ if (!isActive()) throw new Error("网页登录会话已结束");
251
+ saveToken(token, env);
252
+ },
253
+ });
254
+ try {
255
+ await onUpdate({ type: "ready", url: session.url });
256
+ return await session.result;
257
+ } finally {
258
+ session.close();
259
+ }
260
+ };
261
+
262
+ export const getQiandaoUser = async (token, env = "prod") => {
263
+ const result = await request(
264
+ "/user/users/me",
265
+ { headers: { authorization: `Bearer ${token}` } },
266
+ env,
267
+ false,
268
+ );
269
+ return unwrapData(result);
270
+ };
271
+
272
+ export const exchangeQiandaoLogin = async (token, user, env = "prod") => {
273
+ const userInfo = unwrapData(user) || {};
274
+ const thirdPartyUserId = userInfo.id ?? userInfo.userId ?? userInfo.openId;
275
+ if (thirdPartyUserId == null || String(thirdPartyUserId).trim() === "") {
276
+ throw new Error("扫码登录未获取到用户信息");
277
+ }
278
+ const result = await request(
279
+ "/mp/v1/user/login/mobile",
280
+ {
281
+ method: "POST",
282
+ body: JSON.stringify({
283
+ thirdPartyUserId: String(thirdPartyUserId),
284
+ nickname: userInfo.nickName || userInfo.nickname || userInfo.name,
285
+ avatar: userInfo.avatar || userInfo.avatarUrl,
286
+ }),
287
+ },
288
+ env,
289
+ false,
290
+ );
291
+ const platformToken = unwrapData(result)?.token;
292
+ if (!platformToken) throw new Error("扫码登录未获取到开放平台 Token");
293
+ saveToken(platformToken, env);
294
+ return platformToken;
295
+ };
296
+
297
+ export const loginByQrcode = async (env = "prod", onUpdate = () => {}) => {
298
+ const session = await generateLoginQrcode(env);
299
+ await onUpdate({ type: "ready", ...session });
300
+ const deadline = Date.now() + 2 * 60 * 1000;
301
+ let previousStatus = "";
302
+
303
+ while (Date.now() < deadline) {
304
+ const result = await getLoginQrcodeStatus(session.qrcodeId, env);
305
+ if (result.status !== previousStatus) {
306
+ previousStatus = result.status;
307
+ await onUpdate({ type: "status", status: result.status });
308
+ }
309
+ if (["expired", "cancelled", "canceled", "error"].includes(result.status)) {
310
+ throw new Error(result.status === "expired" ? "二维码已过期" : "扫码登录已结束");
311
+ }
312
+ if (result.status === "success") {
313
+ const qdToken = result.token;
314
+ if (!qdToken) throw new Error("扫码登录未返回登录态");
315
+ const user = await getQiandaoUser(qdToken, env);
316
+ await exchangeQiandaoLogin(qdToken, user, env);
317
+ return { ...session, status: "success" };
318
+ }
319
+ await new Promise((resolve) => setTimeout(resolve, 1500));
320
+ }
321
+ throw new Error("二维码登录超时,请重新登录");
322
+ };
323
+
324
+ export const isAuthRequiredError = (error) => error?.code === "AUTH_REQUIRED";
325
+
326
+ export const ensureAuth = async (env = "prod") => {
327
+ const token = getToken(env);
328
+ if (!token) {
329
+ throw Object.assign(new Error("未授权,执行 qdmp-cli login 完成登录"), {
330
+ code: "AUTH_REQUIRED",
331
+ });
332
+ }
333
+ try {
334
+ await request("/mp/v1/user/me", {}, env);
335
+ return token;
336
+ } catch (error) {
337
+ if (isAuthRequiredError(error)) clearToken(env);
338
+ throw error;
339
+ }
340
+ };
106
341
  /**
107
342
  * 获取用户信息
108
343
  * @returns {Promise} 用户信息
package/index.js CHANGED
@@ -16,6 +16,7 @@ import {
16
16
  buildAction,
17
17
  } from "./actions.js";
18
18
  import { printLogo } from "./utils/logHandler.js";
19
+ import { openAPIApplyURLAction } from "./openapi.js";
19
20
  // import.meta.url: esm模块化中的属性,获取模块文件的绝对地址
20
21
  // __dirname: 获取模块文件所在目录的绝对路径,由nodejs在每个模块注入的特殊变量,只能在commonjs中使用
21
22
  // process.cwd(): 获取当前执行nodejs命令的当前工作目录
@@ -51,7 +52,7 @@ program
51
52
  program
52
53
  .command("login")
53
54
  .description("登录")
54
- .option("-e, --env <env>", "当前环境")
55
+ .option("-e, --env <env>", "登录环境:prod 或 dev", "prod")
55
56
  .action(loginAction);
56
57
  program
57
58
  .command("getMe")
@@ -69,4 +70,18 @@ program
69
70
  .option("-e, --env <env>", "当前环境")
70
71
  .option("-d, --description <description>", "版本描述(选填,200字以内)")
71
72
  .action(uploadAction);
73
+
74
+ const openapi = program
75
+ .command("openapi")
76
+ .description("OpenAPI 使用清单与权限申请工具");
77
+
78
+ openapi
79
+ .command("apply-url")
80
+ .description("根据代码使用清单生成权限申请页面地址")
81
+ .option("-m, --manifest <path>", "OpenAPI 使用清单", ".qdmp/openapi-usage.json")
82
+ .option("-e, --env <env>", "环境:prod 或 dev", "prod")
83
+ .option("-a, --app-id <appId>", "应用 ID")
84
+ .option("--open-platform-url <url>", "开放平台前端地址(本地联调使用)")
85
+ .option("--json", "输出结构化 JSON")
86
+ .action(openAPIApplyURLAction);
72
87
  program.parse(process.argv);
package/openapi.js ADDED
@@ -0,0 +1,84 @@
1
+ import { createOpenAPIApplicationLink, getAllOpenAPIResources } from "./api.js";
2
+ import { resolveAppId } from "./utils/common.js";
3
+ import {
4
+ classifyOpenAPIResources,
5
+ readOpenAPIManifest,
6
+ resolveOpenAPIManifest,
7
+ } from "./utils/openapiManifest.js";
8
+ import { error, info, success, warn } from "./utils/logHandler.js";
9
+ import { ensureInteractiveAuth } from "./utils/authFlow.js";
10
+
11
+ const openPlatformBaseURL = (env, override) => {
12
+ const baseURL = override?.trim().replace(/\/+$/, "")
13
+ || (env === "prod" ? "https://open.qiandao.com" : "https://dev-open.qiandao.com");
14
+ return baseURL;
15
+ };
16
+
17
+ export const prepareOpenAPIApplication = async ({
18
+ appId,
19
+ env = "prod",
20
+ manifest = ".qdmp/openapi-usage.json",
21
+ openPlatformUrl,
22
+ cwd = process.cwd(),
23
+ }) => {
24
+ const resolvedAppId = String(appId || resolveAppId(cwd, env)).trim();
25
+ if (!resolvedAppId) {
26
+ throw new Error("未找到 appId,请使用 --app-id 指定,或检查 qdmp-config.json/frontend/qdmp.json");
27
+ }
28
+ const usage = readOpenAPIManifest(manifest, cwd);
29
+ const catalog = await getAllOpenAPIResources(resolvedAppId, env);
30
+ const matched = resolveOpenAPIManifest(usage.openapis, catalog);
31
+ const classified = classifyOpenAPIResources(matched);
32
+
33
+ let queryKey = "";
34
+ let expiresAt = 0;
35
+ if (classified.required.length > 0) {
36
+ const link = await createOpenAPIApplicationLink(resolvedAppId, classified.required, env);
37
+ queryKey = link.queryKey;
38
+ expiresAt = Number(link.expiresAt || 0);
39
+ if (link.resourceIds.map(String).join(",") !== classified.required.join(",")) {
40
+ throw new Error("短链返回的 OpenAPI ID 与请求不一致,已停止生成申请地址");
41
+ }
42
+ }
43
+
44
+ const baseURL = openPlatformBaseURL(env, openPlatformUrl);
45
+ const url = new URL(`/apps/${encodeURIComponent(resolvedAppId)}/capabilities`, `${baseURL}/`);
46
+ if (queryKey) url.searchParams.set("requiredApis", queryKey);
47
+ return {
48
+ appId: resolvedAppId,
49
+ manifest: usage.path,
50
+ matchedResourceIds: matched.map((item) => String(item.resource.resourceId)),
51
+ requiredResourceIds: classified.required,
52
+ grantedResourceIds: classified.granted,
53
+ pendingResourceIds: classified.pending,
54
+ noPermissionRequiredResourceIds: classified.noPermissionRequired,
55
+ unavailableResourceIds: classified.unavailable,
56
+ queryKey,
57
+ expiresAt,
58
+ url: url.toString(),
59
+ };
60
+ };
61
+
62
+ export const openAPIApplyURLAction = async (option) => {
63
+ try {
64
+ await ensureInteractiveAuth(option.env);
65
+ const result = await prepareOpenAPIApplication(option);
66
+ if (option.json) {
67
+ console.log(JSON.stringify(result, null, 2));
68
+ return;
69
+ }
70
+ info(`已匹配 ${result.matchedResourceIds.length} 个 OpenAPI`);
71
+ if (result.grantedResourceIds.length > 0) info(`${result.grantedResourceIds.length} 个已开通`);
72
+ if (result.pendingResourceIds.length > 0) info(`${result.pendingResourceIds.length} 个正在审核`);
73
+ if (result.noPermissionRequiredResourceIds.length > 0) info(`${result.noPermissionRequiredResourceIds.length} 个无需申请`);
74
+ if (result.unavailableResourceIds.length > 0) warn(`${result.unavailableResourceIds.length} 个当前不可申请`);
75
+ if (result.requiredResourceIds.length === 0) {
76
+ success("当前没有需要手动申请的 OpenAPI");
77
+ return;
78
+ }
79
+ success(`已生成权限申请地址(${result.requiredResourceIds.length} 个接口):`);
80
+ console.log(result.url);
81
+ } catch (cause) {
82
+ error(cause?.message || String(cause));
83
+ }
84
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qdmp-cli",
3
- "version": "0.1.24",
3
+ "version": "0.1.27",
4
4
  "description": "qdmp-cli",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -9,6 +9,7 @@
9
9
  "api.js",
10
10
  "constants.js",
11
11
  "index.js",
12
+ "openapi.js",
12
13
  "utils/"
13
14
  ],
14
15
  "scripts": {
@@ -28,16 +29,25 @@
28
29
  "dependencies": {
29
30
  "@dimina/compiler": "1.0.17",
30
31
  "adm-zip": "^0.5.16",
32
+ "ajax-hook": "2.1.3",
31
33
  "chalk": "^5.4.1",
32
34
  "commander": "^14.0.0",
33
35
  "figlet": "^1.8.1",
34
36
  "inquirer": "^12.6.3",
35
37
  "is-unicode-supported": "^2.1.0",
38
+ "js-base64": "3.7.8",
36
39
  "ora": "^8.2.0",
40
+ "qrcode-terminal": "^0.12.0",
41
+ "query-string": "7.1.3",
37
42
  "shelljs": "^0.10.0",
38
- "table": "^6.9.0"
43
+ "table": "^6.9.0",
44
+ "use-singleton": "0.3.0"
39
45
  },
40
46
  "engines": {
41
47
  "node": "^20.19.0 || >=22.12.0"
42
- }
43
- }
48
+ },
49
+ "publishConfig": {
50
+ "registry": "https://registry.npmjs.org/",
51
+ "access": "public"
52
+ }
53
+ }
@@ -0,0 +1,97 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { error, info, warn } from "./logHandler.js";
3
+ import { inquirerAccountPassword, inquirerLoginMethod } from "./interactive.js";
4
+ import {
5
+ ensureAuth,
6
+ isAuthRequiredError,
7
+ login,
8
+ loginByQrcode,
9
+ loginByPlatformURL,
10
+ } from "../api.js";
11
+
12
+ import { normalizeLoginEnv } from "./loginEnvironment.js";
13
+
14
+ const openLoginURL = (url) => {
15
+ const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
16
+ const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
17
+ try {
18
+ execFileSync(command, args, { stdio: "ignore" });
19
+ return true;
20
+ } catch {
21
+ return false;
22
+ }
23
+ };
24
+
25
+ const renderTerminalQRCode = async (url) => {
26
+ try {
27
+ const module = await import("qrcode-terminal");
28
+ const renderer = module.default || module;
29
+ renderer.generate(url, { small: true });
30
+ return true;
31
+ } catch {
32
+ warn("当前环境未安装二维码终端渲染依赖,请复制下方登录 URL 完成登录。");
33
+ return false;
34
+ }
35
+ };
36
+
37
+ const printLoginURL = (url) => {
38
+ console.log("\n登录地址(可复制):");
39
+ console.log(url);
40
+ console.log("完成登录后,CLI 会自动接收登录结果,无需回到终端确认。\n");
41
+ };
42
+
43
+ export const interactiveLogin = async (env = "prod") => {
44
+ normalizeLoginEnv(env);
45
+ info(`当前登录环境:${env}`);
46
+ const method = await inquirerLoginMethod();
47
+ if (method === "password") {
48
+ const { account, password } = await inquirerAccountPassword();
49
+ await login(account, password, env);
50
+ return;
51
+ }
52
+
53
+ const onUpdate = async (event) => {
54
+ if (event.type === "ready") {
55
+ if (method === "qrcode") {
56
+ console.log("\n请使用千岛 App 扫描下方二维码登录:\n");
57
+ const rendered = await renderTerminalQRCode(event.url);
58
+ printLoginURL(event.url);
59
+ if (!rendered) {
60
+ info("二维码未能在终端显示,请使用上面的 URL 登录。");
61
+ }
62
+ } else {
63
+ printLoginURL(event.url);
64
+ if (openLoginURL(event.url)) {
65
+ info("已尝试打开浏览器,请完成登录;成功后 CLI 会自动退出。");
66
+ } else {
67
+ info("未能自动打开浏览器,请复制上面的 URL 手动打开。");
68
+ }
69
+ }
70
+ }
71
+ if (event.type === "status" && event.status === "scanned") {
72
+ info("已扫码,请在手机或浏览器中确认登录。");
73
+ }
74
+ };
75
+
76
+ if (method === "qrcode") {
77
+ // 与 miniapp-h5 的 QRCodeLogin 保持一致:App 扫描后直接进入确认登录弹框。
78
+ await loginByQrcode(env, onUpdate);
79
+ } else {
80
+ await loginByPlatformURL(env, onUpdate);
81
+ }
82
+ };
83
+
84
+ export const ensureInteractiveAuth = async (env = "prod") => {
85
+ try {
86
+ return await ensureAuth(env);
87
+ } catch (cause) {
88
+ if (!isAuthRequiredError(cause)) throw cause;
89
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
90
+ throw new Error("当前命令需要登录,请在支持交互输入的终端中重新执行,或设置 QDMP_TOKEN");
91
+ }
92
+ await interactiveLogin(env);
93
+ return ensureAuth(env);
94
+ }
95
+ };
96
+
97
+ export const reportAuthError = (cause) => error(cause?.message || String(cause));
@@ -1,26 +1,40 @@
1
1
  import fs from "fs";
2
2
  import path from "path";
3
3
  import os from "os";
4
+ import { normalizeLoginEnv } from "./loginEnvironment.js";
4
5
  import { error, info } from "./logHandler.js";
5
6
 
6
7
  // 获取配置文件路径
7
8
  const getConfigPath = () => {
8
- return path.join(os.homedir(), ".qdtool.config.json");
9
+ return process.env.QDMP_CONFIG_PATH?.trim() ||
10
+ path.join(os.homedir(), ".qdtool.config.json");
9
11
  };
10
12
 
11
- /**
12
- * 保存认证令牌
13
- * @param {string} token - 认证令牌
14
- */
15
- export const saveToken = (token) => {
13
+ const readConfig = () => {
16
14
  const configPath = getConfigPath();
17
- const config = { token };
15
+ try {
16
+ if (!fs.existsSync(configPath)) return {};
17
+ return JSON.parse(fs.readFileSync(configPath, "utf8"));
18
+ } catch {
19
+ return {};
20
+ }
21
+ };
18
22
 
23
+ export const saveToken = (token, env = "prod") => {
24
+ normalizeLoginEnv(env);
25
+ const configPath = getConfigPath();
26
+ const config = readConfig();
27
+ const nextConfig = {
28
+ ...config,
29
+ tokens: { ...config.tokens, [env]: token },
30
+ ...(env === "prod" ? { token } : {}),
31
+ };
19
32
  try {
20
- fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
21
- info("🔑 认证令牌已安全存储");
33
+ fs.writeFileSync(configPath, JSON.stringify(nextConfig, null, 2), { mode: 0o600 });
34
+ fs.chmodSync(configPath, 0o600);
35
+ info(`🔑 ${env} 环境认证令牌已安全存储`);
22
36
  } catch (err) {
23
- error("保存认证令牌失败:", err.message);
37
+ throw new Error(`保存 ${env} 环境认证令牌失败:${err.message}`);
24
38
  }
25
39
  };
26
40
 
@@ -28,16 +42,24 @@ export const saveToken = (token) => {
28
42
  * 获取认证令牌
29
43
  * @returns {string} - 认证令牌
30
44
  */
31
- export const getToken = () => {
45
+ export const getToken = (env = "prod") => {
46
+ normalizeLoginEnv(env);
47
+ const environmentToken = process.env.QDMP_TOKEN?.trim();
48
+ if (environmentToken) return environmentToken;
49
+ const config = readConfig();
50
+ return config.tokens?.[env] || (env === "prod" ? config.token : null) || null;
51
+ };
52
+
53
+ export const clearToken = (env = "prod") => {
54
+ if (process.env.QDMP_TOKEN?.trim()) return;
55
+ normalizeLoginEnv(env);
32
56
  const configPath = getConfigPath();
57
+ const config = readConfig();
58
+ if (config.tokens) delete config.tokens[env];
59
+ if (env === "prod") delete config.token;
33
60
  try {
34
- if (fs.existsSync(configPath)) {
35
- const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
36
- return config.token || null;
37
- } else {
38
- error("认证令牌不存在,请重新登录");
39
- }
61
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
40
62
  } catch (err) {
41
- error("读取认证令牌失败,请重新登录:", err.message);
63
+ error(`清理 ${env} 环境认证令牌失败:${err.message}`);
42
64
  }
43
65
  };
package/utils/common.js CHANGED
@@ -33,6 +33,28 @@ export function getAppId(env = "prod") {
33
33
  }
34
34
  }
35
35
 
36
+ export function resolveAppId(cwd = process.cwd(), env = "prod") {
37
+ const fieldNames = env === "prod" ? ["appId"] : ["devAppId", "appId"];
38
+ const configPaths = [
39
+ path.join(cwd, "qdmp-config.json"),
40
+ path.join(cwd, "frontend", "qdmp.json"),
41
+ path.join(cwd, "qdmp.json"),
42
+ ];
43
+ for (const configPath of configPaths) {
44
+ if (!fs.existsSync(configPath)) continue;
45
+ try {
46
+ const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
47
+ for (const fieldName of fieldNames) {
48
+ const appId = String(config?.[fieldName] || "").trim();
49
+ if (appId) return appId;
50
+ }
51
+ } catch {
52
+ // 继续检查兼容配置,最终由调用方给出统一错误。
53
+ }
54
+ }
55
+ return "";
56
+ }
57
+
36
58
  /** 验证URL是否可访问
37
59
  * @param {*} url
38
60
  */
@@ -35,6 +35,22 @@ export const inquirerAccountPassword = async (message) => {
35
35
  return { account, password };
36
36
  };
37
37
 
38
+ export const inquirerLoginMethod = async () => {
39
+ const { method } = await inquirer.prompt([
40
+ {
41
+ type: "list",
42
+ name: "method",
43
+ message: "请选择登录方式",
44
+ choices: [
45
+ { name: "扫码登录(终端显示二维码)", value: "qrcode" },
46
+ { name: "手动登录 URL(显示可复制地址)", value: "url" },
47
+ { name: "账号密码登录", value: "password" },
48
+ ],
49
+ },
50
+ ]);
51
+ return method;
52
+ };
53
+
38
54
  /**
39
55
  * 输入版本描述
40
56
  * @returns {Promise<string>} 版本描述
@@ -0,0 +1,107 @@
1
+ import { createServer } from "node:http";
2
+ import { randomBytes } from "node:crypto";
3
+ import { normalizeLoginEnv, platformLoginURL } from "./loginEnvironment.js";
4
+
5
+ // Token travels in the fragment (never in HTTP URLs). The landing page removes
6
+ // it from browser history and submits it to this same-origin, single-use endpoint.
7
+ export const startLocalhostLogin = async ({ env = "prod", timeoutMs = 120000, acceptToken }) => {
8
+ normalizeLoginEnv(env);
9
+ const state = randomBytes(32).toString("hex");
10
+ const nonce = randomBytes(24).toString("base64");
11
+ let origin;
12
+ let settled = false;
13
+ let accepting = false;
14
+ let timer;
15
+ let resolveResult;
16
+ let rejectResult;
17
+ const result = new Promise((resolve, reject) => {
18
+ resolveResult = resolve;
19
+ rejectResult = reject;
20
+ });
21
+ // The timeout may fire while the browser opener is still running.
22
+ result.catch(() => {});
23
+ const finish = (error) => {
24
+ if (settled) return;
25
+ settled = true;
26
+ clearTimeout(timer);
27
+ process.removeListener("SIGINT", onInterrupt);
28
+ process.removeListener("SIGTERM", onInterrupt);
29
+ server.close();
30
+ server.closeIdleConnections();
31
+ if (error) rejectResult(error);
32
+ else resolveResult({ status: "confirmed" });
33
+ };
34
+ const onInterrupt = () => finish(new Error("已取消网页登录"));
35
+ const server = createServer(async (req, res) => {
36
+ res.setHeader("Cache-Control", "no-store");
37
+ res.setHeader("Referrer-Policy", "no-referrer");
38
+ res.setHeader("X-Content-Type-Options", "nosniff");
39
+ res.setHeader("Content-Security-Policy", `default-src 'none'; style-src 'nonce-${nonce}'; script-src 'nonce-${nonce}'; connect-src 'self'; frame-ancestors 'none'; base-uri 'none'; form-action 'none'`);
40
+ const reply = (status, text) => { res.writeHead(status); res.end(text); };
41
+ if (req.headers.host !== new URL(origin).host) return reply(403, "Invalid host");
42
+ if (settled) return reply(410, "Login expired");
43
+ const url = new URL(req.url, origin);
44
+ if (req.method === "GET" && url.pathname === "/callback" && url.searchParams.get("state") === state) {
45
+ res.setHeader("Content-Type", "text/html; charset=utf-8");
46
+ return reply(200, `<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>千岛 CLI 登录</title><style nonce="${nonce}">
47
+ * { box-sizing: border-box; }
48
+ html, body { margin: 0; min-height: 100%; }
49
+ body { background: #fff; color: #202123; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
50
+ .page { min-height: 100vh; display: flex; flex-direction: column; }
51
+ .brand { padding: 24px 28px; font-size: 18px; font-weight: 650; letter-spacing: -.02em; }
52
+ .content { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 24px; transform: translateY(-5vh); text-align: center; }
53
+ .mark { width: 42px; height: 42px; margin-bottom: 18px; border: 1px solid #e5e7eb; border-radius: 50%; color: #6b7280; display: grid; place-items: center; font-size: 22px; line-height: 1; }
54
+ .mark.success { border-color: #b7e4c7; background: #f0fdf4; color: #15803d; }
55
+ .mark.error { border-color: #fecaca; background: #fef2f2; color: #dc2626; }
56
+ .status { margin: 0; font-size: 16px; line-height: 1.5; font-weight: 500; }
57
+ .status.success { color: #202123; }
58
+ .status.error { color: #b91c1c; }
59
+ .hint { margin: 10px 0 0; color: #6b7280; font-size: 14px; line-height: 1.5; }
60
+ </style></head><body><div class="page"><header class="brand">千岛开放平台</header><main class="content"><div id="mark" class="mark" aria-hidden="true">…</div><p id="status" class="status" aria-live="polite">正在完成登录…</p><p id="hint" class="hint">请稍候,登录状态正在同步到 CLI。</p></main></div><script nonce="${nonce}">
61
+ const token = new URLSearchParams(location.hash.slice(1)).get('token');
62
+ history.replaceState(null, '', '/callback');
63
+ fetch('/complete', {method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({state: '${state}', env: '${env}', token})})
64
+ .then(async response => { if (!response.ok) throw new Error(); document.getElementById('mark').textContent = '✓'; document.getElementById('mark').className = 'mark success'; document.getElementById('status').textContent = '你已登录,可以关闭此标签页'; document.getElementById('status').className = 'status success'; document.getElementById('hint').textContent = 'CLI 已获取登录状态,请返回终端继续操作。'; })
65
+ .catch(() => { document.getElementById('mark').textContent = '!'; document.getElementById('mark').className = 'mark error'; document.getElementById('status').textContent = '登录失败或已超时'; document.getElementById('status').className = 'status error'; document.getElementById('hint').textContent = '请返回终端重新发起登录。'; });
66
+ </script></body></html>`);
67
+ }
68
+ if (req.method !== "POST" || url.pathname !== "/complete") return reply(404, "Not found");
69
+ if (req.headers.origin !== origin || req.headers["content-type"] !== "application/json") return reply(403, "Invalid origin");
70
+ if (accepting) return reply(409, "Login in progress");
71
+ try {
72
+ let body = "";
73
+ for await (const chunk of req) {
74
+ body += chunk;
75
+ if (Buffer.byteLength(body) > 16384) return reply(413, "Request too large");
76
+ }
77
+ const data = JSON.parse(body);
78
+ if (data.state !== state || data.env !== env || typeof data.token !== "string" || !data.token.trim() || data.token.length > 8192) {
79
+ return reply(400, "Invalid login callback");
80
+ }
81
+ if (settled || accepting) return reply(409, "Login no longer available");
82
+ accepting = true;
83
+ await acceptToken(data.token, () => !settled);
84
+ if (settled) return reply(410, "Login expired");
85
+ reply(200, "OK");
86
+ finish();
87
+ } catch (error) {
88
+ reply(400, "Login failed");
89
+ if (accepting) finish(error);
90
+ }
91
+ });
92
+ server.requestTimeout = 10000;
93
+ server.headersTimeout = 10000;
94
+ await new Promise((resolve, reject) => {
95
+ server.once("error", reject);
96
+ server.listen(0, "127.0.0.1", resolve);
97
+ });
98
+ // Use the stable localhost name in the browser callback. The listener is
99
+ // still bound to IPv4 loopback, so the callback never exposes a LAN port.
100
+ origin = `http://localhost:${server.address().port}`;
101
+ process.once("SIGINT", onInterrupt);
102
+ process.once("SIGTERM", onInterrupt);
103
+ timer = setTimeout(() => finish(new Error("网页登录超时,请重新登录")), timeoutMs);
104
+ const url = new URL(platformLoginURL(env));
105
+ url.search = new URLSearchParams({ cli_callback: `${origin}/callback`, cli_state: state, _env: env }).toString();
106
+ return { url: url.toString(), result, close: () => finish(new Error("已关闭网页登录会话")) };
107
+ };
@@ -0,0 +1,11 @@
1
+ export const normalizeLoginEnv = (env = "prod") => {
2
+ if (env !== "prod" && env !== "dev") {
3
+ throw new Error("登录环境仅支持 prod 或 dev");
4
+ }
5
+ return env;
6
+ };
7
+
8
+ export const platformLoginURL = (env = "prod") =>
9
+ normalizeLoginEnv(env) === "dev"
10
+ ? "https://dev-open.qiandao.com/login"
11
+ : "https://open.qiandao.com/login";
@@ -0,0 +1,128 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+
4
+ const HTTP_METHOD_PATTERN = /^(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)$/i;
5
+
6
+ const normalizePath = (value) => {
7
+ const raw = String(value || "").trim();
8
+ if (!raw) return "";
9
+ try {
10
+ if (/^https?:\/\//i.test(raw)) return new URL(raw).pathname.replace(/\/+$/, "") || "/";
11
+ } catch {
12
+ return "";
13
+ }
14
+ return (raw.split(/[?#]/, 1)[0].replace(/\/+$/, "") || "/");
15
+ };
16
+
17
+ const normalizeDescriptor = (entry, index) => {
18
+ if (typeof entry === "string") {
19
+ const value = entry.trim();
20
+ const separator = value.indexOf(" ");
21
+ if (separator > 0 && HTTP_METHOD_PATTERN.test(value.slice(0, separator))) {
22
+ return {
23
+ method: value.slice(0, separator).toUpperCase(),
24
+ path: normalizePath(value.slice(separator + 1)),
25
+ };
26
+ }
27
+ if (value) return { showCode: value };
28
+ }
29
+ if (entry && typeof entry === "object") {
30
+ const descriptor = {
31
+ method: String(entry.method || "").trim().toUpperCase(),
32
+ path: normalizePath(entry.path || entry.url),
33
+ showCode: String(entry.showCode || "").trim(),
34
+ apiCode: String(entry.apiCode || "").trim(),
35
+ };
36
+ if ((descriptor.method && descriptor.path) || descriptor.showCode || descriptor.apiCode) {
37
+ if (descriptor.method && !HTTP_METHOD_PATTERN.test(descriptor.method)) {
38
+ throw new Error(`openapis[${index}].method 不是有效 HTTP 方法`);
39
+ }
40
+ return descriptor;
41
+ }
42
+ }
43
+ throw new Error(`openapis[${index}] 缺少 method + path、showCode 或 apiCode`);
44
+ };
45
+
46
+ export const readOpenAPIManifest = (manifestPath, cwd = process.cwd()) => {
47
+ const absolutePath = path.resolve(cwd, manifestPath);
48
+ if (!fs.existsSync(absolutePath)) {
49
+ throw new Error(`未找到 OpenAPI 使用清单:${absolutePath}`);
50
+ }
51
+ let manifest;
52
+ try {
53
+ manifest = JSON.parse(fs.readFileSync(absolutePath, "utf8"));
54
+ } catch (error) {
55
+ throw new Error(`OpenAPI 使用清单不是有效 JSON:${error.message}`);
56
+ }
57
+ if (manifest?.schemaVersion !== 1 || !Array.isArray(manifest?.openapis)) {
58
+ throw new Error("OpenAPI 使用清单必须包含 schemaVersion: 1 和 openapis 数组");
59
+ }
60
+ if (manifest.openapis.length === 0) {
61
+ throw new Error("OpenAPI 使用清单中的 openapis 不能为空");
62
+ }
63
+ return {
64
+ path: absolutePath,
65
+ openapis: manifest.openapis.map(normalizeDescriptor),
66
+ };
67
+ };
68
+
69
+ const sameText = (left, right) => String(left || "").trim() === String(right || "").trim();
70
+
71
+ const resourceMatches = (descriptor, item) => {
72
+ const resource = item?.resource || {};
73
+ if (descriptor.showCode && !sameText(descriptor.showCode, resource.showCode)) return false;
74
+ if (descriptor.apiCode && !sameText(descriptor.apiCode, resource.apiCode)) return false;
75
+ if (descriptor.method && descriptor.method !== String(resource.method || "").trim().toUpperCase()) return false;
76
+ if (descriptor.path && descriptor.path !== normalizePath(resource.path)) return false;
77
+ return true;
78
+ };
79
+
80
+ export const resolveOpenAPIManifest = (descriptors, catalog) => {
81
+ const resolved = [];
82
+ const seenResourceIDs = new Set();
83
+ for (const descriptor of descriptors) {
84
+ const matches = catalog.filter((item) => resourceMatches(descriptor, item));
85
+ if (matches.length === 0) {
86
+ throw new Error(`未找到 OpenAPI:${JSON.stringify(descriptor)}`);
87
+ }
88
+ if (matches.length > 1) {
89
+ throw new Error(`OpenAPI 匹配不唯一,请补充 method + path 或唯一编码:${JSON.stringify(descriptor)}`);
90
+ }
91
+ const item = matches[0];
92
+ const resourceId = String(item?.resource?.resourceId || "");
93
+ if (!resourceId) throw new Error(`OpenAPI 缺少 resourceId:${JSON.stringify(descriptor)}`);
94
+ if (seenResourceIDs.has(resourceId)) continue;
95
+ seenResourceIDs.add(resourceId);
96
+ resolved.push(item);
97
+ }
98
+ return resolved;
99
+ };
100
+
101
+ export const classifyOpenAPIResources = (items) => {
102
+ const result = {
103
+ required: [],
104
+ granted: [],
105
+ pending: [],
106
+ noPermissionRequired: [],
107
+ unavailable: [],
108
+ };
109
+ for (const item of items) {
110
+ const resource = item.resource || {};
111
+ const appContext = item.appContext || {};
112
+ const resourceId = String(resource.resourceId || "");
113
+ const accessStatus = String(appContext.accessStatus || "").toUpperCase();
114
+ const applyStatus = String(appContext.applyStatus || "").toUpperCase();
115
+ if (String(resource.permissionPolicy || "").toUpperCase() === "NO_NEED_PERMISSION") {
116
+ result.noPermissionRequired.push(resourceId);
117
+ } else if (String(appContext.grantStatus || "").toUpperCase() === "GRANTED" || accessStatus === "GRANTED") {
118
+ result.granted.push(resourceId);
119
+ } else if (accessStatus === "PENDING" || applyStatus.includes("PENDING")) {
120
+ result.pending.push(resourceId);
121
+ } else if (appContext.capability?.canApply === true) {
122
+ result.required.push(resourceId);
123
+ } else {
124
+ result.unavailable.push(resourceId);
125
+ }
126
+ }
127
+ return result;
128
+ };
@@ -0,0 +1,57 @@
1
+ import { fileURLToPath } from "node:url";
2
+
3
+ let signerPromise;
4
+
5
+ const installBrowserGlobals = () => {
6
+ if (!globalThis.window) {
7
+ globalThis.window = {
8
+ location: {
9
+ protocol: "https:",
10
+ host: "open.qiandao.com",
11
+ hostname: "open.qiandao.com",
12
+ },
13
+ };
14
+ }
15
+ if (!globalThis.document) globalThis.document = {};
16
+ if (globalThis.XMLHttpRequest) return;
17
+
18
+ // @frontend/pigeon 在初始化时会读取 api.qiandao.cn/now 获取时钟偏移。
19
+ // CLI 使用本地时间即可完成签名,避免为一个时间接口引入额外网络依赖。
20
+ globalThis.XMLHttpRequest = class NodeXMLHttpRequest {
21
+ open(method, url, async = true) {
22
+ this.method = method;
23
+ this.url = url;
24
+ this.async = async;
25
+ }
26
+
27
+ setRequestHeader() {}
28
+
29
+ send() {
30
+ queueMicrotask(() => {
31
+ const response = JSON.stringify({
32
+ data: { timestamp: Math.floor(Date.now() / 1000) },
33
+ });
34
+ this.status = 200;
35
+ this.readyState = 4;
36
+ this.response = response;
37
+ this.responseText = response;
38
+ this.onreadystatechange?.();
39
+ this.onload?.();
40
+ this.onloadend?.();
41
+ });
42
+ }
43
+
44
+ abort() {}
45
+ };
46
+ };
47
+
48
+ export const signQiandaoRequest = async (url) => {
49
+ if (!/^https?:\/\/(?:dev-)?api\.qiandao\.com\//.test(url)) return {};
50
+ if (!signerPromise) {
51
+ installBrowserGlobals();
52
+ const signerURL = new URL("./vendor/pigeon/browser-base.esm.js", import.meta.url);
53
+ signerPromise = import(fileURLToPath(signerURL)).then(({ sign }) => sign);
54
+ }
55
+ const sign = await signerPromise;
56
+ return sign(url);
57
+ };
@@ -0,0 +1 @@
1
+ const t=g;!function(t,n){const r=g,e=tt();for(;;)try{if(639296===-parseInt(r(596))/1*(parseInt(r(416))/2)+parseInt(r(585))/3+-parseInt(r(538))/4*(parseInt(r(678))/5)+-parseInt(r(499))/6+-parseInt(r(526))/7+-parseInt(r(649))/8+-parseInt(r(552))/9*(-parseInt(r(558))/10))break;e.push(e.shift())}catch(t){e.push(e.shift())}}();var n=function(t,{withKey:n=!1,immediate:r=!1}={}){const e={};let o,a=e;function s(r){return(a===e||(s=r,!(!n||void 0===s||s===o)))&&(o=r,a=t(o)),a;var s}return r&&s(),s},r={},e=n=>encodeURIComponent(n)[t(588)](/[!'()*]/g,(n=>"%"+n[t(543)](0)[t(482)](16)[t(458)]())),o=new RegExp("%[a-f0-9]{2}","gi"),a=new RegExp("(%[a-f0-9]{2})+","gi");function s(n,r){const e=t;try{return decodeURIComponent(n[e(688)](""))}catch(t){}if(1===n[e(701)])return n;r=r||1;var o=n.slice(0,r),a=n[e(628)](r);return Array[e(689)][e(494)].call([],s(o),s(a))}function c(n){const r=t;try{return decodeURIComponent(n)}catch(t){for(var e=n.match(o),a=1;a<e[r(701)];a++)e=(n=s(e,a)[r(688)]("")).match(o);return n}}var i=function(n){const r=t;if("string"!=typeof n)throw new TypeError(r(432)+typeof n+"`");try{return n=n[r(588)](/\+/g," "),decodeURIComponent(n)}catch(r){return function(n){const r=t;for(var e={"%FE%FF":"\ufffd\ufffd","%FF%FE":"\ufffd\ufffd"},o=a[r(507)](n);o;){try{e[o[0]]=decodeURIComponent(o[0])}catch(t){var s=c(o[0]);s!==o[0]&&(e[o[0]]=s)}o=a[r(507)](n)}e["%C2"]="\ufffd";for(var i=Object[r(677)](e),u=0;u<i[r(701)];u++){var p=i[u];n=n[r(588)](new RegExp(p,"g"),e[p])}return n}(n)}},u=(n,r)=>{const e=t;if(typeof n!==e(444)||typeof r!==e(444))throw new TypeError("Expected the arguments to be of type `string`");if(""===r)return[n];const o=n[e(631)](r);return-1===o?[n]:[n[e(628)](0,o),n[e(628)](o+r[e(701)])]},p=function(n,r){const e=t;for(var o={},a=Object[e(677)](n),s=Array[e(452)](r),c=0;c<a[e(701)];c++){var i=a[c],u=n[i];(s?-1!==r.indexOf(i):r(i,u,n))&&(o[i]=u)}return o};!function(n){const r=t,o=e,a=i,s=u,c=p,f=Symbol(r(517));function l(t){const n=r;if(typeof t!==n(444)||1!==t[n(701)])throw new TypeError(n(504))}function I(t,n){return n[r(424)]?n.strict?o(t):encodeURIComponent(t):t}function y(t,n){return n[r(437)]?a(t):t}function d(t){const n=r;return Array[n(452)](t)?t.sort():"object"==typeof t?d(Object.keys(t)).sort(((t,n)=>Number(t)-Number(n)))[n(582)]((n=>t[n])):t}function m(t){const n=t[r(631)]("#");return-1!==n&&(t=t.slice(0,n)),t}function g(t){const n=r,e=(t=m(t))[n(631)]("?");return-1===e?"":t[n(628)](e+1)}function h(t,n){const e=r;return n[e(454)]&&!Number[e(629)](Number(t))&&typeof t===e(444)&&""!==t[e(706)]()?t=Number(t):n[e(670)]&&null!==t&&("true"===t[e(563)]()||"false"===t[e(563)]())&&(t=t[e(563)]()===e(698)),t}function b(t,n){const e=r;l((n=Object[e(682)]({decode:!0,sort:!0,arrayFormat:"none",arrayFormatSeparator:",",parseNumbers:!1,parseBooleans:!1},n))[e(434)]);const o=function(t){const n=r;let e;switch(t[n(430)]){case n(428):return(t,r,o)=>{e=/\[(\d*)\]$/[n(507)](t),t=t.replace(/\[\d*\]$/,""),e?(void 0===o[t]&&(o[t]={}),o[t][e[1]]=r):o[t]=r};case n(441):return(t,r,o)=>{const a=n;e=/(\[\])$/[a(507)](t),t=t.replace(/\[\]$/,""),e?void 0!==o[t]?o[t]=[][a(494)](o[t],r):o[t]=[r]:o[t]=r};case"colon-list-separator":return(t,r,o)=>{const a=n;e=/(:list)$/[a(507)](t),t=t[a(588)](/:list$/,""),e?void 0!==o[t]?o[t]=[].concat(o[t],r):o[t]=[r]:o[t]=r};case n(607):case n(667):return(r,e,o)=>{const a=n,s="string"==typeof e&&e[a(502)](t.arrayFormatSeparator),c=typeof e===a(444)&&!s&&y(e,t).includes(t[a(434)]);e=c?y(e,t):e;const i=s||c?e.split(t.arrayFormatSeparator).map((n=>y(n,t))):null===e?e:y(e,t);o[r]=i};case n(463):return(r,e,o)=>{const a=n,s=/(\[\])$/[a(549)](r);if(r=r[a(588)](/\[\]$/,""),!s)return void(o[r]=e?y(e,t):e);const c=null===e?[]:e[a(528)](t[a(434)])[a(582)]((n=>y(n,t)));void 0!==o[r]?o[r]=[].concat(o[r],c):o[r]=c};default:return(t,r,e)=>{const o=n;void 0!==e[t]?e[t]=[][o(494)](e[t],r):e[t]=r}}}(n),a=Object[e(660)](null);if(typeof t!==e(444))return a;if(!(t=t[e(706)]()[e(588)](/^[?#&]/,"")))return a;for(const r of t[e(528)]("&")){if(""===r)continue;let[t,c]=s(n[e(437)]?r[e(588)](/\+/g," "):r,"=");c=void 0===c?null:[e(607),e(667),e(463)][e(502)](n[e(430)])?c:y(c,n),o(y(t,n),c,a)}for(const t of Object.keys(a)){const r=a[t];if(typeof r===e(614)&&null!==r)for(const t of Object[e(677)](r))r[t]=h(r[t],n);else a[t]=h(r,n)}return!1===n[e(545)]?a:(!0===n.sort?Object[e(677)](a)[e(545)]():Object[e(677)](a)[e(545)](n.sort))[e(594)](((t,n)=>{const r=e,o=a[n];return Boolean(o)&&typeof o===r(614)&&!Array[r(452)](o)?t[n]=d(o):t[n]=o,t}),Object[e(660)](null))}n[r(663)]=g,n[r(630)]=b,n[r(643)]=(t,n)=>{const e=r;if(!t)return"";l((n=Object[e(682)]({encode:!0,strict:!0,arrayFormat:e(534),arrayFormatSeparator:","},n)).arrayFormatSeparator);const o=function(t){const n=r;switch(t.arrayFormat){case n(428):return r=>(e,o)=>{const a=n,s=e[a(701)];return void 0===o||t.skipNull&&null===o||t[a(615)]&&""===o?e:null===o?[...e,[I(r,t),"[",s,"]"][a(688)]("")]:[...e,[I(r,t),"[",I(s,t),"]=",I(o,t)].join("")]};case n(441):return r=>(e,o)=>{const a=n;return void 0===o||t[a(598)]&&null===o||t[a(615)]&&""===o?e:null===o?[...e,[I(r,t),"[]"][a(688)]("")]:[...e,[I(r,t),a(609),I(o,t)].join("")]};case n(571):return r=>(e,o)=>{const a=n;return void 0===o||t[a(598)]&&null===o||t[a(615)]&&""===o?e:null===o?[...e,[I(r,t),a(533)][a(688)]("")]:[...e,[I(r,t),a(533),I(o,t)][a(688)]("")]};case n(607):case n(667):case"bracket-separator":{const r=t.arrayFormat===n(463)?n(609):"=";return e=>(o,a)=>{const s=n;return void 0===a||t[s(598)]&&null===a||t.skipEmptyString&&""===a?o:(a=null===a?"":a,0===o[s(701)]?[[I(e,t),r,I(a,t)][s(688)]("")]:[[o,I(a,t)][s(688)](t[s(434)])])}}default:return r=>(e,o)=>{const a=n;return void 0===o||t.skipNull&&null===o||t[a(615)]&&""===o?e:null===o?[...e,I(r,t)]:[...e,[I(r,t),"=",I(o,t)][a(688)]("")]}}}(n),a={};for(const r of Object[e(677)](t))s=r,!(n[e(598)]&&null==t[s]||n[e(615)]&&""===t[s])&&(a[r]=t[r]);var s;const c=Object.keys(a);return!1!==n.sort&&c[e(545)](n[e(545)]),c[e(582)]((r=>{const a=e,s=t[r];return void 0===s?"":null===s?I(r,n):Array[a(452)](s)?0===s[a(701)]&&"bracket-separator"===n[a(430)]?I(r,n)+"[]":s.reduce(o(r),[]).join("&"):I(r,n)+"="+I(s,n)}))[e(696)]((t=>t[e(701)]>0))[e(688)]("&")},n[r(562)]=(t,n)=>{const e=r;n=Object[e(682)]({decode:!0},n);const[o,a]=s(t,"#");return Object[e(682)]({url:o.split("?")[0]||"",query:b(g(t),n)},n&&n.parseFragmentIdentifier&&a?{fragmentIdentifier:y(a,n)}:{})},n[r(648)]=(t,e)=>{const o=r;e=Object[o(682)]({encode:!0,strict:!0,[f]:!0},e);const a=m(t.url)[o(528)]("?")[0]||"",s=n[o(663)](t[o(489)]),c=n[o(630)](s,{sort:!1}),i=Object[o(682)](c,t[o(474)]);let u=n[o(643)](i,e);u&&(u="?"+u);let p=function(t){const n=r;let e="";const o=t[n(631)]("#");return-1!==o&&(e=t[n(628)](o)),e}(t[o(489)]);return t[o(690)]&&(p="#"+(e[f]?I(t[o(690)],e):t[o(690)])),""+a+u+p},n[r(676)]=(t,e,o)=>{const a=r;o=Object.assign({parseFragmentIdentifier:!0,[f]:!1},o);const{url:s,query:i,fragmentIdentifier:u}=n[a(562)](t,o);return n[a(648)]({url:s,query:c(i,e),fragmentIdentifier:u},o)},n[r(492)]=(t,e,o)=>{const a=r,s=Array.isArray(e)?t=>!e[a(502)](t):(t,n)=>!e(t,n);return n[a(676)](t,s,o)}}(r);const f=typeof btoa===t(659),l=typeof Buffer===t(659);typeof TextDecoder===t(659)&&new TextDecoder;const I=typeof TextEncoder===t(659)?new TextEncoder:void 0,y=t(410),d=Array[t(689)][t(628)][t(439)](y);(n=>{let r={};d[t(567)](((t,n)=>r[t]=n))})();const m=String[t(496)][t(479)](String);function g(t,n){const r=tt();return(g=function(t,n){return r[t-=408]})(t,n)}typeof Uint8Array[t(423)]===t(659)&&Uint8Array[t(423)].bind(Uint8Array);const h=f?t=>btoa(t):l?n=>Buffer.from(n,"binary").toString(t(623)):n=>{const r=t;let e,o,a,s,c="";const i=n.length%3;for(let t=0;t<n[r(701)];){if((o=n[r(543)](t++))>255||(a=n[r(543)](t++))>255||(s=n.charCodeAt(t++))>255)throw new TypeError(r(684));e=o<<16|a<<8|s,c+=d[e>>18&63]+d[e>>12&63]+d[e>>6&63]+d[63&e]}return i?c[r(628)](0,i-3)+r(522)[r(581)](i):c},b=l?n=>Buffer.from(n)[t(482)]("base64"):n=>{const r=t;let e=[];for(let t=0,o=n[r(701)];t<o;t+=4096)e[r(557)](m[r(436)](null,n[r(675)](t,t+4096)));return h(e.join(""))},v=n=>{const r=t;if(n[r(701)]<2)return(e=n[r(543)](0))<128?n:e<2048?m(192|e>>>6)+m(128|63&e):m(224|e>>>12&15)+m(128|e>>>6&63)+m(128|63&e);var e=65536+1024*(n[r(543)](0)-55296)+(n[r(543)](1)-56320);return m(240|e>>>18&7)+m(128|e>>>12&63)+m(128|e>>>6&63)+m(128|63&e)},w=/[\uD800-\uDBFF][\uDC00-\uDFFFF]|[^\x00-\x7F]/g,k=l?n=>Buffer[t(423)](n,t(580))[t(482)](t(623)):I?n=>b(I[t(424)](n)):n=>h(n[t(588)](w,v)),T=(n,r=!1)=>r?k(n)[t(588)](/=/g,"").replace(/[+\/]/g,(t=>"+"==t?"-":"_")):k(n);function F(){const n=t;var r=[n(559),n(656),n(535),n(592),n(469),n(477),n(500),"len","22833PggbRz",n(681),n(579),n(624),n(613),n(639),n(605),n(604),"012",n(446),n(652),n(687),"cat",n(508),"mCh",n(601),n(468)];return(F=function(){return r})()}!function(n,r){const e=t;for(var o=S,a=F();;)try{if(382809===-parseInt(o(135))/1+-parseInt(o(124))/2+parseInt(o(134))/3*(-parseInt(o(126))/4)+parseInt(o(141))/5+parseInt(o(137))/6+-parseInt(o(136))/7+-parseInt(o(145))/8*(-parseInt(o(147))/9))break;a[e(557)](a.shift())}catch(t){a[e(557)](a[e(658)]())}}();var q=0,j=function(n,r){return function(n){const r=t;var e=S;for(var o,a=q?e(142)+r(685)+e(128)+r(626)+e(143)+"F":r(566)+r(685)+r(535)+r(486)+e(144)+"f",s="",c=0;c<n[r(523)+e(139)];c++)o=n[r(468)+r(656)+e(129)+"t"](c),s+=a[e(125)+e(138)](o>>>4&15)+a["cha"+e(138)](15&o);return s}(function(n,r){const e=t;var o=S,a=R(n);a[o(133)+o(139)]>16&&(a=x(a,8*n[o(133)+o(139)]));for(var s=Array(16),c=Array(16),i=0;i<16;i++)s[i]=909522486^a[i],c[i]=1549556828^a[i];var u=x(s[o(131)+e(542)](R(r)),512+8*r[o(133)+o(139)]);return function(n){const r=t;for(var e=S,o="",a=0;a<32*n[e(133)+e(139)];a+=8)o+=String[e(130)+r(427)+e(140)+e(132)](n[a>>5]>>>24-a%32&255);return o}(x(c[o(131)+o(146)](u),768))}(O(n),O(r)))};function O(n){const r=t;for(var e,o,a=S,s="",c=-1;++c<n[a(133)+a(139)];)e=n[a(125)+a(127)+a(129)+"t"](c),o=c+1<n[a(133)+a(139)]?n[r(468)+a(127)+a(129)+"t"](c+1):0,55296<=e&&e<=56319&&56320<=o&&o<=57343&&(e=65536+((1023&e)<<10)+(1023&o),c++),e<=127?s+=String[a(130)+a(123)+r(605)+"ode"](e):e<=2047?s+=String[a(130)+a(123)+r(605)+a(132)](192|e>>>6&31,128|63&e):e<=65535?s+=String[a(130)+a(123)+r(605)+r(500)](224|e>>>12&15,128|e>>>6&63,128|63&e):e<=2097151&&(s+=String[a(130)+a(123)+a(140)+a(132)](240|e>>>18&7,128|e>>>12&63,128|e>>>6&63,128|63&e));return s}function R(n){const r=t;var e=S,o=Array(n[r(523)+e(139)]>>2);for(let t=0;t<o[r(523)+e(139)];t++)o[t]=0;for(let t=0;t<8*n[e(133)+e(139)];t+=8)o[t>>5]|=(255&n[e(125)+e(127)+r(592)+"t"](t/8))<<24-t%32;return o}function S(t,n){var r=F();return(S=function(t,n){return r[t-=123]})(t,n)}function C(t,n){return t>>>n|t<<32-n}function A(t,n){return t>>>n}var U=[1116352408,1899447441,-1245643825,-373957723,961987163,1508970993,-1841331548,-1424204075,-670586216,310598401,607225278,1426881987,1925078388,-2132889090,-1680079193,-1046744716,-459576895,-272742522,264347078,604807628,770255983,1249150122,1555081692,1996064986,-1740746414,-1473132947,-1341970488,-1084653625,-958395405,-710438585,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,-2117940946,-1838011259,-1564481375,-1474664885,-1035236496,-949202525,-778901479,-694614492,-200395387,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,-2067236844,-1933114872,-1866530822,-1538233109,-1090935817,-965641998];function x(t,n){var r,e,o,a,s,c,i,u,p,f,l,I,y,d,m,g,h,b,v,w,k=S,T=[1779033703,-1150833019,1013904242,-1521486534,1359893119,-1694144372,528734635,1541459225],F=new Array(64);for(t[n>>5]|=128<<24-n%32,t[15+(n+64>>9<<4)]=n,p=0;p<t[k(133)+k(139)];p+=16){for(r=T[0],e=T[1],o=T[2],a=T[3],s=T[4],c=T[5],i=T[6],u=T[7],f=0;f<64;f++)F[f]=f<16?t[f+p]:M(M(M(C(w=F[f-2],17)^C(w,19)^A(w,10),F[f-7]),C(v=F[f-15],7)^C(v,18)^A(v,3)),F[f-16]),l=M(M(M(M(u,C(b=s,6)^C(b,11)^C(b,25)),(h=s)&c^~h&i),U[f]),F[f]),I=M(C(g=r,2)^C(g,13)^C(g,22),(y=r)&(d=e)^y&(m=o)^d&m),u=i,i=c,c=s,s=M(a,l),a=o,o=e,e=r,r=M(l,I);T[0]=M(r,T[0]),T[1]=M(e,T[1]),T[2]=M(o,T[2]),T[3]=M(a,T[3]),T[4]=M(s,T[4]),T[5]=M(c,T[5]),T[6]=M(i,T[6]),T[7]=M(u,T[7])}return T}function M(t,n){var r=(65535&t)+(65535&n);return(t>>16)+(n>>16)+(r>>16)<<16|65535&r}const N=E;function E(t,n){const r=z();return(E=function(t,n){return r[t-=445]})(t,n)}!function(n,r){const e=t,o=E,a=z();for(;;)try{if(394227===-parseInt(o(446))/1*(parseInt(o(452))/2)+-parseInt(o(461))/3*(parseInt(o(447))/4)+-parseInt(o(458))/5+-parseInt(o(453))/6+parseInt(o(449))/7*(parseInt(o(450))/8)+parseInt(o(459))/9+parseInt(o(451))/10)break;a[e(557)](a.shift())}catch(t){a[e(557)](a.shift())}}();const B=/^((http[s]?|ftp):\/)?\/?([^:/\s]+)((\/\w+)*\/)([\w\-.]+[^#?\s]+)(.*)?(#[\w-]+)?$/,Z=[N(456),null,N(448)+N(455)+"ol",N(445)+"t",t(707)+"h",null,t(449)+"e","query",t(447)+"h"];function z(){const n=t,r=[n(453),n(489),n(620),n(560),n(586),n(529),"3009UAAwrI",n(587),n(555),n(570),"pro",n(461),"8FIcGbg",n(440),n(577),n(429),"exe"];return(z=function(){return r})()}const D=L;function L(t,n){const r=Y();return(L=function(t,n){return r[t-=128]})(t,n)}!function(n,r){const e=t,o=L,a=Y();for(;;)try{if(850613===-parseInt(o(158))/1+parseInt(o(144))/2*(parseInt(o(133))/3)+-parseInt(o(139))/4+parseInt(o(148))/5+parseInt(o(152))/6*(-parseInt(o(131))/7)+-parseInt(o(142))/8*(-parseInt(o(130))/9)+parseInt(o(154))/10)break;a[e(557)](a[e(658)]())}catch(t){a[e(557)](a[e(658)]())}}();const V=["qia"+D(143)+D(128)+t(448)+"om",D(150)+t(425)+D(155)+"n",D(153)+t(640)+t(606),D(151)+D(163)+D(156),t(433)+"jie"+D(136)+D(157)+"m",D(150)+D(143)+D(155)+"om",D(134)+"uoa"+D(138)+D(147),D(159)+D(161)+t(679)+"om",D(159)+"iar"+D(132)+"n",D(159)+"i.cn",D(137)+D(149)+D(140)+"t",D(159)+t(637)+D(146)+D(129)];function Y(){const n=t,r=["qia",n(651),"144642nNFKAO",n(606),n(539),n(603),n(488),".co",n(451),n(680),n(503),n(637),n(587),"alh","oap",n(710),"35001zLLvdL","427DJUmdn","t.c","322914qCHpIY",n(612),n(419),n(633),n(654),n(521),n(591),".ar",n(655),n(455),n(425),"4hdWIOs",n(435),"t.w","net",n(497),n(683)];return(Y=function(){return r})()}function J(t,n){var r=W();return(J=function(t,n){return r[t-=416]})(t,n)}function X({signVersion:n,signType:r}){const e=t;var o=J;return{"X-Request-Sign":o(418)+o(440)+o(437)+o(429)+e(411)+"iYW"+o(447)+o(421)+o(420)+"3NT"+o(422)+e(622)+o(423)+o(416)+o(435)+o(436)+e(556)+o(432)+o(427)+o(442)+e(597)+o(425)+o(443)+o(446)+o(430)+o(426)+o(433)+o(417)+o(444)+"=","X-Request-Timestamp":(new Date)[o(438)+"ueOf"]()+"","X-Request-Package-Sign-Version":n,"X-Request-Sign-Version":"v1","X-Request-Sign-Type":r}}function W(){const n=t;var r=[n(565),n(465),"15292jCTRCL",n(694),n(661),n(420),"www",n(617),n(472),n(473),n(491),n(561),n(702),n(665),n(568),n(464),n(530),n(450),n(611),n(506),n(512),n(645),n(509),n(650),"VjY",n(632),n(693),n(669),n(619),n(708),n(476),n(625)];return(W=function(){return r})()}function H(){const n=t;var r=[n(527),"2456JLZcaJ",n(671),n(664),n(431),"177551wLbUqr",n(590),n(578),n(456),"170pKeqPU",n(525),n(510),n(627),n(520),"597WMqdTk",n(475),n(618)];return(H=function(){return r})()}function K(t,n){var r=H();return(K=function(t,n){return r[t-=221]})(t,n)}function P(t,n){const r=Q();return(P=function(t,n){return r[t-=103]})(t,n)}function G(t,n){return t+"">n+""?-1:1}function Q(){const n=t,r=[n(519),n(707),"10bXItph",n(515),n(621),"isA",n(610),"val",n(672),n(691),n(703),"l0b",n(484),n(683),"44ElgMcX",n(695),n(583),n(459),n(408),n(470),n(599),n(595),n(711),n(483),n(642),"ing",n(589),n(657),"web",n(662),n(674),n(699),n(490),n(553)];return(Q=function(){return r})()}!function(n,r){const e=t;for(var o=J,a=W();;)try{if(544237===-parseInt(o(431))/1+parseInt(o(445))/2+parseInt(o(439))/3*(parseInt(o(424))/4)+-parseInt(o(419))/5+parseInt(o(428))/6+parseInt(o(434))/7+parseInt(o(441))/8)break;a[e(557)](a[e(658)]())}catch(t){a[e(557)](a[e(658)]())}}(),function(n,r){const e=t;for(var o=K,a=H();;)try{if(635303===parseInt(o(227))/1*(-parseInt(o(232))/2)+parseInt(o(229))/3*(-parseInt(o(233))/4)+-parseInt(o(225))/5+parseInt(o(222))/6*(-parseInt(o(234))/7)+parseInt(o(230))/8*(parseInt(o(226))/9)+-parseInt(o(224))/10*(parseInt(o(237))/11)+parseInt(o(236))/12)break;a[e(557)](a[e(658)]())}catch(t){a[e(557)](a[e(658)]())}}(),function(n,r){const e=t,o=P,a=Q();for(;;)try{if(843148===parseInt(o(116))/1*(-parseInt(o(112))/2)+-parseInt(o(127))/3*(-parseInt(o(131))/4)+parseInt(o(114))/5*(-parseInt(o(125))/6)+parseInt(o(126))/7+parseInt(o(106))/8*(parseInt(o(136))/9)+parseInt(o(119))/10*(-parseInt(o(135))/11)+-parseInt(o(123))/12)break;a[e(557)](a[e(658)]())}catch(t){a[e(557)](a[e(658)]())}}();function $(t,n){var r=_();return($=function(t,n){return r[t-=147]})(t,n)}function _(){const n=t;var r=[n(438),n(453),"ati",n(442),n(460),n(576),n(524),n(478),n(569),n(546),"sta",n(544),n(646),n(572),n(412),n(593),n(541),n(493),n(651),"pro",n(564),n(467),n(587)];return(_=function(){return r})()}function tt(){const t=["toLowerCase","7979590QFRJdx","JmZ","012","forEach","lYm","21763FKmLpL","304VwiPjv","colon-list-separator","63040510jzjdUv","pi.","one","2TJ","rts","2nDvaaQ","96NrlQDu","4406871TspeYB","utf8","substring","map","ueO","7350EtwmCd","3706851tbCTOD","6376005dODHoR","hos","replace","ogr","duc","2979880tFJwxB","deA","tes","reduce","\u83b7\u53d6s","2961aqyZHV","NTB","skipNull","str","L2n","1036796BOvlFR","als","o.c","1082225ejLpKH","arC","ech","comma","4WUMxTc","[]=","5793132tZSiMb","TA2","qih","rAt","object","skipEmptyString","vK1","OTY","elo","169731pxxpby","Eac","ipr","WM1","base64","501186ZYgCfk","zQ1","9AB","4SGrkBk","slice","isNaN","parse","indexOf","WZh","app","cyv","res","olv","iar","1Tf","gth","o.t","1kSrAxh","red","stringify","HMA","wM2","55185hfjBOs","hRT","stringifyUrl","5598512YLokdl","5412974stsFgr","loc","cde","fun","ten","tna","rCo","ify","shift","function","create","I4Z","430iNiuFd","extract","pme","72zEUTwC","que","separator","3045504LvtPoH","val","parseBooleans","22778UNMRNJ","6LkZoEy","ede","par","subarray","pick","keys","5ZzbQHk","t.c","tus","417069jwMega","assign","sor","invalid character found","345","Iao","112MveLPu","join","prototype","fragmentIdentifier","8968365kiexgV","GI9","fxZ","TY2","uce","filter","74031RzAUpt","true","1243420DYKDlM","sen","length","N2Y","49713TzkqTb","12060351mIbsyz","C_S","trim","pat","yNj","7fDgZeQ","ork","cke","5728591nDpjrU","n/n","ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=","NWR","86Zsztdi","Tim","7VwUbSI","ign","452vSrZBM","rro","1561311POyGFH","cti","1Y2","err","591116JWPjST","from","encode","nda","LUX","mCh","index","2791014XLpOMX","arrayFormat","11648592gnQwCo","Expected `encodedURI` to be of type `string`, got `","lin","arrayFormatSeparator","som","apply","decode","10740etFGCo","call","7731790JTnElv","bracket","36xWcGcc","4066835lFyuBl","string","1311185OoTqii","CDE","has","p.c","fil","1354548ZMaTVQ","848660TicgRU","isArray","toc","parseNumbers","3488YktLKF","tio","htt","toUpperCase","spl","9aytTvJ","1389661SpRBic","YAO","bracket-separator","wXW","ww=","8129bllqUF","2488qUpRiz","cha","fro","9JMGpYT","3qw","5173880HTCqWt","MzA","query","862472xbRmfr","4182008MukRkk","con","Wit","bind","nti","P0G","toString","9270904giMZcI","rep","ore","9ab","wit","ost","url","key","Dkw","exclude","404335TOMKjW","concat","pro","fromCharCode","2609375gvhchA","3Kh","805758NdnuRZ","ode","MW3","includes","ati","arrayFormatSeparator must be single character string","pon","YTE","exec","1426293jfojbI","Rww","81DesQir","0.0","968810OKPvbX","HA2","GJq","min","abs","encodeFragmentIdentifier","1171308LGdQBk","bra","dev","pp.","===","len","36FCqHMB","1497615lzHlLN","3233139RxaMWU","278762oJMPYH","split","for","ZhY","UIg","1931373VbIoJF",":list=","none","678","get","6337128WRzZzl","4173140BgVVUM","14820150XtcHBp","onl","6633jgCBuq","cat","charCodeAt","27832UgGzRT","sort","lac","ps:","qia","test","MMt","834978helUYJ","28341uqVzKg","2451bLnzaK","web","605621bpvhPH","MjQ","push","7660GfqKfO","300ZaSRth","694510IzOVCO","JlY","parseUrl"];return(tt=function(){return t})()}function nt(t,n){const r=rt();return(nt=function(t,n){return r[t-=389]})(t,n)}function rt(){const n=t,r=[n(466),"7632084ZIZxVJ",n(418),n(608),n(443),n(537),n(593),n(709),n(704),n(584),"74cZKQrB",n(697)];return(rt=function(){return r})()}!function(n,r){const e=t;for(var o=$,a=_();;)try{if(697194===parseInt(o(148))/1*(-parseInt(o(168))/2)+-parseInt(o(169))/3*(parseInt(o(163))/4)+parseInt(o(151))/5*(-parseInt(o(160))/6)+parseInt(o(165))/7*(-parseInt(o(155))/8)+parseInt(o(161))/9*(-parseInt(o(154))/10)+-parseInt(o(150))/11*(parseInt(o(157))/12)+parseInt(o(147))/13)break;a[e(557)](a[e(658)]())}catch(t){a[e(557)](a[e(658)]())}}(),function(n,r){const e=t,o=nt,a=rt();for(;;)try{if(901448===-parseInt(o(389))/1+-parseInt(o(397))/2*(-parseInt(o(398))/3)+parseInt(o(390))/4*(-parseInt(o(391))/5)+-parseInt(o(400))/6*(-parseInt(o(394))/7)+-parseInt(o(392))/8+parseInt(o(395))/9+-parseInt(o(396))/10*(-parseInt(o(399))/11))break;a[e(557)](a[e(658)]())}catch(t){a.push(a[e(658)]())}}();const et=ot;function ot(t,n){const r=lt();return(ot=function(t,n){return r[t-=476]})(t,n)}!function(n,r){const e=t,o=ot,a=lt();for(;;)try{if(256664===parseInt(o(480))/1*(parseInt(o(523))/2)+parseInt(o(521))/3+-parseInt(o(528))/4+-parseInt(o(520))/5+-parseInt(o(506))/6*(parseInt(o(497))/7)+parseInt(o(503))/8+parseInt(o(489))/9)break;a[e(557)](a[e(658)]())}catch(t){a.push(a[e(658)]())}}();const at=({fetchSkey:e,platform:o,signVersion:a,signType:s})=>{let c=0,i="0";const u=n((async()=>{const t=g,n=P;try{const r=await e();if(!r)throw new Error(n(104)+t(490)+"\u5931\u8d25");return r}catch(t){throw i=Number(i)+1+"",t}}),{withKey:!0});return{setTimeOffset:function(t){c=t},refreshSkey:async function(){const t=P;return await u((new Date)[t(124)+t(133)+"f"]()+"")},sign:async function({url:n,requestId:e="",timestamp:p,skey:f="",query:l}){const I=g,y=P;switch(o){case y(120)+y(121)+y(109)+"am":if(!function(){const n=L;return typeof getApp==t(653)+n(135)+"on"}())return X({signType:s,signVersion:a});break;case y(111):if(!function(){const t=D;return window&&V[t(145)+"e"]((n=>{const r=t,e=window[g(651)+r(160)+"on"][r(162)+r(141)+"me"];return e===n||e.endsWith("."+n)}))}())return X({signType:s,signVersion:a});break;case y(128)+"0b":break;default:return X({signType:s,signVersion:a})}const d=(p||(new Date)[I(536)+I(413)+"e"]()+c)+"",m=function({url:n}){const r=t;var e=K;return/^(https?:)?\/\/(dev-|local|test-[a-z]-)/[r(593)+"t"](n)?e(228)+e(231)+e(235)+"nt":r(495)+e(221)+e(223)+"n"}({url:n}),h=f||(await async function(){return await u(i)}())[m],b=(t=>{const n=N,r=B[n(454)+"c"](t),e={};return r?Z[n(460)+n(457)+"h"](((t,n)=>{t&&(e[t]=r[n]||"")})):Z[n(460)+"Each"]((t=>{t&&(e[t]="")})),e})(n),v={...r[y(113)+"se"](b[I(666)+"ry"][y(129)+I(546)+"e"](/^\?/,""),{parseNumbers:!1,arrayFormat:y(117)+y(105)+"t"}),...r[y(113)+"se"](r[y(103)+y(108)+I(657)](l||{},{strict:!1,arrayFormat:y(117)+y(105)+"t"}),{arrayFormat:I(519)+y(105)+"t"})},w=r[y(103)+y(108)+y(110)](Object[y(115)+"s"](v)[y(107)+y(132)](((t,n)=>{const r=y;let e=v[n];return Array[r(122)+"rray"](e)&&(e=e[r(130)+"t"](G)),t[n]=e,t}),{}),{arrayFormat:y(117)+y(105)+"t",strict:!1,sort:G}),k=e||"",F=b[y(118)+"h"]+b[I(449)+"e"]+function(t){const n=g,r=P;return t?t[r(134)+"it"]("&")[n(642)+r(132)](((t,n,e)=>{const o=r;let[a,s]=n[o(134)+"it"]("=");return a=encodeURIComponent(a),s=s||"",t+(e>0?"&":"")+a+"="+s}),""):""}(w)+k+d;return{"X-Request-Sign":T(j(h,F)),"X-Request-Timestamp":d,"X-Request-Package-Sign-Version":a,"X-Request-Sign-Version":"v1","X-Request-Sign-Type":s}}}},st={production:et(477)+t(647)+et(516)+et(525)+et(517)+et(515)+et(513)+et(496)+et(508)+et(490)+"kC",development:et(518)+t(616)+et(514)+t(481)+et(499)+et(500)+t(692)+et(478)+t(634)+t(426)+"0q"};let ct=!1;const{sign:it,setTimeOffset:ut}=at({fetchSkey:async function(){return st},platform:et(512),signVersion:et(485)+".1",signType:et(495)+et(524)+et(505)+"56"});const pt=n((async()=>{const n=t,r=et;try{const t=await(e=r(491)+r(488)+r(482)+r(479)+r(484)+n(425)+r(502)+n(409)+"ow",new Promise(((t,n)=>{const r=g,o=ot,a=new XMLHttpRequest;a[o(501)+o(486)+r(673)+r(480)+o(527)]=!1,a[o(507)+"n"](o(476),e,!0),a[o(509)+o(483)+"r"]=function(t){n(t)},a[o(526)+o(492)]=function(){const n=r,e=o;t(JSON[e(519)+"se"](a[e(510)+n(505)+"se"]))},a[r(700)+"d"]()}))),{data:o}=t,{timestamp:a}=o,s=1e3*a-(new Date)[r(522)+r(529)+"e"]();Math[n(516)](s)>6e4&&ut(s)}catch(t){console[r(494)+"or"](t)}var e}),{immediate:!0}),ft=async(n,r)=>{const e=et;return function(t){const n=nt;return e=t,/^(https?:)?\/\/(dev-|test-[a-z]-)?((sankeng|gapi|api|local|gapi-cdn|om|wms|api-admin)\.(qiandaoapp\.com|echoing\.tech|qiandao\.cn|pgscard\.com|echo\.tech|qiandaoapp\.net|qiandao\.com|qihuoapp\.net|tusiart\.com|tusiart\.cn|tensor\.art|tusi\.cn)|localhost)/[n(393)+"t"](e)&&(r=t,!/((\/(now|airline|collect))|\.json)(\?|$)/[n(393)+"t"](r));var r,e}(n=function(n){const r=t;var e=$;return/[a-zA-Z]+:/[e(149)+"t"](n)?n:n[e(167)+e(162)+e(164)+"h"]("//")?""+window[e(152)+e(159)+"on"][e(153)+r(453)+"ol"]+n:window[e(152)+e(159)+"on"][r(495)+e(158)+"ol"]+"//"+window[e(152)+e(159)+"on"][e(156)+"t"]+"/"+n[r(484)+e(166)+"e"](/^\//,"")}(n))?(!ct&&!r?.[e(498)+e(493)+"Tim"+e(487)+e(511)+"t"]&&(await pt(),ct=!0),await it({url:n,query:r?.[e(481)+"ry"]})):Promise[e(510)+e(504)+"e"]({})};function lt(){const n=t,r=[n(540),n(602),n(518),"Tim","GET",n(575),n(462),n(573),n(641),n(666),"//a",n(417),n(548),n(511),"hCr","eOf",n(547),n(532),n(600),n(457),"oad",n(485),n(421),n(644),n(550),n(414),n(415),n(501),n(638),n(487),n(603),n(668),n(636),n(513),"2144646MjtUNC","ope",n(498),n(574),n(635),"fse",n(554),n(686),n(514),n(471),"pCp","nWl","67s",n(674),n(445),n(551),n(536),n(422),n(705),n(531)];return(lt=function(){return r})()}export{ft as sign,at as useApiSign};