@heybox/hb-sdk 0.8.4 → 0.9.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +42 -0
- package/dist/cli-chunks/{build-DgV-or9j.cjs → build-Dv5vWsBh.cjs} +5 -5
- package/dist/cli-chunks/{context-DlcETCGM.cjs → context-B15FzPf9.cjs} +2 -2
- package/dist/cli-chunks/{create-BjJ6U5qS.cjs → create-BtZUsxJd.cjs} +1 -1
- package/dist/cli-chunks/{dev-hIWir4Bp.cjs → dev-tAThflC6.cjs} +414 -44
- package/dist/cli-chunks/{doctor-DImHJjzI.cjs → doctor-PE_MhGBX.cjs} +1 -1
- package/dist/cli-chunks/{index-CbDWlPpw.cjs → index-CeA8xI0T.cjs} +2 -2
- package/dist/cli-chunks/{index-CO8AEaXW.cjs → index-Dd_eGaDz.cjs} +159 -67
- package/dist/cli-chunks/{index.esm-COo1T4B1.cjs → index.esm-Bw3R5BAL.cjs} +7 -7
- package/dist/cli-chunks/{login-DbbuUauB.cjs → login-5FPaFRi0.cjs} +2 -2
- package/dist/cli-chunks/{project-vite-Dw04aEsD.cjs → project-vite-ckDtbYtt.cjs} +1 -1
- package/dist/cli-chunks/{remote-CWeqNVk_.cjs → remote-DCHqFvmB.cjs} +7 -7
- package/dist/cli-chunks/{runtime-gate-PxUnfrPH.cjs → runtime-gate-DkTbgx4d.cjs} +1 -1
- package/dist/cli-chunks/{runtime-permission-env-9GWrfOTi.cjs → runtime-permission-env-DR89buKQ.cjs} +1 -1
- package/dist/cli-chunks/{session-CRiCQkg-.cjs → session-BiT-5VHK.cjs} +63 -1
- package/dist/cli-chunks/{skill-BZFvZvOT.cjs → skill-C5EoUf2T.cjs} +2 -2
- package/dist/cli-chunks/{version-Bk5ehprU.cjs → version-CJ7rfbl-.cjs} +1 -1
- package/dist/cli.cjs +1 -1
- package/dist/devtools/browser-dev-host/assets/browser-dev-host-Cu0OUKXf.js +102 -0
- package/dist/devtools/browser-dev-host/assets/{desktop-app-launch-DnjiCJj1.js → desktop-app-launch-2BQ4uwPZ.js} +1 -1
- package/dist/devtools/browser-dev-host/assets/{index-CjUagRoX.js → index-B1ZPOArZ.js} +3 -3
- package/dist/devtools/browser-dev-host/index.html +2 -2
- package/dist/index.cjs.js +1 -1
- package/dist/index.esm.js +1 -1
- package/dist/vite.cjs.js +1 -1
- package/dist/vite.esm.js +1 -1
- package/package.json +4 -4
- package/skill/skill.json +4 -4
- package/types/modules/auth/index.d.ts +4 -0
- package/types/modules/cloud/index.d.ts +25 -0
- package/types/modules/user/index.d.ts +16 -0
- package/types/protocol/dev-session.d.ts +18 -0
- package/dist/devtools/browser-dev-host/assets/browser-dev-host-C_NWoBa2.js +0 -102
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var context = require('./context-
|
|
3
|
+
var context = require('./context-B15FzPf9.cjs');
|
|
4
4
|
var node_crypto = require('node:crypto');
|
|
5
5
|
var fs = require('node:fs');
|
|
6
|
-
var runtimePermissionEnv = require('./runtime-permission-env-
|
|
6
|
+
var runtimePermissionEnv = require('./runtime-permission-env-DR89buKQ.cjs');
|
|
7
7
|
var os = require('node:os');
|
|
8
8
|
var path = require('node:path');
|
|
9
9
|
var net = require('node:net');
|
|
@@ -12,13 +12,13 @@ var node_http = require('node:http');
|
|
|
12
12
|
var node_module = require('node:module');
|
|
13
13
|
var undici = require('undici');
|
|
14
14
|
var fs$1 = require('node:fs/promises');
|
|
15
|
-
var index = require('./index-
|
|
15
|
+
var index = require('./index-Dd_eGaDz.cjs');
|
|
16
16
|
var require$$0$1 = require('crypto');
|
|
17
17
|
var require$$0 = require('buffer');
|
|
18
|
-
var session = require('./session-
|
|
18
|
+
var session = require('./session-BiT-5VHK.cjs');
|
|
19
19
|
var browser = require('./browser-RAy8e8cV.cjs');
|
|
20
|
-
var projectVite = require('./project-vite-
|
|
21
|
-
var version = require('./version-
|
|
20
|
+
var projectVite = require('./project-vite-ckDtbYtt.cjs');
|
|
21
|
+
var version = require('./version-CJ7rfbl-.cjs');
|
|
22
22
|
require('node:zlib');
|
|
23
23
|
|
|
24
24
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
@@ -1247,6 +1247,12 @@ function createContextFields(context) {
|
|
|
1247
1247
|
local_dev_launch_token: context.localDevLaunchToken,
|
|
1248
1248
|
mini_program_id: context.miniProgramId,
|
|
1249
1249
|
companion_manifest_sha256: context.companionManifestSha256,
|
|
1250
|
+
// 限流状态是加性字段:限流时才写入;显式置 null 表示已恢复,需要覆盖旧快照里的状态。
|
|
1251
|
+
...(context.rateLimit
|
|
1252
|
+
? { rate_limit: context.rateLimit }
|
|
1253
|
+
: context.rateLimit === null
|
|
1254
|
+
? { rate_limit: null }
|
|
1255
|
+
: {}),
|
|
1250
1256
|
};
|
|
1251
1257
|
}
|
|
1252
1258
|
function validateDevSessionCreatePayload(payload, allowedMiniUrlOrigins, runtimePermissions) {
|
|
@@ -1986,8 +1992,8 @@ const DEFAULT_CHANNEL_TRANSPORT = {
|
|
|
1986
1992
|
};
|
|
1987
1993
|
const IPV4_LOOPBACK_ADDRESSES = new net.BlockList();
|
|
1988
1994
|
IPV4_LOOPBACK_ADDRESSES.addSubnet('127.0.0.0', 8, 'ipv4');
|
|
1989
|
-
const { WebSocket, WebSocketServer } = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli-chunks/dev-
|
|
1990
|
-
const { parse: parseHtml } = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli-chunks/dev-
|
|
1995
|
+
const { WebSocket, WebSocketServer } = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli-chunks/dev-tAThflC6.cjs', document.baseURI).href)))('ws');
|
|
1996
|
+
const { parse: parseHtml } = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli-chunks/dev-tAThflC6.cjs', document.baseURI).href)))('parse5');
|
|
1991
1997
|
const BROWSER_DEV_HOST_ROOT_CANDIDATES = [
|
|
1992
1998
|
path.resolve(__dirname, 'devtools/browser-dev-host'),
|
|
1993
1999
|
path.resolve(__dirname, '../devtools/browser-dev-host'),
|
|
@@ -11086,7 +11092,7 @@ function assertNodeRuntimeCapabilities(ctx, settings) {
|
|
|
11086
11092
|
function createNodeRealtimeDriverRegistry() {
|
|
11087
11093
|
const registry = createRuntimeRealtimeDriverRegistry();
|
|
11088
11094
|
registry.registerLazy('node-websocket', async () => {
|
|
11089
|
-
const mod = await Promise.resolve().then(function () { return require('./index.esm-
|
|
11095
|
+
const mod = await Promise.resolve().then(function () { return require('./index.esm-Bw3R5BAL.cjs'); });
|
|
11090
11096
|
return mod.createNodeWebSocketRuntimeDriver();
|
|
11091
11097
|
});
|
|
11092
11098
|
return registry;
|
|
@@ -15212,6 +15218,142 @@ const COMPANION_ARTIFACT_ACCESS_TOKEN_ENV = 'HB_SDK_COMPANION_ARTIFACT_ACCESS_TO
|
|
|
15212
15218
|
// 本地 PC 调试 manifest 的专用令牌:开窗时扩展还没拿到 LocalDevelopmentBinding,只能靠启动 URI 带来。
|
|
15213
15219
|
const MINI_PROGRAM_MANIFEST_ACCESS_TOKEN_ENV = 'HB_SDK_MINI_PROGRAM_MANIFEST_ACCESS_TOKEN';
|
|
15214
15220
|
const COMPANION_ARTIFACT_ACCESS_TOKEN_HEADER = 'x-hb-sdk-companion-dev-token';
|
|
15221
|
+
/** 失败上下文的缓存窗口;成功上下文复用到绑定或声明变化。 */
|
|
15222
|
+
const DEFAULT_DEV_CONTEXT_CACHE_TTL_MS = 30_000;
|
|
15223
|
+
/** 覆盖 Dev Context 缓存 TTL(毫秒);设为 0 关闭缓存。 */
|
|
15224
|
+
const DEV_CONTEXT_CACHE_TTL_ENV = 'HB_SDK_DEV_CONTEXT_TTL_MS';
|
|
15225
|
+
/** 普通失败的指数退避起点。 */
|
|
15226
|
+
const DEFAULT_DEV_CONTEXT_BACKOFF_START_MS = 1_000;
|
|
15227
|
+
/** 普通失败的指数退避上限(60s)。 */
|
|
15228
|
+
const DEFAULT_DEV_CONTEXT_BACKOFF_MAX_MS = 60_000;
|
|
15229
|
+
/** 限流类失败(403/429)的退避起点。 */
|
|
15230
|
+
const RATE_LIMIT_BACKOFF_START_MS = 30_000;
|
|
15231
|
+
/** 限流类失败的退避上限(300s)。 */
|
|
15232
|
+
const RATE_LIMIT_BACKOFF_MAX_MS = 300_000;
|
|
15233
|
+
/** 退避抖动比例:±20%。 */
|
|
15234
|
+
const DEV_CONTEXT_BACKOFF_JITTER_RATIO = 0.2;
|
|
15235
|
+
/** 覆盖退避上限(毫秒);同时作用于普通失败与限流类失败。 */
|
|
15236
|
+
const DEV_CONTEXT_BACKOFF_MAX_ENV = 'HB_SDK_BACKOFF_MAX_MS';
|
|
15237
|
+
/** 缓存命中的限流结果最长压制时间,避免封禁期内继续打远端。 */
|
|
15238
|
+
const DEV_CONTEXT_RATE_LIMIT_CACHE_MAX_MS = 300_000;
|
|
15239
|
+
/**
|
|
15240
|
+
* 启动时命中限流、值得等待窗口过去的时长上限(毫秒)。
|
|
15241
|
+
*
|
|
15242
|
+
* 只有网关明确给出、且不超过该上限的窗口才等待并重试一次(自动恢复,不必重启 CLI);
|
|
15243
|
+
* 更长或未知的窗口直接跳过等待,按限流原因阻塞真机调试——事故里封禁 24h,等下去只会把
|
|
15244
|
+
* `hb-sdk dev` 启动拖住几分钟却依然恢复不了。
|
|
15245
|
+
*/
|
|
15246
|
+
const DEV_CONTEXT_STARTUP_RATE_LIMIT_WAIT_MAX_MS = 30_000;
|
|
15247
|
+
/** 新声明已替代在途请求;旧结果不应用,也不作为远端失败提示。 */
|
|
15248
|
+
class DevContextSupersededError extends Error {
|
|
15249
|
+
}
|
|
15250
|
+
/** 读取 Dev Context 缓存 TTL;非法值回落到默认 30 秒,0 表示关闭缓存。 */
|
|
15251
|
+
function resolveDevContextCacheTtlMs(env = process.env) {
|
|
15252
|
+
const raw = env[DEV_CONTEXT_CACHE_TTL_ENV];
|
|
15253
|
+
if (raw === undefined || raw.trim() === '')
|
|
15254
|
+
return DEFAULT_DEV_CONTEXT_CACHE_TTL_MS;
|
|
15255
|
+
const parsed = Number(raw);
|
|
15256
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : DEFAULT_DEV_CONTEXT_CACHE_TTL_MS;
|
|
15257
|
+
}
|
|
15258
|
+
/**
|
|
15259
|
+
* 带 TTL 的 Dev Context 结果缓存。
|
|
15260
|
+
*
|
|
15261
|
+
* key = `projectRoot` + 当前绑定的 `miniProgramId`:同一项目内的启动读取、设备会话读取和
|
|
15262
|
+
* 权限刷新共用一份结果,避免手机端每次重连都打一次远端 `detail`。TTL 内命中不打远端;
|
|
15263
|
+
* `ttlMs` 为 0 时关闭缓存。
|
|
15264
|
+
*/
|
|
15265
|
+
function createDevContextCache(options) {
|
|
15266
|
+
const now = options.now ?? Date.now;
|
|
15267
|
+
const entries = new Map();
|
|
15268
|
+
const inflight = new Map();
|
|
15269
|
+
/**
|
|
15270
|
+
* 全局代际:任一次失效都自增,早于本次失效发起的请求不得写回缓存。
|
|
15271
|
+
*
|
|
15272
|
+
* 刻意不用「按 key 记录 generation」:那样全量失效时只能推进已经登记过的 key,
|
|
15273
|
+
* 而首次被全量失效的 key 不在表里,旧结果仍会回填(生产路径走的正是无参失效)。
|
|
15274
|
+
*/
|
|
15275
|
+
let generation = 0;
|
|
15276
|
+
const cacheEnabled = options.ttlMs > 0;
|
|
15277
|
+
const resolveTtlMs = (value) => {
|
|
15278
|
+
const dynamic = options.ttlMsOf?.(value);
|
|
15279
|
+
return typeof dynamic === 'number' && Number.isFinite(dynamic) && dynamic > 0
|
|
15280
|
+
? Math.max(options.ttlMs, Math.min(dynamic, DEV_CONTEXT_RATE_LIMIT_CACHE_MAX_MS))
|
|
15281
|
+
: options.ttlMs;
|
|
15282
|
+
};
|
|
15283
|
+
return {
|
|
15284
|
+
invalidate: (key) => {
|
|
15285
|
+
generation += 1;
|
|
15286
|
+
if (key === undefined) {
|
|
15287
|
+
entries.clear();
|
|
15288
|
+
inflight.clear();
|
|
15289
|
+
return;
|
|
15290
|
+
}
|
|
15291
|
+
entries.delete(key);
|
|
15292
|
+
inflight.delete(key);
|
|
15293
|
+
},
|
|
15294
|
+
peek: (key) => entries.get(key)?.value,
|
|
15295
|
+
read: (key) => {
|
|
15296
|
+
if (cacheEnabled) {
|
|
15297
|
+
const cached = entries.get(key);
|
|
15298
|
+
if (cached && cached.expiresAt > now())
|
|
15299
|
+
return Promise.resolve(cached.value);
|
|
15300
|
+
if (cached)
|
|
15301
|
+
entries.delete(key);
|
|
15302
|
+
}
|
|
15303
|
+
const pending = inflight.get(key);
|
|
15304
|
+
if (pending)
|
|
15305
|
+
return pending;
|
|
15306
|
+
const readGeneration = generation;
|
|
15307
|
+
const promise = options
|
|
15308
|
+
.load(key)
|
|
15309
|
+
.then((value) => {
|
|
15310
|
+
// 主动失效后,早先 in-flight 的结果不得写回缓存;ttlMs 为 0 表示完全关闭缓存。
|
|
15311
|
+
if (cacheEnabled && readGeneration === generation) {
|
|
15312
|
+
const ttlMs = resolveTtlMs(value);
|
|
15313
|
+
if (ttlMs > 0)
|
|
15314
|
+
entries.set(key, { expiresAt: now() + ttlMs, value });
|
|
15315
|
+
}
|
|
15316
|
+
return value;
|
|
15317
|
+
})
|
|
15318
|
+
.finally(() => {
|
|
15319
|
+
if (inflight.get(key) === promise)
|
|
15320
|
+
inflight.delete(key);
|
|
15321
|
+
});
|
|
15322
|
+
inflight.set(key, promise);
|
|
15323
|
+
return promise;
|
|
15324
|
+
},
|
|
15325
|
+
};
|
|
15326
|
+
}
|
|
15327
|
+
/** 退避上限:普通失败默认 60s,限流类默认 300s;两者都可被 HB_SDK_BACKOFF_MAX_MS 覆盖。 */
|
|
15328
|
+
function resolveDevContextBackoffLimits(env = process.env) {
|
|
15329
|
+
const raw = env[DEV_CONTEXT_BACKOFF_MAX_ENV];
|
|
15330
|
+
const parsed = raw === undefined || raw.trim() === '' ? Number.NaN : Number(raw);
|
|
15331
|
+
const override = Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;
|
|
15332
|
+
return {
|
|
15333
|
+
normalMaxMs: override ?? DEFAULT_DEV_CONTEXT_BACKOFF_MAX_MS,
|
|
15334
|
+
// 限流上限不得低于其起点,否则退避会退化成固定间隔。
|
|
15335
|
+
rateLimitMaxMs: Math.max(RATE_LIMIT_BACKOFF_START_MS, override ?? RATE_LIMIT_BACKOFF_MAX_MS),
|
|
15336
|
+
};
|
|
15337
|
+
}
|
|
15338
|
+
/**
|
|
15339
|
+
* 计算 Dev Context 失败退避时长。
|
|
15340
|
+
*
|
|
15341
|
+
* 普通失败 `1s → 2s → 4s → … → 60s`;限流类失败(403/429)`30s → 60s → … → 300s`;
|
|
15342
|
+
* 两者都带 ±20% 抖动,避免多个客户端同步重试。
|
|
15343
|
+
*/
|
|
15344
|
+
function computeDevContextBackoffDelayMs(options) {
|
|
15345
|
+
const limits = resolveDevContextBackoffLimits(options.env);
|
|
15346
|
+
const rateLimited = options.rateLimited === true;
|
|
15347
|
+
const startMs = rateLimited ? RATE_LIMIT_BACKOFF_START_MS : DEFAULT_DEV_CONTEXT_BACKOFF_START_MS;
|
|
15348
|
+
const maxMs = rateLimited ? limits.rateLimitMaxMs : limits.normalMaxMs;
|
|
15349
|
+
const attempt = Number.isFinite(options.attempt) ? Math.max(0, Math.floor(options.attempt)) : 0;
|
|
15350
|
+
const exponentialMs = startMs * 2 ** Math.min(attempt, 24);
|
|
15351
|
+
const random = options.random ?? Math.random;
|
|
15352
|
+
const randomValue = Math.min(1, Math.max(0, random()));
|
|
15353
|
+
const jittered = exponentialMs * (1 - DEV_CONTEXT_BACKOFF_JITTER_RATIO + 2 * DEV_CONTEXT_BACKOFF_JITTER_RATIO * randomValue);
|
|
15354
|
+
// 先抖动再封顶:否则 300s 的限流上限实际会被 ±20% 抖动突破到 360s。
|
|
15355
|
+
return Math.max(1, Math.round(Math.min(maxMs, jittered)));
|
|
15356
|
+
}
|
|
15215
15357
|
async function runDevCommand(options, runtime = {}) {
|
|
15216
15358
|
const logger = runtime.logger ?? index.createCliLogger();
|
|
15217
15359
|
const fetchImpl = runtime.fetchImpl ?? fetch;
|
|
@@ -15237,14 +15379,70 @@ async function runDevCommand(options, runtime = {}) {
|
|
|
15237
15379
|
const devSessionId = node_crypto.randomBytes(16).toString('hex');
|
|
15238
15380
|
const devContextPath = `/__hb_sdk__/dev-context/${node_crypto.randomBytes(32).toString('hex')}`;
|
|
15239
15381
|
const miniProgramPlatforms = runtimePermissionEnv.readMiniappPlatformsFromPackageJson(projectRoot);
|
|
15240
|
-
|
|
15241
|
-
|
|
15242
|
-
|
|
15243
|
-
|
|
15244
|
-
|
|
15245
|
-
|
|
15382
|
+
// A1:Dev Context 结果缓存。启动读取、设备会话读取和权限刷新共用同一份结果,
|
|
15383
|
+
// 手机端重连不再 1:1 产生远端 `detail` 请求;限流时按网关建议时长延长压制。
|
|
15384
|
+
const defaultSleep = (ms) => new Promise(resolve => { setTimeout(resolve, ms).unref?.(); });
|
|
15385
|
+
const devContextCache = createDevContextCache({
|
|
15386
|
+
load: () => loadDevLaunchContext({
|
|
15387
|
+
env: runtime.env,
|
|
15388
|
+
fetchImpl,
|
|
15389
|
+
logger,
|
|
15390
|
+
projectRoot,
|
|
15391
|
+
timeoutMs: runtime.devContextTimeoutMs ?? DEFAULT_DEV_CONTEXT_TIMEOUT_MS,
|
|
15392
|
+
}),
|
|
15393
|
+
ttlMs: resolveDevContextCacheTtlMs(runtime.env ?? process.env),
|
|
15394
|
+
ttlMsOf: (result) => result.context.rateLimit?.retryAfterMs,
|
|
15246
15395
|
});
|
|
15396
|
+
/**
|
|
15397
|
+
* 读取 Dev Context。
|
|
15398
|
+
*
|
|
15399
|
+
* key 里带上当前 `package.json` 绑定的 miniProgramId:项目绑定变更会直接换成另一个缓存 key,
|
|
15400
|
+
* 立即重新请求远端,而不是等缓存过期后才发现换了小程序。
|
|
15401
|
+
* 读绑定是本地文件读取,不打远端,符合「只读本地快照」的高频读范式。
|
|
15402
|
+
*/
|
|
15403
|
+
const readDevLaunchContext = async (reuseSnapshot = false) => {
|
|
15404
|
+
const boundMiniProgramId = await context.readBoundMiniProgramId(projectRoot).catch(() => undefined);
|
|
15405
|
+
const key = `${projectRoot}\0${boundMiniProgramId ?? ''}`;
|
|
15406
|
+
const snapshot = reuseSnapshot ? devContextCache.peek(key) : undefined;
|
|
15407
|
+
if (snapshot?.context.source === 'remote' && !snapshot.context.rateLimit?.limited)
|
|
15408
|
+
return snapshot;
|
|
15409
|
+
return devContextCache.read(key);
|
|
15410
|
+
};
|
|
15411
|
+
let { context: devLaunchContext, miniProgram } = await readDevLaunchContext();
|
|
15412
|
+
let lastRateLimitHint = '';
|
|
15413
|
+
// A3:提示只在内容变化时输出一次,避免手机端反复重连刷屏。
|
|
15414
|
+
const reportRateLimitOnce = (state) => {
|
|
15415
|
+
const hint = index.formatCliRateLimitHint(state);
|
|
15416
|
+
if (hint === lastRateLimitHint)
|
|
15417
|
+
return;
|
|
15418
|
+
lastRateLimitHint = hint;
|
|
15419
|
+
reportDevContextRateLimit(logger, state, runtime.env);
|
|
15420
|
+
};
|
|
15421
|
+
if (devLaunchContext.rateLimit?.limited)
|
|
15422
|
+
reportRateLimitOnce(devLaunchContext.rateLimit);
|
|
15423
|
+
// 启动即被限流时 `miniProgramId` 缺失,后面不会创建 token session,也就没有任何刷新路径
|
|
15424
|
+
// (三条读取入口都以 `source === 'remote'` 为前提),开发者只能重启 CLI。这里先等网关窗口
|
|
15425
|
+
// 过去再重试一次:窗口短就自动恢复,窗口长(例如封禁 24h)则不再空等,转为按限流原因阻塞
|
|
15426
|
+
// 真机调试,而不是误报「未登录 / 未绑定」。
|
|
15427
|
+
const startupRateLimit = devLaunchContext.rateLimit;
|
|
15428
|
+
if (!devLaunchContext.miniProgramId && startupRateLimit?.limited) {
|
|
15429
|
+
const windowMs = startupRateLimit.retryAfterMs;
|
|
15430
|
+
if (windowMs !== undefined && windowMs <= DEV_CONTEXT_STARTUP_RATE_LIMIT_WAIT_MAX_MS) {
|
|
15431
|
+
logger.warn(`已被网关限流,等待 ${Math.round(windowMs / 1000)} 秒后重试一次…`);
|
|
15432
|
+
await (runtime.sleep ?? defaultSleep)(windowMs);
|
|
15433
|
+
// 限流结果在缓存里最多压制 300s,重试前必须先失效,否则读到的还是同一条。
|
|
15434
|
+
devContextCache.invalidate();
|
|
15435
|
+
const retried = await readDevLaunchContext();
|
|
15436
|
+
if (retried.context.miniProgramId) {
|
|
15437
|
+
devLaunchContext = retried.context;
|
|
15438
|
+
miniProgram = retried.miniProgram;
|
|
15439
|
+
// 已恢复正常:允许后续再次限流时重新提示。
|
|
15440
|
+
lastRateLimitHint = '';
|
|
15441
|
+
}
|
|
15442
|
+
}
|
|
15443
|
+
}
|
|
15247
15444
|
let currentDevContext = devLaunchContext;
|
|
15445
|
+
let devContextGeneration = 0;
|
|
15248
15446
|
let contextRevision = 1;
|
|
15249
15447
|
let launchRuntimePermissions = context.createMobileDevSessionRuntimePermissions(devLaunchContext);
|
|
15250
15448
|
const mobileDevContextUrlsByOrigin = new Map();
|
|
@@ -15253,6 +15451,7 @@ async function runDevCommand(options, runtime = {}) {
|
|
|
15253
15451
|
let currentExpiresAt;
|
|
15254
15452
|
let refreshDevCsp;
|
|
15255
15453
|
let lastTemporaryWarning = '';
|
|
15454
|
+
let hasReportedBindingChange = false;
|
|
15256
15455
|
let tokenSession;
|
|
15257
15456
|
const updateLocalContext = (allowedTemporaryKeys) => {
|
|
15258
15457
|
const grant = runtimePermissionEnv.createMiniProgramLocalDevGrant({ declaredPermissions: currentDevContext.declaredPermissions ?? {},
|
|
@@ -15271,6 +15470,16 @@ async function runDevCommand(options, runtime = {}) {
|
|
|
15271
15470
|
launchRuntimePermissions = context.createMobileDevSessionRuntimePermissions(currentDevContext);
|
|
15272
15471
|
refreshDevCsp?.();
|
|
15273
15472
|
};
|
|
15473
|
+
// A3:把限流状态透出给浏览器调试页与宿主页,让手机端停止重连自激循环。
|
|
15474
|
+
const applyRateLimitState = (state) => {
|
|
15475
|
+
currentDevContext = { ...currentDevContext, rateLimit: state };
|
|
15476
|
+
updateLocalContext();
|
|
15477
|
+
browserDevHost.updateBootstrap({ devContext: currentDevContext });
|
|
15478
|
+
if (state)
|
|
15479
|
+
reportRateLimitOnce(state);
|
|
15480
|
+
else
|
|
15481
|
+
lastRateLimitHint = '';
|
|
15482
|
+
};
|
|
15274
15483
|
updateLocalContext();
|
|
15275
15484
|
const readLaunchSource = () => ({
|
|
15276
15485
|
devContext: currentDevContext,
|
|
@@ -15368,14 +15577,28 @@ async function runDevCommand(options, runtime = {}) {
|
|
|
15368
15577
|
},
|
|
15369
15578
|
readMobileDevSessionContext: async () => {
|
|
15370
15579
|
if (currentDevContext.source === 'remote') {
|
|
15371
|
-
const
|
|
15372
|
-
|
|
15373
|
-
if (
|
|
15580
|
+
const generation = devContextGeneration;
|
|
15581
|
+
const refreshed = await readDevLaunchContext(true);
|
|
15582
|
+
if (generation !== devContextGeneration)
|
|
15583
|
+
return currentDevContext;
|
|
15584
|
+
if (refreshed.context.rateLimit?.limited) {
|
|
15585
|
+
// 被限流时不再替换上下文,只透出限流状态;手机端据此停止重连。
|
|
15586
|
+
applyRateLimitState(refreshed.context.rateLimit);
|
|
15587
|
+
}
|
|
15588
|
+
else if (refreshed.context.source === 'remote' && refreshed.context.miniProgramId === currentDevContext.miniProgramId) {
|
|
15374
15589
|
if (JSON.stringify(currentDevContext.runtimePermissions) !== JSON.stringify(refreshed.context.runtimePermissions))
|
|
15375
15590
|
contextRevision++;
|
|
15376
15591
|
currentDevContext = withCurrentLocalDevLaunchToken(refreshed.context, currentLocalDevLaunchToken);
|
|
15377
15592
|
updateLocalContext();
|
|
15378
15593
|
browserDevHost.updateBootstrap({ devContext: currentDevContext });
|
|
15594
|
+
hasReportedBindingChange = false;
|
|
15595
|
+
}
|
|
15596
|
+
else if (refreshed.context.source === 'remote' && refreshed.context.miniProgramId !== currentDevContext.miniProgramId) {
|
|
15597
|
+
// 绑定已变更:保留当前上下文不误发,但必须提示重启,避免真机调试一直用旧小程序。
|
|
15598
|
+
if (!hasReportedBindingChange) {
|
|
15599
|
+
hasReportedBindingChange = true;
|
|
15600
|
+
logger.warn('检测到项目绑定已变更,请重新运行 hb-sdk dev 后再扫码调试');
|
|
15601
|
+
}
|
|
15379
15602
|
}
|
|
15380
15603
|
}
|
|
15381
15604
|
return currentDevContext;
|
|
@@ -15401,8 +15624,14 @@ async function runDevCommand(options, runtime = {}) {
|
|
|
15401
15624
|
companionDeclared: currentDevContext.declaredPermissions?.companion?.enabled === true && runtimePermissionEnv.hasMiniappDesktopPlatform(currentDevContext.platforms),
|
|
15402
15625
|
miniProgramId: devLaunchContext.miniProgramId,
|
|
15403
15626
|
refreshPermissions: async () => {
|
|
15404
|
-
const
|
|
15405
|
-
|
|
15627
|
+
const generation = devContextGeneration;
|
|
15628
|
+
const refreshed = await readDevLaunchContext();
|
|
15629
|
+
if (generation !== devContextGeneration)
|
|
15630
|
+
throw new DevContextSupersededError();
|
|
15631
|
+
// 限流必须原样抛出,才能走 30s 起、上限 300s 的退避与「请勿反复重启」提示。
|
|
15632
|
+
if (refreshed.context.rateLimit?.limited) {
|
|
15633
|
+
throw index.createCliRateLimitedErrorFromState(refreshed.context.rateLimit);
|
|
15634
|
+
}
|
|
15406
15635
|
if (refreshed.context.miniProgramId !== devLaunchContext.miniProgramId)
|
|
15407
15636
|
throw new Error('项目绑定已变更,请重新启动调试');
|
|
15408
15637
|
if (refreshed.context.source !== 'remote')
|
|
@@ -15428,13 +15657,24 @@ async function runDevCommand(options, runtime = {}) {
|
|
|
15428
15657
|
});
|
|
15429
15658
|
},
|
|
15430
15659
|
onReadiness: devReadiness => browserDevHost.updateBootstrap({ devReadiness }),
|
|
15660
|
+
// 主动失效:项目绑定变更或开发者点重试后立即重新请求(key 会带上新绑定,这里清空全部)。
|
|
15661
|
+
onInvalidate: () => {
|
|
15662
|
+
devContextGeneration += 1;
|
|
15663
|
+
devContextCache.invalidate();
|
|
15664
|
+
},
|
|
15665
|
+
// 限流状态与恢复都要透出给调试页与宿主页。
|
|
15666
|
+
onRateLimit: state => applyRateLimitState(state),
|
|
15431
15667
|
projectRoot,
|
|
15432
15668
|
timeoutMs: runtime.devContextTimeoutMs ?? DEFAULT_DEV_CONTEXT_TIMEOUT_MS,
|
|
15433
15669
|
});
|
|
15434
15670
|
closers.push(tokenSession.close);
|
|
15435
15671
|
}
|
|
15436
15672
|
else {
|
|
15437
|
-
|
|
15673
|
+
// 限流与「未登录 / 未绑定」是两回事:前者等窗口过去即可,后者要开发者去操作。
|
|
15674
|
+
// 用限流原因阻塞真机调试,避免把已封禁误报成登录/绑定问题。
|
|
15675
|
+
const reason = devLaunchContext.rateLimit?.limited
|
|
15676
|
+
? `真机调试暂不可用:${index.formatCliRateLimitHint(devLaunchContext.rateLimit)}`
|
|
15677
|
+
: '登录或小程序绑定不可用,请完成 CLI 登录和项目绑定';
|
|
15438
15678
|
browserDevHost.updateBootstrap({
|
|
15439
15679
|
devReadiness: {
|
|
15440
15680
|
browser: { state: 'ready' },
|
|
@@ -15444,11 +15684,36 @@ async function runDevCommand(options, runtime = {}) {
|
|
|
15444
15684
|
});
|
|
15445
15685
|
}
|
|
15446
15686
|
const packagePath = path.join(projectRoot, 'package.json');
|
|
15687
|
+
/**
|
|
15688
|
+
* 上一次生效的声明指纹。
|
|
15689
|
+
*
|
|
15690
|
+
* `watchFile` 是 mtime 轮询、不比较内容,版本号或依赖的普通写入也会触发;不比对就直接
|
|
15691
|
+
* `refresh()` 会清空缓存并立刻打一次远端 `detail`(密集写入可达数十次/分钟)。
|
|
15692
|
+
*/
|
|
15693
|
+
let lastDeclarationFingerprint;
|
|
15694
|
+
const readBindingFingerprint = () => context.readBoundMiniProgramIdFromPackageJson(JSON.parse(fs.readFileSync(packagePath, 'utf8')));
|
|
15695
|
+
const readDeclarationFingerprint = () => JSON.stringify([
|
|
15696
|
+
runtimePermissionEnv.readMiniappPermissionsFromPackageJson(projectRoot, runtimePermissionEnv.resolveMiniappSdkVersion()).permissions ?? null,
|
|
15697
|
+
runtimePermissionEnv.readMiniappPlatformsFromPackageJson(projectRoot),
|
|
15698
|
+
readBindingFingerprint(),
|
|
15699
|
+
]);
|
|
15700
|
+
// 先记录启动时已生效的声明,这样与调试声明无关的写入(版本号、依赖等)不会触发远端刷新。
|
|
15701
|
+
try {
|
|
15702
|
+
lastDeclarationFingerprint = readDeclarationFingerprint();
|
|
15703
|
+
}
|
|
15704
|
+
catch {
|
|
15705
|
+
// 读不出来就交给下面的 onDeclarationChange 走异常分支。
|
|
15706
|
+
}
|
|
15447
15707
|
const onDeclarationChange = () => {
|
|
15448
15708
|
try {
|
|
15449
15709
|
const previouslyTemporary = new Set(currentDevContext.localDevGrant?.entries.map(entry => entry.key));
|
|
15450
15710
|
const local = runtimePermissionEnv.readMiniappPermissionsFromPackageJson(projectRoot, runtimePermissionEnv.resolveMiniappSdkVersion());
|
|
15451
15711
|
const platforms = runtimePermissionEnv.readMiniappPlatformsFromPackageJson(projectRoot);
|
|
15712
|
+
// 与调试声明无关的写入(版本号、依赖等)不触发远端刷新。
|
|
15713
|
+
const fingerprint = JSON.stringify([local.permissions ?? null, platforms, readBindingFingerprint()]);
|
|
15714
|
+
if (fingerprint === lastDeclarationFingerprint)
|
|
15715
|
+
return;
|
|
15716
|
+
lastDeclarationFingerprint = fingerprint;
|
|
15452
15717
|
contextRevision++;
|
|
15453
15718
|
currentDevContext = { ...currentDevContext, declaredPermissions: local.permissions ?? {}, platforms,
|
|
15454
15719
|
runtimePermissions: runtimePermissionEnv.composeMiniappRuntimePermissions(local, currentDevContext.runtimePermissions).runtimePermissions };
|
|
@@ -15555,9 +15820,33 @@ async function loadDevLaunchContext(options) {
|
|
|
15555
15820
|
throw new Error(`版本资料配置失败:${index.readRedactedErrorMessage(error)}。请配置 heybox.name/icon/coverImages 后重试。`, { cause: error });
|
|
15556
15821
|
if (error instanceof context.MiniProgramProjectBindingError && error.code.startsWith('PACKAGE_JSON_'))
|
|
15557
15822
|
throw error;
|
|
15823
|
+
// A3:限流要作为结构化状态继续透出;不能和其它错误一样只报「未连接线上上下文」。
|
|
15824
|
+
const rateLimitState = index.readCliRateLimitState(error);
|
|
15825
|
+
if (rateLimitState) {
|
|
15826
|
+
return {
|
|
15827
|
+
context: {
|
|
15828
|
+
...anonymous(index.formatCliRateLimitHint(rateLimitState)).context,
|
|
15829
|
+
rateLimit: rateLimitState,
|
|
15830
|
+
},
|
|
15831
|
+
miniProgram: null,
|
|
15832
|
+
};
|
|
15833
|
+
}
|
|
15558
15834
|
return anonymous('未连接线上上下文,请检查 CLI 登录态、项目绑定和网络');
|
|
15559
15835
|
}
|
|
15560
15836
|
}
|
|
15837
|
+
/**
|
|
15838
|
+
* 输出限流提示并强提示升级。
|
|
15839
|
+
*
|
|
15840
|
+
* 老版本 CLI 无法被强制升级,只能在此明确提示「请勿反复重启」并引导更新。
|
|
15841
|
+
*/
|
|
15842
|
+
function reportDevContextRateLimit(logger, state, env) {
|
|
15843
|
+
void index.printRateLimitUpgradeHint({
|
|
15844
|
+
env,
|
|
15845
|
+
logger,
|
|
15846
|
+
...(state.retryAfterMs === undefined ? {} : { retryAfterMs: state.retryAfterMs }),
|
|
15847
|
+
...(state.message ? { gatewayMessage: state.message } : {}),
|
|
15848
|
+
}).catch(() => undefined);
|
|
15849
|
+
}
|
|
15561
15850
|
function createMiniProgramSummary(miniProgramId, detail) {
|
|
15562
15851
|
const id = typeof detail.mini_program_id === 'string' && detail.mini_program_id.trim() ? detail.mini_program_id.trim() : miniProgramId;
|
|
15563
15852
|
return {
|
|
@@ -15822,13 +16111,17 @@ function startLocalDevLaunchTokenSession(options) {
|
|
|
15822
16111
|
let hasReportedTokenFailure = false;
|
|
15823
16112
|
let refreshTimer;
|
|
15824
16113
|
let companionDeclared = options.companionDeclared;
|
|
15825
|
-
let
|
|
16114
|
+
let permissionsRefreshPending = false;
|
|
16115
|
+
let permissionsRefreshFailure;
|
|
16116
|
+
let permissionsGeneration = 0;
|
|
16117
|
+
let validatedPermissionsGeneration = 0;
|
|
15826
16118
|
let publishedToken;
|
|
15827
16119
|
let publishedDigest;
|
|
15828
16120
|
let ticket;
|
|
15829
16121
|
let pending;
|
|
15830
16122
|
let refreshing = false;
|
|
15831
|
-
|
|
16123
|
+
let consecutiveFailures = 0;
|
|
16124
|
+
const acquireTicket = async (companionManifestSha256) => {
|
|
15832
16125
|
if (closed)
|
|
15833
16126
|
return undefined;
|
|
15834
16127
|
if (ticket && (companionManifestSha256 === undefined || ticket.companionManifestSha256 === companionManifestSha256) && ticket.refreshAt > Date.now())
|
|
@@ -15838,11 +16131,14 @@ function startLocalDevLaunchTokenSession(options) {
|
|
|
15838
16131
|
const issueTicket = (companionManifestSha256) => {
|
|
15839
16132
|
if (pending && pending.digest === companionManifestSha256)
|
|
15840
16133
|
return pending.promise;
|
|
16134
|
+
const generation = permissionsGeneration;
|
|
15841
16135
|
const promise = issueLocalDevLaunchToken({
|
|
15842
16136
|
...options,
|
|
15843
16137
|
...(companionManifestSha256 ? { companionManifestSha256 } : {}),
|
|
15844
16138
|
})
|
|
15845
16139
|
.then(issued => {
|
|
16140
|
+
if (closed || generation !== permissionsGeneration)
|
|
16141
|
+
throw new DevContextSupersededError();
|
|
15846
16142
|
ticket = issued;
|
|
15847
16143
|
return issued;
|
|
15848
16144
|
}).finally(() => { if (pending?.promise === promise)
|
|
@@ -15850,15 +16146,50 @@ function startLocalDevLaunchTokenSession(options) {
|
|
|
15850
16146
|
pending = { digest: companionManifestSha256, promise };
|
|
15851
16147
|
return promise;
|
|
15852
16148
|
};
|
|
16149
|
+
const getTicket = async (companionManifestSha256) => {
|
|
16150
|
+
if (closed)
|
|
16151
|
+
return undefined;
|
|
16152
|
+
const generation = permissionsGeneration;
|
|
16153
|
+
const assertPermissionsReady = () => {
|
|
16154
|
+
if (generation !== permissionsGeneration)
|
|
16155
|
+
throw new DevContextSupersededError();
|
|
16156
|
+
if (permissionsRefreshFailure)
|
|
16157
|
+
throw permissionsRefreshFailure.error;
|
|
16158
|
+
if (validatedPermissionsGeneration !== permissionsGeneration)
|
|
16159
|
+
throw new Error('远端上下文正在刷新,请稍后再试');
|
|
16160
|
+
};
|
|
16161
|
+
assertPermissionsReady();
|
|
16162
|
+
const issued = await acquireTicket(companionManifestSha256);
|
|
16163
|
+
if (closed)
|
|
16164
|
+
return undefined;
|
|
16165
|
+
assertPermissionsReady();
|
|
16166
|
+
return issued;
|
|
16167
|
+
};
|
|
15853
16168
|
const refresh = async () => {
|
|
15854
16169
|
if (refreshing || closed)
|
|
15855
16170
|
return;
|
|
15856
16171
|
refreshing = true;
|
|
16172
|
+
const generation = permissionsGeneration;
|
|
15857
16173
|
try {
|
|
15858
|
-
if (options.refreshPermissions &&
|
|
15859
|
-
|
|
15860
|
-
|
|
16174
|
+
if (options.refreshPermissions && permissionsRefreshPending) {
|
|
16175
|
+
permissionsRefreshPending = false;
|
|
16176
|
+
try {
|
|
16177
|
+
const nextCompanionDeclared = await options.refreshPermissions();
|
|
16178
|
+
if (generation !== permissionsGeneration || closed)
|
|
16179
|
+
return;
|
|
16180
|
+
companionDeclared = nextCompanionDeclared;
|
|
16181
|
+
permissionsRefreshFailure = undefined;
|
|
16182
|
+
validatedPermissionsGeneration = generation;
|
|
16183
|
+
}
|
|
16184
|
+
catch (error) {
|
|
16185
|
+
if (generation !== permissionsGeneration || error instanceof DevContextSupersededError)
|
|
16186
|
+
return;
|
|
16187
|
+
permissionsRefreshFailure = { error };
|
|
16188
|
+
throw error;
|
|
16189
|
+
}
|
|
15861
16190
|
}
|
|
16191
|
+
if (!options.refreshPermissions)
|
|
16192
|
+
validatedPermissionsGeneration = generation;
|
|
15862
16193
|
let manifest;
|
|
15863
16194
|
let snapshotError;
|
|
15864
16195
|
try {
|
|
@@ -15871,52 +16202,82 @@ function startLocalDevLaunchTokenSession(options) {
|
|
|
15871
16202
|
catch (error) {
|
|
15872
16203
|
snapshotError = error;
|
|
15873
16204
|
}
|
|
16205
|
+
if (closed || generation !== permissionsGeneration)
|
|
16206
|
+
return;
|
|
15874
16207
|
const digest = manifest?.artifacts.length ? manifest.digest : undefined;
|
|
15875
|
-
const issued = await
|
|
16208
|
+
const issued = await acquireTicket(digest);
|
|
15876
16209
|
if (!issued)
|
|
15877
16210
|
return;
|
|
15878
|
-
if (closed)
|
|
16211
|
+
if (closed || generation !== permissionsGeneration)
|
|
15879
16212
|
return;
|
|
15880
|
-
if (publishedToken !== issued.token || publishedDigest !== digest) {
|
|
16213
|
+
if (!permissionsRefreshFailure && (publishedToken !== issued.token || publishedDigest !== digest)) {
|
|
15881
16214
|
publishedToken = issued.token;
|
|
15882
16215
|
publishedDigest = digest;
|
|
15883
16216
|
options.onToken(issued.token, digest, issued.expiresAt);
|
|
15884
16217
|
}
|
|
15885
16218
|
options.onReadiness({
|
|
15886
16219
|
browser: { state: 'ready' },
|
|
15887
|
-
mobile:
|
|
15888
|
-
|
|
15889
|
-
|
|
15890
|
-
|
|
15891
|
-
|
|
15892
|
-
|
|
15893
|
-
|
|
15894
|
-
:
|
|
16220
|
+
mobile: permissionsRefreshFailure
|
|
16221
|
+
? { state: 'blocked', reason: '远端上下文刷新失败,请更新声明或重新启动调试' }
|
|
16222
|
+
: { state: 'ready' },
|
|
16223
|
+
companion: permissionsRefreshFailure
|
|
16224
|
+
? { state: 'blocked', reason: '远端上下文刷新失败,请更新声明或重新启动调试' }
|
|
16225
|
+
: snapshotError
|
|
16226
|
+
? { state: snapshotError instanceof Error && snapshotError.message.includes('artifact-updating') ? 'pending' : 'blocked', reason: safeCompanionFailureReason(snapshotError) }
|
|
16227
|
+
: manifest?.artifacts.length
|
|
16228
|
+
? companionDeclared
|
|
16229
|
+
? { state: 'ready', digest, artifacts: manifest.artifacts, reason: '本地产物和启动票已就绪,PC 授权将在打开后验证' }
|
|
16230
|
+
: { state: 'blocked', reason: 'Companion 本地权限或桌面平台声明未开启' }
|
|
16231
|
+
: { state: 'not-configured', reason: '未配置本地 Companion 产物' },
|
|
15895
16232
|
});
|
|
15896
16233
|
if (snapshotError)
|
|
15897
16234
|
options.logger.debug(`PC Companion 快照校验失败:${index.readRedactedErrorMessage(snapshotError, { verbose: true })}`);
|
|
15898
16235
|
const ttl = Math.max(0, issued.expiresAt - Date.now());
|
|
15899
|
-
|
|
16236
|
+
if (!permissionsRefreshFailure)
|
|
16237
|
+
scheduleExpiration(ttl);
|
|
15900
16238
|
schedule(Math.min(2_000, Math.max(1_000, Math.floor(ttl * 0.8))));
|
|
15901
|
-
if (hasReportedTokenFailure) {
|
|
16239
|
+
if (hasReportedTokenFailure && !permissionsRefreshFailure) {
|
|
15902
16240
|
hasReportedTokenFailure = false;
|
|
15903
16241
|
options.logger.success('本地调试凭证已恢复');
|
|
15904
16242
|
}
|
|
16243
|
+
if (consecutiveFailures > 0 && !permissionsRefreshFailure) {
|
|
16244
|
+
consecutiveFailures = 0;
|
|
16245
|
+
options.onRateLimit?.(null);
|
|
16246
|
+
}
|
|
15905
16247
|
}
|
|
15906
16248
|
catch (error) {
|
|
15907
|
-
if (closed)
|
|
16249
|
+
if (closed || generation !== permissionsGeneration || error instanceof DevContextSupersededError)
|
|
15908
16250
|
return;
|
|
15909
|
-
|
|
16251
|
+
// A2 + A3:限流类错误走 30s 起、上限 300s 的退避,并明确提示不要反复重启。
|
|
16252
|
+
const rateLimitState = index.readCliRateLimitState(error);
|
|
16253
|
+
consecutiveFailures += 1;
|
|
16254
|
+
if (rateLimitState) {
|
|
16255
|
+
// 限流提示统一由 onRateLimit 出口负责(runDevCommand 走 reportRateLimitOnce,
|
|
16256
|
+
// 保证同一文案只输出一次);这里不再自己 warn,避免同一次限流打印两行。
|
|
16257
|
+
options.onRateLimit?.(rateLimitState);
|
|
16258
|
+
}
|
|
16259
|
+
else if (!hasReportedTokenFailure) {
|
|
15910
16260
|
options.logger.warn('本地调试凭证获取失败,将自动重试;Web / Browser Fake 仍可使用');
|
|
15911
16261
|
}
|
|
15912
16262
|
hasReportedTokenFailure = true;
|
|
15913
|
-
options.onReadiness({ browser: { state: 'ready' },
|
|
16263
|
+
options.onReadiness({ browser: { state: 'ready' },
|
|
16264
|
+
mobile: { state: 'blocked', reason: rateLimitState ? index.formatCliRateLimitHint(rateLimitState) : '调试凭证暂不可用' },
|
|
15914
16265
|
companion: { state: 'blocked', reason: safeCompanionFailureReason(error) } });
|
|
15915
16266
|
options.logger.debug(`本地调试凭证获取失败,详细原因:${index.readRedactedErrorMessage(error, { verbose: true })}`);
|
|
15916
|
-
schedule(
|
|
16267
|
+
schedule(computeDevContextBackoffDelayMs({
|
|
16268
|
+
attempt: consecutiveFailures - 1,
|
|
16269
|
+
env: options.env,
|
|
16270
|
+
rateLimited: rateLimitState !== null,
|
|
16271
|
+
}));
|
|
15917
16272
|
}
|
|
15918
16273
|
finally {
|
|
15919
16274
|
refreshing = false;
|
|
16275
|
+
// 请求期间有新声明时立即处理最新一代,避免被并发保护丢掉刷新事件。
|
|
16276
|
+
if (permissionsRefreshPending && !closed) {
|
|
16277
|
+
if (refreshTimer)
|
|
16278
|
+
clearTimeout(refreshTimer);
|
|
16279
|
+
schedule(0);
|
|
16280
|
+
}
|
|
15920
16281
|
}
|
|
15921
16282
|
};
|
|
15922
16283
|
const schedule = (delayMs) => {
|
|
@@ -15943,7 +16304,12 @@ function startLocalDevLaunchTokenSession(options) {
|
|
|
15943
16304
|
return {
|
|
15944
16305
|
getTicket,
|
|
15945
16306
|
refresh: () => {
|
|
15946
|
-
|
|
16307
|
+
// 项目绑定变更或开发者主动重试:主动失效 Dev Context 缓存后立即重新请求。
|
|
16308
|
+
options.onInvalidate?.();
|
|
16309
|
+
permissionsGeneration += 1;
|
|
16310
|
+
permissionsRefreshPending = true;
|
|
16311
|
+
ticket = undefined;
|
|
16312
|
+
pending = undefined;
|
|
15947
16313
|
publishedDigest = undefined;
|
|
15948
16314
|
if (refreshTimer)
|
|
15949
16315
|
clearTimeout(refreshTimer);
|
|
@@ -16194,8 +16560,12 @@ function isPrivateIPv4(address) {
|
|
|
16194
16560
|
|
|
16195
16561
|
var dev = /*#__PURE__*/Object.freeze({
|
|
16196
16562
|
__proto__: null,
|
|
16563
|
+
computeDevContextBackoffDelayMs: computeDevContextBackoffDelayMs,
|
|
16564
|
+
createDevContextCache: createDevContextCache,
|
|
16197
16565
|
createMiniProgramDevLaunchVitePlugin: createMiniProgramDevLaunchVitePlugin,
|
|
16198
16566
|
normalizeExpiresAt: normalizeExpiresAt,
|
|
16567
|
+
resolveDevContextBackoffLimits: resolveDevContextBackoffLimits,
|
|
16568
|
+
resolveDevContextCacheTtlMs: resolveDevContextCacheTtlMs,
|
|
16199
16569
|
runDevCommand: runDevCommand,
|
|
16200
16570
|
startLocalDevLaunchTokenSession: startLocalDevLaunchTokenSession
|
|
16201
16571
|
});
|