@cloudbase/toolbox 0.7.16-alpha.0 → 0.7.16-beta.1

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.
@@ -27,6 +27,8 @@ export interface WebAuthOptions {
27
27
  */
28
28
  throwError?: boolean;
29
29
  getAuthUrl?: (rawUrl: string) => string;
30
+ noBrowser?: boolean;
31
+ callbackTimeout?: number;
30
32
  }
31
33
  export interface LoginByApiSecretOptions {
32
34
  /**
package/lib/auth/index.js CHANGED
@@ -80,7 +80,7 @@ class AuthSupervisor {
80
80
  */
81
81
  loginByWebAuth(options = {}) {
82
82
  return __awaiter(this, void 0, void 0, function* () {
83
- const { getAuthUrl, throwError } = options;
83
+ const { getAuthUrl, throwError, noBrowser, callbackTimeout } = options;
84
84
  if (this.cacheCredential && this.needCache && !this.isCacheExpire()) {
85
85
  return this.cacheCredential;
86
86
  }
@@ -90,7 +90,9 @@ class AuthSupervisor {
90
90
  return credential;
91
91
  // 兼容临时秘钥
92
92
  credential = yield (0, web_auth_1.getAuthTokenFromWeb)({
93
- getAuthUrl
93
+ getAuthUrl,
94
+ noBrowser,
95
+ callbackTimeout
94
96
  });
95
97
  try {
96
98
  yield (0, common_1.checkAuth)(credential, this.requestConfig);
@@ -1,4 +1,6 @@
1
1
  import { Credential } from '../types';
2
2
  export declare function getAuthTokenFromWeb(options?: {
3
3
  getAuthUrl?: (rawUrl: string) => string;
4
+ noBrowser?: boolean;
5
+ callbackTimeout?: number;
4
6
  }): Promise<Credential>;
@@ -14,27 +14,38 @@ const common_1 = require("./common");
14
14
  const coding_1 = require("../coding");
15
15
  const web_1 = require("../web");
16
16
  const system_1 = require("../system");
17
- const CliAuthBaseUrl = 'https://tcb.cloud.tencent.com/dev#/cli-auth';
17
+ const CliAuthBaseUrl = 'https://tcb.cloud.tencent.com/dev';
18
18
  // 打开云开发控制台,获取授权
19
19
  function getAuthTokenFromWeb(options = {}) {
20
20
  return __awaiter(this, void 0, void 0, function* () {
21
- const { getAuthUrl } = options;
21
+ const { getAuthUrl, noBrowser, callbackTimeout } = options;
22
22
  const mac = yield (0, system_1.getMacAddress)();
23
23
  const os = (0, system_1.getOSInfo)();
24
24
  const macHash = (0, coding_1.md5Encoding)(mac);
25
25
  const query = yield (0, web_1.getDataFromWeb)((port) => {
26
+ const callbackUrl = `http://127.0.0.1:${port}`;
27
+ const encodedQuery = `port=${encodeURIComponent(String(port))}`
28
+ + `&hash=${encodeURIComponent(macHash)}`
29
+ + `&mac=${encodeURIComponent(mac)}`
30
+ + `&os=${encodeURIComponent(os)}`
31
+ + '&from=cli';
32
+ const encodedCallbackUrl = encodeURIComponent(callbackUrl);
26
33
  // 授权链接
27
- let cliAuthUrl = `${CliAuthBaseUrl}?port=${port}&hash=${macHash}&mac=${mac}&os=${os}&from=cli`;
34
+ const rawAuthUrl = `${CliAuthBaseUrl}?authCallbackUrl=${encodedCallbackUrl}#/cli-auth?${encodedQuery}`;
35
+ let cliAuthUrl = rawAuthUrl;
28
36
  if (getAuthUrl) {
29
37
  try {
30
- cliAuthUrl = getAuthUrl(`${CliAuthBaseUrl}?port=${port}&hash=${macHash}&mac=${mac}&os=${os}&from=cli`);
38
+ cliAuthUrl = getAuthUrl(rawAuthUrl);
31
39
  }
32
40
  catch (error) {
33
41
  // 忽略错误
34
42
  }
35
43
  }
36
44
  return cliAuthUrl;
37
- }, 'login');
45
+ }, 'login', {
46
+ noBrowser,
47
+ callbackTimeout
48
+ });
38
49
  const credential = (0, common_1.resolveCredential)(query);
39
50
  return credential;
40
51
  });
@@ -15,12 +15,32 @@ Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.getPort = void 0;
16
16
  const portfinder_1 = __importDefault(require("portfinder"));
17
17
  // 默认端口
18
- const DEFAULT_PORT = 9012;
18
+ const LOCAL_DEFAULT_PORT = 9012;
19
+ const REMOTE_DEFAULT_PORT = 19012;
20
+ function isRemoteEnvironment() {
21
+ return Boolean(process.env.SSH_CONNECTION
22
+ || process.env.SSH_CLIENT
23
+ || process.env.SSH_TTY
24
+ || process.env.VSCODE_IPC_HOOK_CLI
25
+ || process.env.VSCODE_AGENT_FOLDER
26
+ || process.env.REMOTE_CONTAINERS
27
+ || process.env.CODESPACES
28
+ || process.env.GITPOD_WORKSPACE_ID);
29
+ }
30
+ function getStartPort() {
31
+ const customStartPort = Number(process.env.TCB_WEB_LOGIN_START_PORT);
32
+ if (Number.isInteger(customStartPort) && customStartPort > 0 && customStartPort < 65535) {
33
+ return customStartPort;
34
+ }
35
+ return isRemoteEnvironment() ? REMOTE_DEFAULT_PORT : LOCAL_DEFAULT_PORT;
36
+ }
19
37
  // 获取本地可用端口
20
38
  function getPort() {
21
39
  return __awaiter(this, void 0, void 0, function* () {
40
+ const startPort = getStartPort();
22
41
  const port = yield portfinder_1.default.getPortPromise({
23
- port: DEFAULT_PORT
42
+ port: startPort,
43
+ stopPort: 65535
24
44
  });
25
45
  return port;
26
46
  });
package/lib/web/web.d.ts CHANGED
@@ -3,4 +3,8 @@ export interface IQuery {
3
3
  }
4
4
  export type GetUrlFn = (port: number) => string;
5
5
  export type CheckFn = (query: IQuery) => Promise<void>;
6
- export declare function getDataFromWeb<T extends IQuery>(getUrl: GetUrlFn, type: 'login' | 'getData'): Promise<T>;
6
+ export interface GetDataFromWebOptions {
7
+ noBrowser?: boolean;
8
+ callbackTimeout?: number;
9
+ }
10
+ export declare function getDataFromWeb<T extends IQuery>(getUrl: GetUrlFn, type: 'login' | 'getData', options?: GetDataFromWebOptions): Promise<T>;
package/lib/web/web.js CHANGED
@@ -19,6 +19,7 @@ const http_1 = __importDefault(require("http"));
19
19
  const system_1 = require("../system");
20
20
  const error_1 = require("../error");
21
21
  const html_1 = require("../html");
22
+ const child_process_1 = require("child_process");
22
23
  // 创建本地 Web 服务器,接受 Web 控制台传入的信息
23
24
  function createLocalServer() {
24
25
  return __awaiter(this, void 0, void 0, function* () {
@@ -52,39 +53,96 @@ function respondWithFile(options) {
52
53
  resolve();
53
54
  });
54
55
  }
56
+ function isTruthyFlag(value) {
57
+ if (!value) {
58
+ return false;
59
+ }
60
+ return ['1', 'true', 'yes', 'on'].includes(String(value).trim().toLowerCase());
61
+ }
62
+ function isVSCodeEnvironment() {
63
+ return !!(process.env.VSCODE_PID || process.env.VSCODE_CWD || process.env.TERM_PROGRAM === 'vscode');
64
+ }
65
+ function openUrlByVSCode(url) {
66
+ return __awaiter(this, void 0, void 0, function* () {
67
+ return new Promise((resolve, reject) => {
68
+ (0, child_process_1.execFile)('code', ['--open-url', url], (error) => {
69
+ if (error) {
70
+ reject(error);
71
+ return;
72
+ }
73
+ resolve();
74
+ });
75
+ });
76
+ });
77
+ }
78
+ function hasSystemBrowserOpener() {
79
+ return __awaiter(this, void 0, void 0, function* () {
80
+ if (process.platform !== 'linux') {
81
+ return true;
82
+ }
83
+ return new Promise((resolve) => {
84
+ (0, child_process_1.execFile)('which', ['xdg-open'], (error) => {
85
+ resolve(!error);
86
+ });
87
+ });
88
+ });
89
+ }
55
90
  // 从 Web 页面中获取数据
56
- function getDataFromWeb(getUrl, type) {
91
+ function getDataFromWeb(getUrl, type, options = {}) {
92
+ var _a, _b;
57
93
  return __awaiter(this, void 0, void 0, function* () {
58
94
  const { server, port } = yield createLocalServer();
95
+ const noBrowser = (_a = options.noBrowser) !== null && _a !== void 0 ? _a : isTruthyFlag(process.env.TCB_NO_BROWSER);
96
+ const callbackTimeout = (_b = options.callbackTimeout) !== null && _b !== void 0 ? _b : 180000;
97
+ if (!Number.isFinite(callbackTimeout) || callbackTimeout <= 0) {
98
+ throw new error_1.CloudBaseError('callbackTimeout must be a positive number');
99
+ }
59
100
  const url = getUrl(port);
60
- // 对 url 转码, 避免 wsl 无法正常打开地址
61
- // https://www.npmjs.com/package/open#url
62
- // https://github.com/sindresorhus/open/blob/master/index.js#L48
63
- // 使用 URL 类进行转码(兼容 v7 和 v8+)
64
- const encodedUrl = new URL(url).href;
65
- try {
66
- // v8+ 支持显式指定浏览器,绕过 xdg-open 依赖
67
- // 如果第一个浏览器不存在,会自动尝试下一个
68
- yield (0, open_1.default)(encodedUrl, {
69
- app: {
70
- name: [
71
- 'google-chrome',
72
- 'google-chrome-stable',
73
- 'chromium',
74
- 'chromium-browser',
75
- 'firefox',
76
- 'microsoft-edge',
77
- 'microsoft-edge-dev'
78
- ]
101
+ console.log('\n\n若链接未自动打开,请手动复制此链接至浏览器,或尝试使用其他登录方式:');
102
+ console.log(`\n${url}`);
103
+ if (!noBrowser) {
104
+ const hasOpener = yield hasSystemBrowserOpener();
105
+ if (hasOpener) {
106
+ // 对 url 转码, 避免 wsl 无法正常打开地址
107
+ // https://www.npmjs.com/package/open#url
108
+ // https://github.com/sindresorhus/open/blob/master/index.js#L48
109
+ try {
110
+ yield (0, open_1.default)(url, { url: true });
79
111
  }
80
- });
81
- }
82
- catch (e) {
83
- throw new error_1.CloudBaseError('打开浏览器失败,请尝试使用其他登录方式');
112
+ catch (e) {
113
+ const code = (e === null || e === void 0 ? void 0 : e.code) || 'UNKNOWN';
114
+ console.warn(`自动打开浏览器失败(${code})。`);
115
+ }
116
+ }
117
+ if (isVSCodeEnvironment()) {
118
+ try {
119
+ yield openUrlByVSCode(url);
120
+ }
121
+ catch (vscodeError) {
122
+ // ignore error
123
+ }
124
+ }
84
125
  }
85
126
  return new Promise((resolve, reject) => {
127
+ let finished = false;
128
+ const timer = setTimeout(() => {
129
+ if (finished) {
130
+ return;
131
+ }
132
+ finished = true;
133
+ server.close();
134
+ reject(new error_1.CloudBaseError(`等待浏览器授权回调超时(${Math.floor(callbackTimeout / 1000)}s)!`));
135
+ }, callbackTimeout);
136
+ const finish = (fn) => {
137
+ if (finished) {
138
+ return;
139
+ }
140
+ finished = true;
141
+ clearTimeout(timer);
142
+ fn();
143
+ };
86
144
  server.on('request', (req, res) => {
87
- const { url } = req;
145
+ const url = req.url || '/';
88
146
  const { query } = query_string_1.default.parseUrl(url);
89
147
  // 响应 HTML 文件
90
148
  if (query === null || query === void 0 ? void 0 : query.html) {
@@ -94,11 +152,15 @@ function getDataFromWeb(getUrl, type) {
94
152
  statusCode: 200,
95
153
  filename: `${type}Success`
96
154
  }).then(() => {
97
- server.close();
98
- resolve(query);
155
+ finish(() => {
156
+ server.close();
157
+ resolve(query);
158
+ });
99
159
  }).catch((e) => {
100
- server.close();
101
- reject(e);
160
+ finish(() => {
161
+ server.close();
162
+ reject(e);
163
+ });
102
164
  });
103
165
  }
104
166
  // CORS 响应普通文本
@@ -114,7 +176,9 @@ function getDataFromWeb(getUrl, type) {
114
176
  if (req.method !== 'OPTIONS') {
115
177
  server.close();
116
178
  }
117
- resolve(query);
179
+ finish(() => {
180
+ resolve(query);
181
+ });
118
182
  });
119
183
  });
120
184
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudbase/toolbox",
3
- "version": "0.7.16-alpha.0",
3
+ "version": "0.7.16-beta.1",
4
4
  "description": "The toolbox for cloudbase",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {
@@ -31,7 +31,7 @@
31
31
  "mustache": "^4.0.1",
32
32
  "nanoid": "^3.1.10",
33
33
  "node-fetch": "^2.6.1",
34
- "open": "^8.4.2",
34
+ "open": "7.1.0",
35
35
  "ora": "^5.3.0",
36
36
  "parse-json": "^5.0.0",
37
37
  "path-type": "^4.0.0",
@@ -63,7 +63,7 @@
63
63
  },
64
64
  "husky": {
65
65
  "hooks": {
66
- "pre-commit": "npm run build && git add ."
66
+ "pre-commit": "npm run build"
67
67
  }
68
68
  },
69
69
  "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
package/diagnose-open.js DELETED
@@ -1,77 +0,0 @@
1
- #!/usr/bin/env node
2
- const path = require('path');
3
- const fs = require('fs');
4
-
5
- console.log('=== Open Package Diagnostics ===\n');
6
-
7
- // 找到 open 包的位置
8
- let openPackagePath;
9
- try {
10
- openPackagePath = require.resolve('open');
11
- console.log('✓ open package found at:', openPackagePath);
12
- } catch (e) {
13
- console.log('✗ open package not found:', e.message);
14
- process.exit(1);
15
- }
16
-
17
- // 计算 xdg-open 路径
18
- const openDir = path.dirname(openPackagePath);
19
- const xdgOpenPath = path.join(openDir, 'xdg-open');
20
-
21
- console.log('✓ Expected xdg-open path:', xdgOpenPath);
22
-
23
- // 检查文件是否存在
24
- if (fs.existsSync(xdgOpenPath)) {
25
- console.log('✓ xdg-open file exists');
26
-
27
- // 检查权限
28
- const stats = fs.statSync(xdgOpenPath);
29
- const mode = stats.mode.toString(8);
30
- console.log(' - Permissions:', mode);
31
- console.log(' - Size:', stats.size, 'bytes');
32
-
33
- // 检查是否可执行
34
- try {
35
- fs.accessSync(xdgOpenPath, fs.constants.X_OK);
36
- console.log('✓ xdg-open is executable');
37
- } catch (e) {
38
- console.log('✗ xdg-open is NOT executable');
39
- console.log(' Fix with: chmod +x', xdgOpenPath);
40
- }
41
- } else {
42
- console.log('✗ xdg-open file does NOT exist');
43
- }
44
-
45
- // 检查环境
46
- console.log('\n=== Environment ===');
47
- console.log('- __dirname:', __dirname);
48
- console.log('- Platform:', process.platform);
49
- console.log('- Node version:', process.version);
50
- console.log('- Is bundled:', (!__dirname || __dirname === '/'));
51
-
52
- // 检查系统 xdg-open
53
- const { execSync } = require('child_process');
54
- try {
55
- const systemXdgOpen = execSync('which xdg-open', { encoding: 'utf8' }).trim();
56
- console.log('✓ System xdg-open found:', systemXdgOpen);
57
-
58
- const version = execSync('xdg-open --version', { encoding: 'utf8' }).trim();
59
- console.log(' - Version:', version);
60
- } catch (e) {
61
- console.log('✗ System xdg-open NOT found');
62
- console.log(' Install with: sudo apt-get install xdg-utils');
63
- }
64
-
65
- console.log('\n=== Recommendation ===');
66
- if (!fs.existsSync(xdgOpenPath)) {
67
- console.log('The bundled xdg-open is missing. Reinstall the open package:');
68
- console.log(' npm install open@latest --force');
69
- } else {
70
- try {
71
- fs.accessSync(xdgOpenPath, fs.constants.X_OK);
72
- console.log('Everything looks good! The bundled xdg-open should work.');
73
- } catch (e) {
74
- console.log('Fix permissions:');
75
- console.log(' chmod +x', xdgOpenPath);
76
- }
77
- }