@skrillex1224/playwright-toolkit 2.1.171 → 2.1.180

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/dist/index.js CHANGED
@@ -308,18 +308,18 @@ var fallbackLog = {
308
308
  error: (...args) => console.error(...args),
309
309
  debug: (...args) => console.debug ? console.debug(...args) : console.log(...args)
310
310
  };
311
- var resolveLogMethod = (logger13, name) => {
312
- if (logger13 && typeof logger13[name] === "function") {
313
- return logger13[name].bind(logger13);
311
+ var resolveLogMethod = (logger12, name) => {
312
+ if (logger12 && typeof logger12[name] === "function") {
313
+ return logger12[name].bind(logger12);
314
314
  }
315
- if (name === "warning" && logger13 && typeof logger13.warn === "function") {
316
- return logger13.warn.bind(logger13);
315
+ if (name === "warning" && logger12 && typeof logger12.warn === "function") {
316
+ return logger12.warn.bind(logger12);
317
317
  }
318
318
  return fallbackLog[name];
319
319
  };
320
320
  var defaultLogger = null;
321
- var setDefaultLogger = (logger13) => {
322
- defaultLogger = logger13;
321
+ var setDefaultLogger = (logger12) => {
322
+ defaultLogger = logger12;
323
323
  };
324
324
  var resolveLogger = (explicitLogger) => {
325
325
  if (explicitLogger && typeof explicitLogger.info === "function") {
@@ -346,8 +346,8 @@ var colorize = (text, color) => {
346
346
  var createBaseLogger = (prefix = "", explicitLogger) => {
347
347
  const name = prefix ? String(prefix) : "";
348
348
  const dispatch = (methodName, icon, message, color) => {
349
- const logger13 = resolveLogger(explicitLogger);
350
- const logFn = resolveLogMethod(logger13, methodName);
349
+ const logger12 = resolveLogger(explicitLogger);
350
+ const logFn = resolveLogMethod(logger12, methodName);
351
351
  const timestamp = colorize(`[${formatTimestamp()}]`, ANSI.gray);
352
352
  const line = formatLine(name, icon, message);
353
353
  const coloredLine = colorize(line, color);
@@ -909,8 +909,582 @@ var ProxyMeterRuntime = {
909
909
  getProxyMeterSnapshot
910
910
  };
911
911
 
912
+ // src/runtime-env.js
913
+ var BROWSER_PROFILE_SCHEMA_VERSION = 1;
914
+ var rememberedRuntimeState = null;
915
+ var isPlainObject = (value) => value && typeof value === "object" && !Array.isArray(value);
916
+ var deepClone = (value) => {
917
+ if (value == null) return value;
918
+ try {
919
+ return JSON.parse(JSON.stringify(value));
920
+ } catch {
921
+ return value;
922
+ }
923
+ };
924
+ var tryParseJSON = (value) => {
925
+ if (value == null) return null;
926
+ if (typeof value === "object") return value;
927
+ const raw = String(value || "").trim();
928
+ if (!raw) return null;
929
+ try {
930
+ return JSON.parse(raw);
931
+ } catch {
932
+ return null;
933
+ }
934
+ };
935
+ var normalizeLocalStorage = (value) => {
936
+ const source = value && typeof value === "object" && !Array.isArray(value) ? value : {};
937
+ return Object.entries(source).reduce((acc, [key, item]) => {
938
+ const safeKey = String(key || "").trim();
939
+ const safeValue = String(item ?? "").trim();
940
+ if (!safeKey || !safeValue || safeValue === "<nil>") return acc;
941
+ acc[safeKey] = safeValue;
942
+ return acc;
943
+ }, {});
944
+ };
945
+ var normalizeSessionStorage = (value) => {
946
+ const source = value && typeof value === "object" && !Array.isArray(value) ? value : {};
947
+ return Object.entries(source).reduce((acc, [key, item]) => {
948
+ const safeKey = String(key || "").trim();
949
+ const safeValue = String(item ?? "").trim();
950
+ if (!safeKey || !safeValue || safeValue === "<nil>") return acc;
951
+ acc[safeKey] = safeValue;
952
+ return acc;
953
+ }, {});
954
+ };
955
+ var normalizeAuth = (value) => {
956
+ const source = value && typeof value === "object" && !Array.isArray(value) ? value : {};
957
+ return Object.entries(source).reduce((acc, [key, item]) => {
958
+ const safeKey = String(key || "").trim();
959
+ const safeValue = String(item ?? "").trim();
960
+ if (!safeKey || !safeValue || safeValue === "<nil>") return acc;
961
+ acc[safeKey] = safeValue;
962
+ return acc;
963
+ }, {});
964
+ };
965
+ var normalizeCookies = (value) => {
966
+ if (!Array.isArray(value)) return [];
967
+ return value.map((item) => {
968
+ const raw = item && typeof item === "object" ? item : {};
969
+ const name = String(raw.name || "").trim();
970
+ const cookieValue = String(raw.value ?? "").trim();
971
+ if (!name || !cookieValue || cookieValue === "<nil>") return null;
972
+ const domain = String(raw.domain || "").trim();
973
+ const path2 = String(raw.path || "").trim() || "/";
974
+ const sameSiteRaw = String(raw.sameSite || "").trim();
975
+ return {
976
+ ...raw,
977
+ name,
978
+ value: cookieValue,
979
+ domain,
980
+ path: path2,
981
+ ...sameSiteRaw ? { sameSite: sameSiteRaw } : {}
982
+ };
983
+ }).filter(Boolean);
984
+ };
985
+ var buildCookieMap = (cookies) => cookies.reduce((acc, item) => {
986
+ acc[item.name] = item.value;
987
+ return acc;
988
+ }, {});
989
+ var normalizeObservedBrowserProfile = (value) => {
990
+ const source = isPlainObject(value) ? value : {};
991
+ const profile = {};
992
+ const stringFields = {
993
+ user_agent: source.user_agent,
994
+ platform: source.platform,
995
+ language: source.language,
996
+ timezone: source.timezone
997
+ };
998
+ Object.entries(stringFields).forEach(([key, item]) => {
999
+ const safeValue = String(item || "").trim();
1000
+ if (safeValue) {
1001
+ profile[key] = safeValue;
1002
+ }
1003
+ });
1004
+ if (Array.isArray(source.languages)) {
1005
+ const languages = source.languages.map((item) => String(item || "").trim()).filter(Boolean);
1006
+ if (languages.length > 0) {
1007
+ profile.languages = languages;
1008
+ }
1009
+ }
1010
+ const numericFields = {
1011
+ hardware_concurrency: Number(source.hardware_concurrency || 0),
1012
+ device_memory: Number(source.device_memory || 0)
1013
+ };
1014
+ Object.entries(numericFields).forEach(([key, item]) => {
1015
+ if (Number.isFinite(item) && item > 0) {
1016
+ profile[key] = item;
1017
+ }
1018
+ });
1019
+ if (isPlainObject(source.viewport)) {
1020
+ const width = Number(source.viewport.width || 0);
1021
+ const height = Number(source.viewport.height || 0);
1022
+ if (width > 0 && height > 0) {
1023
+ profile.viewport = { width, height };
1024
+ }
1025
+ }
1026
+ if (isPlainObject(source.screen)) {
1027
+ const screenProfile = {};
1028
+ const screenFields = {
1029
+ width: Number(source.screen.width || 0),
1030
+ height: Number(source.screen.height || 0),
1031
+ avail_width: Number(source.screen.avail_width || 0),
1032
+ avail_height: Number(source.screen.avail_height || 0),
1033
+ color_depth: Number(source.screen.color_depth || 0),
1034
+ pixel_depth: Number(source.screen.pixel_depth || 0)
1035
+ };
1036
+ Object.entries(screenFields).forEach(([key, item]) => {
1037
+ if (Number.isFinite(item) && item > 0) {
1038
+ screenProfile[key] = item;
1039
+ }
1040
+ });
1041
+ if (Object.keys(screenProfile).length > 0) {
1042
+ profile.screen = screenProfile;
1043
+ }
1044
+ }
1045
+ return profile;
1046
+ };
1047
+ var normalizeBrowserProfileCore = (value) => {
1048
+ const source = isPlainObject(value) ? value : {};
1049
+ const profile = {};
1050
+ if (isPlainObject(source.fingerprint) && Object.keys(source.fingerprint).length > 0) {
1051
+ profile.fingerprint = deepClone(source.fingerprint);
1052
+ }
1053
+ const timezoneId = String(source.timezone_id || "").trim();
1054
+ if (timezoneId) {
1055
+ profile.timezone_id = timezoneId;
1056
+ }
1057
+ const locale = String(source.locale || "").trim();
1058
+ if (locale) {
1059
+ profile.locale = locale;
1060
+ }
1061
+ const profileKey = String(source.profile_key || "").trim();
1062
+ if (profileKey) {
1063
+ profile.profile_key = profileKey;
1064
+ }
1065
+ const browserMajorVersion = Number(source.browser_major_version || 0);
1066
+ if (Number.isFinite(browserMajorVersion) && browserMajorVersion > 0) {
1067
+ profile.browser_major_version = browserMajorVersion;
1068
+ }
1069
+ const schemaVersion = Number(source.schema_version || 0);
1070
+ if (Number.isFinite(schemaVersion) && schemaVersion > 0) {
1071
+ profile.schema_version = schemaVersion;
1072
+ } else if (Object.keys(profile).length > 0) {
1073
+ profile.schema_version = BROWSER_PROFILE_SCHEMA_VERSION;
1074
+ }
1075
+ return profile;
1076
+ };
1077
+ var buildBrowserProfilePayload = (core = {}, observed = {}) => {
1078
+ const payload = {};
1079
+ if (isPlainObject(core) && Object.keys(core).length > 0) {
1080
+ payload.core = core;
1081
+ }
1082
+ if (isPlainObject(observed) && Object.keys(observed).length > 0) {
1083
+ payload.observed = observed;
1084
+ }
1085
+ return payload;
1086
+ };
1087
+ var mergePlainObject = (target = {}, source = {}) => {
1088
+ const next = isPlainObject(target) ? deepClone(target) : {};
1089
+ if (!isPlainObject(source)) return next;
1090
+ Object.entries(source).forEach(([key, value]) => {
1091
+ if (!key) return;
1092
+ if (isPlainObject(value) && isPlainObject(next[key])) {
1093
+ next[key] = mergePlainObject(next[key], value);
1094
+ return;
1095
+ }
1096
+ next[key] = deepClone(value);
1097
+ });
1098
+ return next;
1099
+ };
1100
+ var mergeBrowserProfilePayload = (target = {}, source = {}) => {
1101
+ const current = isPlainObject(target) ? target : {};
1102
+ const incoming = isPlainObject(source) ? source : {};
1103
+ const currentCore = normalizeBrowserProfileCore(current.core);
1104
+ const incomingCore = normalizeBrowserProfileCore(incoming.core);
1105
+ const currentObserved = normalizeObservedBrowserProfile(current.observed);
1106
+ const incomingObserved = normalizeObservedBrowserProfile(incoming.observed);
1107
+ let mergedCore = currentCore;
1108
+ if (Object.keys(mergedCore).length === 0 && Object.keys(incomingCore).length > 0) {
1109
+ mergedCore = incomingCore;
1110
+ } else if (Object.keys(incomingCore).length > 0) {
1111
+ const currentVersion = Number(currentCore.browser_major_version || 0);
1112
+ const incomingVersion = Number(incomingCore.browser_major_version || 0);
1113
+ if (currentVersion > 0 && incomingVersion > 0 && currentVersion !== incomingVersion) {
1114
+ mergedCore = incomingCore;
1115
+ }
1116
+ }
1117
+ const mergedObserved = mergePlainObject(currentObserved, incomingObserved);
1118
+ return buildBrowserProfilePayload(mergedCore, mergedObserved);
1119
+ };
1120
+ var mergeEnvPatchObjects = (...patches) => {
1121
+ const merged = {};
1122
+ patches.forEach((patch) => {
1123
+ if (!isPlainObject(patch)) return;
1124
+ Object.entries(patch).forEach(([key, value]) => {
1125
+ if (!key) return;
1126
+ if (key === "browser_profile") {
1127
+ const browserProfile = mergeBrowserProfilePayload(merged.browser_profile, value);
1128
+ if (Object.keys(browserProfile).length > 0) {
1129
+ merged.browser_profile = browserProfile;
1130
+ }
1131
+ return;
1132
+ }
1133
+ if (isPlainObject(value) && isPlainObject(merged[key])) {
1134
+ merged[key] = mergePlainObject(merged[key], value);
1135
+ return;
1136
+ }
1137
+ merged[key] = deepClone(value);
1138
+ });
1139
+ });
1140
+ return Object.keys(merged).length > 0 ? merged : null;
1141
+ };
1142
+ var normalizeBrowserProfile = (value) => {
1143
+ const source = isPlainObject(value) ? value : {};
1144
+ const coreSource = isPlainObject(source.core) ? source.core : {};
1145
+ const observedSource = isPlainObject(source.observed) ? source.observed : {};
1146
+ const core = normalizeBrowserProfileCore(coreSource);
1147
+ const observed = normalizeObservedBrowserProfile(observedSource);
1148
+ return {
1149
+ core,
1150
+ observed,
1151
+ payload: buildBrowserProfilePayload(core, observed)
1152
+ };
1153
+ };
1154
+ var rememberRuntimeState = (state) => {
1155
+ rememberedRuntimeState = deepClone(state);
1156
+ return rememberedRuntimeState;
1157
+ };
1158
+ var normalizeRuntimeState = (source = {}, actor = "") => {
1159
+ if (source && typeof source === "object" && source.runtime && typeof source.runtime === "object" && !Array.isArray(source.runtime) && Object.prototype.hasOwnProperty.call(source, "browserProfileCore")) {
1160
+ return source;
1161
+ }
1162
+ return RuntimeEnv.parseInput(source, actor);
1163
+ };
1164
+ var RuntimeEnv = {
1165
+ tryParseJSON,
1166
+ normalizeCookies,
1167
+ normalizeLocalStorage,
1168
+ normalizeSessionStorage,
1169
+ normalizeAuth,
1170
+ normalizeBrowserProfileCore,
1171
+ normalizeObservedBrowserProfile,
1172
+ mergeEnvPatches(...patches) {
1173
+ return mergeEnvPatchObjects(...patches);
1174
+ },
1175
+ // parseInput 把 Actor 输入里的 runtime 字段标准化成 toolkit 内部统一结构。
1176
+ // 后续 visitor 只和这个 state 交互,不再自己拼 token/cookie 逻辑。
1177
+ parseInput(input = {}, actor = "") {
1178
+ const runtime2 = tryParseJSON(input?.runtime) || {};
1179
+ const resolvedActor = String(actor || input?.actor || "").trim();
1180
+ const cookies = normalizeCookies(runtime2?.cookies);
1181
+ const cookieMap = buildCookieMap(cookies);
1182
+ const localStorage = normalizeLocalStorage(runtime2?.local_storage);
1183
+ const sessionStorage = normalizeSessionStorage(runtime2?.session_storage);
1184
+ const auth = normalizeAuth(runtime2?.auth);
1185
+ const browserProfile = normalizeBrowserProfile(runtime2?.browser_profile);
1186
+ const envId = String(input?.env_id || "").trim();
1187
+ const normalizedRuntime = {
1188
+ ...runtime2,
1189
+ ...cookies.length > 0 ? { cookies } : {},
1190
+ ...Object.keys(localStorage).length > 0 ? { local_storage: localStorage } : {},
1191
+ ...Object.keys(sessionStorage).length > 0 ? { session_storage: sessionStorage } : {},
1192
+ ...Object.keys(auth).length > 0 ? { auth } : {}
1193
+ };
1194
+ delete normalizedRuntime.actor;
1195
+ delete normalizedRuntime.env_id;
1196
+ if (Object.keys(browserProfile.payload).length > 0) {
1197
+ normalizedRuntime.browser_profile = browserProfile.payload;
1198
+ } else {
1199
+ delete normalizedRuntime.browser_profile;
1200
+ }
1201
+ const state = {
1202
+ actor: resolvedActor,
1203
+ runtime: normalizedRuntime,
1204
+ envId,
1205
+ auth,
1206
+ cookies,
1207
+ cookieMap,
1208
+ localStorage,
1209
+ sessionStorage,
1210
+ browserProfile: browserProfile.payload,
1211
+ browserProfileCore: browserProfile.core,
1212
+ browserProfileObserved: browserProfile.observed
1213
+ };
1214
+ rememberRuntimeState(state);
1215
+ return state;
1216
+ },
1217
+ // buildEnvPatch 只构造允许回写到后端 env 的字段集合。
1218
+ buildEnvPatch(source = {}, actor = "") {
1219
+ const state = normalizeRuntimeState(source, actor);
1220
+ const browserProfile = buildBrowserProfilePayload(state.browserProfileCore, state.browserProfileObserved);
1221
+ const envPatch = {
1222
+ ...Object.keys(state.auth || {}).length > 0 ? { auth: state.auth } : {},
1223
+ ...Array.isArray(state.cookies) && state.cookies.length > 0 ? { cookies: state.cookies } : {},
1224
+ ...Object.keys(state.localStorage || {}).length > 0 ? { local_storage: state.localStorage } : {},
1225
+ ...Object.keys(state.sessionStorage || {}).length > 0 ? { session_storage: state.sessionStorage } : {},
1226
+ ...Object.keys(browserProfile).length > 0 ? { browser_profile: browserProfile } : {}
1227
+ };
1228
+ return Object.keys(envPatch).length > 0 ? envPatch : null;
1229
+ },
1230
+ // hasLoginState 用来区分“必须鉴权的平台”和“可匿名运行的平台”。
1231
+ hasLoginState(source = {}, actor = "") {
1232
+ const state = normalizeRuntimeState(source, actor);
1233
+ return Array.isArray(state.cookies) && state.cookies.length > 0 || Object.keys(state.localStorage || {}).length > 0 || Object.keys(state.sessionStorage || {}).length > 0 || Object.keys(state.auth || {}).length > 0;
1234
+ },
1235
+ getAuthValue(source = {}, key = "", actor = "") {
1236
+ const state = normalizeRuntimeState(source, actor);
1237
+ const safeKey = String(key || "").trim();
1238
+ if (!safeKey) return "";
1239
+ return String(state.auth?.[safeKey] ?? "").trim();
1240
+ },
1241
+ rememberState(source = {}) {
1242
+ const state = normalizeRuntimeState(source);
1243
+ rememberRuntimeState(state);
1244
+ return RuntimeEnv.peekRememberedState();
1245
+ },
1246
+ peekRememberedState() {
1247
+ return rememberedRuntimeState ? deepClone(rememberedRuntimeState) : null;
1248
+ },
1249
+ getBrowserProfileCore(source = {}, actor = "") {
1250
+ const state = normalizeRuntimeState(source, actor);
1251
+ return deepClone(state.browserProfileCore || {});
1252
+ },
1253
+ setBrowserProfileCore(source = {}, core = {}, actor = "") {
1254
+ const state = normalizeRuntimeState(source, actor);
1255
+ const normalizedCore = normalizeBrowserProfileCore(core);
1256
+ state.browserProfileCore = normalizedCore;
1257
+ state.browserProfile = buildBrowserProfilePayload(normalizedCore, state.browserProfileObserved);
1258
+ if (Object.keys(state.browserProfile).length > 0) {
1259
+ state.runtime.browser_profile = state.browserProfile;
1260
+ } else {
1261
+ delete state.runtime.browser_profile;
1262
+ }
1263
+ rememberRuntimeState(state);
1264
+ return state;
1265
+ },
1266
+ // applyToPage 只负责把登录态相关字段注入页面:
1267
+ // cookies / localStorage / sessionStorage。
1268
+ // 指纹、时区、UA、viewport 的回放发生在 launch.js 启动阶段,不在这里做。
1269
+ async applyToPage(page, source = {}, options = {}) {
1270
+ if (!page) return;
1271
+ const state = normalizeRuntimeState(source, options?.actor || "");
1272
+ const localStorage = state.localStorage || {};
1273
+ const sessionStorage = state.sessionStorage || {};
1274
+ const cookies = (state.cookies || []).map((cookie) => {
1275
+ const normalized = { ...cookie };
1276
+ if (!normalized.path) {
1277
+ normalized.path = "/";
1278
+ }
1279
+ if (!normalized.domain) {
1280
+ return null;
1281
+ }
1282
+ return normalized;
1283
+ }).filter(Boolean);
1284
+ if (cookies.length > 0) {
1285
+ await page.context().addCookies(cookies);
1286
+ }
1287
+ if (Object.keys(localStorage).length > 0) {
1288
+ await page.addInitScript((payload) => {
1289
+ try {
1290
+ Object.entries(payload || {}).forEach(([key, value]) => {
1291
+ if (!key) return;
1292
+ window.localStorage.setItem(key, String(value ?? ""));
1293
+ });
1294
+ } catch (error) {
1295
+ }
1296
+ }, localStorage);
1297
+ }
1298
+ if (Object.keys(sessionStorage).length > 0) {
1299
+ await page.addInitScript((payload) => {
1300
+ try {
1301
+ Object.entries(payload || {}).forEach(([key, value]) => {
1302
+ if (!key) return;
1303
+ window.sessionStorage.setItem(key, String(value ?? ""));
1304
+ });
1305
+ } catch (error) {
1306
+ }
1307
+ }, sessionStorage);
1308
+ }
1309
+ },
1310
+ // captureEnvPatch 在任务结束时采集最新环境快照,用于 pushSuccess / pushFailed 自动回写。
1311
+ async captureEnvPatch(page, source = {}, options = {}) {
1312
+ const state = normalizeRuntimeState(source, options?.actor || "");
1313
+ const baseline = RuntimeEnv.buildEnvPatch(state) || {};
1314
+ const patch = {
1315
+ ...baseline
1316
+ };
1317
+ if (!page || typeof page.evaluate !== "function" || typeof page.context !== "function") {
1318
+ return Object.keys(patch).length > 0 ? patch : null;
1319
+ }
1320
+ try {
1321
+ const contextCookies = await page.context().cookies();
1322
+ const normalizedCookies = normalizeCookies(contextCookies || []);
1323
+ if (normalizedCookies.length > 0) {
1324
+ patch.cookies = normalizedCookies;
1325
+ }
1326
+ } catch (error) {
1327
+ }
1328
+ try {
1329
+ const snapshot = await page.evaluate(() => {
1330
+ const localStorage2 = {};
1331
+ const sessionStorage2 = {};
1332
+ try {
1333
+ for (let i = 0; i < window.localStorage.length; i += 1) {
1334
+ const key = window.localStorage.key(i);
1335
+ if (!key) continue;
1336
+ const value = window.localStorage.getItem(key);
1337
+ if (value !== null) localStorage2[key] = value;
1338
+ }
1339
+ } catch (error) {
1340
+ }
1341
+ try {
1342
+ for (let i = 0; i < window.sessionStorage.length; i += 1) {
1343
+ const key = window.sessionStorage.key(i);
1344
+ if (!key) continue;
1345
+ const value = window.sessionStorage.getItem(key);
1346
+ if (value !== null) sessionStorage2[key] = value;
1347
+ }
1348
+ } catch (error) {
1349
+ }
1350
+ const nav = window.navigator || {};
1351
+ const screen = window.screen || {};
1352
+ const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone || "";
1353
+ return {
1354
+ localStorage: localStorage2,
1355
+ sessionStorage: sessionStorage2,
1356
+ browserProfileObserved: {
1357
+ user_agent: nav.userAgent || "",
1358
+ platform: nav.platform || "",
1359
+ language: nav.language || "",
1360
+ languages: Array.isArray(nav.languages) ? nav.languages : [],
1361
+ hardware_concurrency: Number(nav.hardwareConcurrency || 0),
1362
+ device_memory: Number(nav.deviceMemory || 0),
1363
+ timezone,
1364
+ viewport: {
1365
+ width: Number(window.innerWidth || 0),
1366
+ height: Number(window.innerHeight || 0)
1367
+ },
1368
+ screen: {
1369
+ width: Number(screen.width || 0),
1370
+ height: Number(screen.height || 0),
1371
+ avail_width: Number(screen.availWidth || 0),
1372
+ avail_height: Number(screen.availHeight || 0),
1373
+ color_depth: Number(screen.colorDepth || 0),
1374
+ pixel_depth: Number(screen.pixelDepth || 0)
1375
+ }
1376
+ }
1377
+ };
1378
+ });
1379
+ const localStorage = normalizeLocalStorage(snapshot?.localStorage);
1380
+ if (Object.keys(localStorage).length > 0) {
1381
+ patch.local_storage = localStorage;
1382
+ }
1383
+ const sessionStorage = normalizeSessionStorage(snapshot?.sessionStorage);
1384
+ if (Object.keys(sessionStorage).length > 0) {
1385
+ patch.session_storage = sessionStorage;
1386
+ }
1387
+ const observedProfile = normalizeObservedBrowserProfile(snapshot?.browserProfileObserved);
1388
+ const browserProfile = buildBrowserProfilePayload(state.browserProfileCore, observedProfile);
1389
+ if (Object.keys(browserProfile).length > 0) {
1390
+ patch.browser_profile = browserProfile;
1391
+ }
1392
+ } catch (error) {
1393
+ }
1394
+ return Object.keys(patch).length > 0 ? patch : null;
1395
+ }
1396
+ };
1397
+
912
1398
  // src/apify-kit.js
913
1399
  var logger3 = createInternalLogger("ApifyKit");
1400
+ var resolveRuntimeContext = (input) => {
1401
+ const rememberedState = RuntimeEnv.peekRememberedState();
1402
+ const state = rememberedState || RuntimeEnv.parseInput(input || {});
1403
+ const envPatch = RuntimeEnv.buildEnvPatch(state) || null;
1404
+ return {
1405
+ actor: state.actor,
1406
+ runtime: state.runtime,
1407
+ envPatch
1408
+ };
1409
+ };
1410
+ var isPageLike = (value) => {
1411
+ if (!value || typeof value !== "object") return false;
1412
+ return typeof value.evaluate === "function" && typeof value.context === "function";
1413
+ };
1414
+ var pickPage = (...candidates) => {
1415
+ for (const candidate of candidates) {
1416
+ if (isPageLike(candidate)) {
1417
+ return candidate;
1418
+ }
1419
+ }
1420
+ return null;
1421
+ };
1422
+ var toSerializable = (value, depth = 0, seen = /* @__PURE__ */ new WeakSet()) => {
1423
+ if (depth > 6) return "[MaxDepth]";
1424
+ if (value == null) return value;
1425
+ const valueType = typeof value;
1426
+ if (valueType === "string" || valueType === "number" || valueType === "boolean") {
1427
+ return value;
1428
+ }
1429
+ if (valueType === "bigint") {
1430
+ return value.toString();
1431
+ }
1432
+ if (valueType === "function" || valueType === "symbol") {
1433
+ return void 0;
1434
+ }
1435
+ if (value instanceof Error) {
1436
+ return serializeError2(value);
1437
+ }
1438
+ if (Array.isArray(value)) {
1439
+ return value.map((item) => toSerializable(item, depth + 1, seen)).filter((item) => item !== void 0);
1440
+ }
1441
+ if (valueType !== "object") {
1442
+ return String(value);
1443
+ }
1444
+ if (seen.has(value)) {
1445
+ return "[Circular]";
1446
+ }
1447
+ seen.add(value);
1448
+ const ctorName = String(value?.constructor?.name || "");
1449
+ if (ctorName === "Page" || ctorName === "BrowserContext" || ctorName === "Browser" || ctorName === "Frame" || ctorName === "ElementHandle" || ctorName === "JSHandle") {
1450
+ return `[${ctorName}]`;
1451
+ }
1452
+ const output = {};
1453
+ Object.entries(value).forEach(([key, item]) => {
1454
+ const safeValue = toSerializable(item, depth + 1, seen);
1455
+ if (safeValue !== void 0) {
1456
+ output[key] = safeValue;
1457
+ }
1458
+ });
1459
+ return output;
1460
+ };
1461
+ var sanitizeMeta = (meta) => {
1462
+ if (!meta || typeof meta !== "object" || Array.isArray(meta)) {
1463
+ return {};
1464
+ }
1465
+ const result = toSerializable(meta) || {};
1466
+ if (result && typeof result === "object" && !Array.isArray(result)) {
1467
+ delete result.page;
1468
+ }
1469
+ return result;
1470
+ };
1471
+ var captureAutoEnvPatch = async (page, runtimeContext) => {
1472
+ const currentPage = pickPage(page);
1473
+ if (!currentPage) return null;
1474
+ try {
1475
+ return await RuntimeEnv.captureEnvPatch(
1476
+ currentPage,
1477
+ {
1478
+ actor: runtimeContext.actor,
1479
+ runtime: runtimeContext.runtime
1480
+ },
1481
+ { actor: runtimeContext.actor }
1482
+ );
1483
+ } catch (error) {
1484
+ logger3.warn(`\u81EA\u52A8\u91C7\u96C6 env_patch \u5931\u8D25: ${error?.message || error}`);
1485
+ return null;
1486
+ }
1487
+ };
914
1488
  async function createApifyKit() {
915
1489
  let apify = null;
916
1490
  try {
@@ -919,6 +1493,9 @@ async function createApifyKit() {
919
1493
  throw new Error("\u26A0\uFE0F apify \u5E93\u672A\u5B89\u88C5\uFF0CApifyKit \u7684 Actor \u76F8\u5173\u529F\u80FD\u4E0D\u53EF\u7528");
920
1494
  }
921
1495
  const { Actor: Actor2 } = apify;
1496
+ const actorInput = await Actor2.getInput() || {};
1497
+ let lastPage = null;
1498
+ const getRuntimeContext = () => resolveRuntimeContext(actorInput);
922
1499
  return {
923
1500
  /**
924
1501
  * 核心封装:执行步骤,带自动日志确认、失败截图处理和重试机制
@@ -934,6 +1511,9 @@ async function createApifyKit() {
934
1511
  * @param {Function} [options.retry.before] - 重试前钩子,可覆盖默认等待行为
935
1512
  */
936
1513
  async runStep(step, page, actionFn, options = {}) {
1514
+ if (isPageLike(page)) {
1515
+ lastPage = page;
1516
+ }
937
1517
  const { failActor = true, retry = {} } = options;
938
1518
  const { times: retryTimes = 0, mode: retryMode = "direct", before: beforeRetry } = retry;
939
1519
  const executeAction = async (attemptNumber) => {
@@ -995,13 +1575,18 @@ async function createApifyKit() {
995
1575
  } catch (snapErr) {
996
1576
  logger3.warn(`\u622A\u56FE\u751F\u6210\u5931\u8D25: ${snapErr.message}`);
997
1577
  }
998
- await this.pushFailed(finalError, {
999
- step,
1000
- page,
1001
- options,
1002
- base64,
1003
- retryAttempts: retryTimes
1004
- });
1578
+ await this.pushFailed(
1579
+ finalError,
1580
+ {
1581
+ step,
1582
+ base64,
1583
+ retryAttempts: retryTimes
1584
+ },
1585
+ {
1586
+ page,
1587
+ options
1588
+ }
1589
+ );
1005
1590
  await Actor2.fail(`Run Step ${step} \u5931\u8D25 (\u5DF2\u91CD\u8BD5 ${retryTimes} \u6B21): ${finalError.message}`);
1006
1591
  } else {
1007
1592
  throw finalError;
@@ -1023,42 +1608,83 @@ async function createApifyKit() {
1023
1608
  });
1024
1609
  },
1025
1610
  /**
1026
- * 推送成功数据的通用方法
1611
+ * 推送成功数据的通用方法。
1612
+ * 这里会自动做三件事:
1613
+ * 1. 采集当前页面的 env_patch;
1614
+ * 2. 合并显式传入的 env_patch;
1615
+ * 3. 一并写入 dataset,供后端回写 runtime_env_json。
1027
1616
  * @param {Object} data - 要推送的数据对象
1028
1617
  */
1029
- async pushSuccess(data) {
1618
+ async pushSuccess(data, options = {}) {
1619
+ const runtimeContext = getRuntimeContext();
1620
+ const page = pickPage(options?.page, data?.page, lastPage);
1621
+ if (page) {
1622
+ lastPage = page;
1623
+ }
1624
+ let payloadData = data;
1625
+ let explicitPatch = null;
1626
+ if (data && typeof data === "object" && !Array.isArray(data)) {
1627
+ payloadData = { ...data };
1628
+ explicitPatch = payloadData.env_patch;
1629
+ delete payloadData.env_patch;
1630
+ delete payloadData.page;
1631
+ }
1632
+ const capturedPatch = await captureAutoEnvPatch(page, runtimeContext);
1633
+ const envPatch = RuntimeEnv.mergeEnvPatches(runtimeContext.envPatch, capturedPatch, explicitPatch);
1030
1634
  const traffic = await ProxyMeterRuntime.getProxyMeterSnapshot({ finalize: true });
1031
- await Actor2.pushData({
1032
- // 固定为0
1635
+ const payload = {
1033
1636
  code: Code.Success,
1034
1637
  status: Status.Success,
1035
1638
  ...traffic ? { traffic } : {},
1036
1639
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1037
- data
1640
+ data: payloadData
1641
+ };
1642
+ if (envPatch) {
1643
+ payload.env_patch = envPatch;
1644
+ }
1645
+ await Actor2.pushData({
1646
+ ...payload
1038
1647
  });
1039
1648
  logger3.success("pushSuccess", "Data pushed");
1040
1649
  },
1041
1650
  /**
1042
- * 推送失败数据的通用方法(私有方法,仅供runStep内部使用)
1043
- * 自动解析 CrawlerError code 和 context
1651
+ * 推送失败数据的通用方法(私有方法,仅供 runStep 内部使用)。
1652
+ * 即便任务失败,这里也会自动回收 env_patch,确保 cookies/storage 的最新状态不会丢。
1044
1653
  * @param {Error|CrawlerError} error - 错误对象
1045
- * @param {Object} [meta] - 额外的数据(如failedStep, screenshotBase64等)
1654
+ * @param {Object} [meta] - 额外的数据(如 failedStep、screenshotBase64 等)
1655
+ * @param {Object} [options] - 附加选项(如 page)
1046
1656
  * @private
1047
1657
  */
1048
- async pushFailed(error, meta = {}) {
1658
+ async pushFailed(error, meta = {}, options = {}) {
1659
+ const runtimeContext = getRuntimeContext();
1660
+ const page = pickPage(options?.page, meta?.page, lastPage);
1661
+ if (page) {
1662
+ lastPage = page;
1663
+ }
1664
+ const capturedPatch = await captureAutoEnvPatch(page, runtimeContext);
1665
+ const explicitPatch = meta?.env_patch;
1666
+ const envPatch = RuntimeEnv.mergeEnvPatches(runtimeContext.envPatch, capturedPatch, explicitPatch);
1667
+ const safeMeta = sanitizeMeta(meta);
1668
+ delete safeMeta.env_patch;
1049
1669
  const isCrawlerError = CrawlerError.isCrawlerError(error);
1050
1670
  const code = isCrawlerError ? error.code : Code.UnknownError;
1051
1671
  const context = isCrawlerError ? error.context : {};
1052
1672
  const traffic = await ProxyMeterRuntime.getProxyMeterSnapshot({ finalize: true });
1053
- await Actor2.pushData({
1673
+ const payload = {
1054
1674
  // 如果是 CrawlerError,使用其 code,否则使用默认 Failed code
1055
1675
  code,
1056
1676
  status: Status.Failed,
1057
1677
  ...traffic ? { traffic } : {},
1058
1678
  error: serializeError2(error),
1059
- meta,
1679
+ meta: safeMeta,
1060
1680
  context,
1061
1681
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
1682
+ };
1683
+ if (envPatch) {
1684
+ payload.env_patch = envPatch;
1685
+ }
1686
+ await Actor2.pushData({
1687
+ ...payload
1062
1688
  });
1063
1689
  logger3.success("pushFailed", "Error data pushed");
1064
1690
  }
@@ -1149,12 +1775,13 @@ var DEFAULT_LAUNCH_ARGS = [
1149
1775
  // '--disable-blink-features=AutomationControlled', // Crawlee 可能会自动处理,过多干预反而会被识别
1150
1776
  "--no-sandbox",
1151
1777
  "--disable-setuid-sandbox",
1152
- "--window-position=0,0",
1153
- `--lang=${BASE_CONFIG.locale}`
1778
+ "--window-position=0,0"
1154
1779
  ];
1155
- function buildFingerprintOptions(locale) {
1780
+ function buildFingerprintOptions({ locale = BASE_CONFIG.locale, browserMajorVersion = 0 } = {}) {
1781
+ const normalizedBrowserMajorVersion = Number(browserMajorVersion || 0);
1782
+ const browserDescriptor = normalizedBrowserMajorVersion > 0 ? [{ name: "chrome", minVersion: normalizedBrowserMajorVersion, maxVersion: normalizedBrowserMajorVersion }] : [{ name: "chrome", minVersion: 110 }];
1156
1783
  return {
1157
- browsers: [{ name: "chrome", minVersion: 110 }],
1784
+ browsers: browserDescriptor,
1158
1785
  devices: ["desktop"],
1159
1786
  operatingSystems: ["windows", "macos", "linux"],
1160
1787
  locales: [locale]
@@ -1170,20 +1797,21 @@ var AntiCheat = {
1170
1797
  /**
1171
1798
  * 用于 Crawlee fingerprint generator 的统一配置(桌面端)。
1172
1799
  */
1173
- getFingerprintGeneratorOptions() {
1174
- return buildFingerprintOptions(BASE_CONFIG.locale);
1800
+ getFingerprintGeneratorOptions(options = {}) {
1801
+ return buildFingerprintOptions(options);
1175
1802
  },
1176
1803
  /**
1177
1804
  * 获取基础启动参数。
1178
1805
  */
1179
- getLaunchArgs() {
1180
- return [...DEFAULT_LAUNCH_ARGS];
1806
+ getLaunchArgs(options = {}) {
1807
+ const locale = String(options?.locale || BASE_CONFIG.locale).trim() || BASE_CONFIG.locale;
1808
+ return [...DEFAULT_LAUNCH_ARGS, `--lang=${locale}`];
1181
1809
  },
1182
1810
  /**
1183
1811
  * 为 got-scraping 生成与浏览器一致的 TLS 指纹配置(桌面端)。
1184
1812
  */
1185
1813
  getTlsFingerprintOptions(userAgent = "", acceptLanguage = "") {
1186
- return buildFingerprintOptions(BASE_CONFIG.locale);
1814
+ return buildFingerprintOptions();
1187
1815
  },
1188
1816
  /**
1189
1817
  * 规范化请求头
@@ -1674,6 +2302,11 @@ var Humanize = {
1674
2302
  }
1675
2303
  };
1676
2304
 
2305
+ // src/launch.js
2306
+ import { execFileSync } from "node:child_process";
2307
+ import { FingerprintGenerator } from "fingerprint-generator";
2308
+ import { FingerprintInjector } from "fingerprint-injector";
2309
+
1677
2310
  // src/proxy-bypass.js
1678
2311
  import picomatch from "picomatch";
1679
2312
  var normalizeByPassDomains = (domains) => {
@@ -1757,12 +2390,17 @@ var ByPass = {
1757
2390
  // src/launch.js
1758
2391
  var logger7 = createInternalLogger("Launch");
1759
2392
  var REQUEST_HOOK_FLAG = Symbol("playwright-toolkit-request-hook");
2393
+ var injectedContexts = /* @__PURE__ */ new WeakSet();
2394
+ var browserMajorVersionCache = /* @__PURE__ */ new Map();
2395
+ var DEFAULT_BROWSER_PROFILE_SCHEMA_VERSION = 1;
2396
+ var DEFAULT_LOCALE = "zh-CN";
1760
2397
  var DEFAULT_CRAWLER_BASE_OPTIONS = Object.freeze({
1761
2398
  maxConcurrency: 1,
1762
2399
  maxRequestRetries: 0,
1763
2400
  requestHandlerTimeoutSecs: 240,
1764
2401
  navigationTimeoutSecs: 120
1765
2402
  });
2403
+ var fingerprintInjector = new FingerprintInjector();
1766
2404
  var resolveProxyLaunchOptions = (proxyConfiguration = {}) => {
1767
2405
  const config = proxyConfiguration && typeof proxyConfiguration === "object" && !Array.isArray(proxyConfiguration) ? proxyConfiguration : {};
1768
2406
  const proxyUrl = String(config.proxy_url || "").trim();
@@ -1773,6 +2411,146 @@ var resolveProxyLaunchOptions = (proxyConfiguration = {}) => {
1773
2411
  const byPassDomains = ByPass.normalizeByPassDomains(config.by_pass_domains);
1774
2412
  return { byPassDomains, enableProxy, proxyUrl };
1775
2413
  };
2414
+ var parseChromeMajorVersion = (rawValue = "") => {
2415
+ const match = String(rawValue || "").match(/Chrome\/(\d+)/i);
2416
+ return match ? Number(match[1] || 0) : 0;
2417
+ };
2418
+ var resolveLauncherExecutablePath = (launcher) => {
2419
+ if (!launcher || typeof launcher !== "object") return "";
2420
+ if (typeof launcher.executablePath === "function") {
2421
+ try {
2422
+ return String(launcher.executablePath() || "").trim();
2423
+ } catch {
2424
+ return "";
2425
+ }
2426
+ }
2427
+ return "";
2428
+ };
2429
+ var detectBrowserMajorVersion = (launcher) => {
2430
+ const executablePath = resolveLauncherExecutablePath(launcher);
2431
+ if (!executablePath) return 0;
2432
+ if (browserMajorVersionCache.has(executablePath)) {
2433
+ return browserMajorVersionCache.get(executablePath);
2434
+ }
2435
+ let detectedVersion = 0;
2436
+ try {
2437
+ const rawVersion = execFileSync(executablePath, ["--version"], {
2438
+ encoding: "utf8",
2439
+ stdio: ["ignore", "pipe", "ignore"]
2440
+ });
2441
+ detectedVersion = parseChromeMajorVersion(rawVersion);
2442
+ } catch (error) {
2443
+ logger7.warn(`\u8BFB\u53D6\u6D4F\u89C8\u5668\u7248\u672C\u5931\u8D25: ${error?.message || error}`);
2444
+ }
2445
+ browserMajorVersionCache.set(executablePath, detectedVersion);
2446
+ return detectedVersion;
2447
+ };
2448
+ var buildFingerprintGenerator = ({ locale, browserMajorVersion }) => {
2449
+ return new FingerprintGenerator(
2450
+ AntiCheat.getFingerprintGeneratorOptions({
2451
+ locale,
2452
+ browserMajorVersion
2453
+ })
2454
+ );
2455
+ };
2456
+ var buildReplayableBrowserProfile = (runtimeState, launcher) => {
2457
+ if (!runtimeState || !RuntimeEnv.hasLoginState(runtimeState)) {
2458
+ return { runtimeState, browserProfileCore: null };
2459
+ }
2460
+ let nextState = RuntimeEnv.rememberState(runtimeState);
2461
+ let browserProfileCore = RuntimeEnv.getBrowserProfileCore(nextState);
2462
+ const timezoneId = String(browserProfileCore?.timezone_id || "").trim() || AntiCheat.getBaseConfig().timezoneId;
2463
+ const locale = DEFAULT_LOCALE;
2464
+ const currentBrowserMajorVersion = detectBrowserMajorVersion(launcher);
2465
+ const storedBrowserMajorVersion = Number(browserProfileCore?.browser_major_version || 0);
2466
+ const needsRebuild = !browserProfileCore?.fingerprint || Object.keys(browserProfileCore.fingerprint || {}).length === 0 || currentBrowserMajorVersion > 0 && storedBrowserMajorVersion > 0 && storedBrowserMajorVersion !== currentBrowserMajorVersion;
2467
+ if (needsRebuild) {
2468
+ const generator = buildFingerprintGenerator({
2469
+ locale,
2470
+ browserMajorVersion: currentBrowserMajorVersion
2471
+ });
2472
+ const fingerprint = generator.getFingerprint();
2473
+ const fingerprintBrowserMajorVersion = parseChromeMajorVersion(fingerprint?.fingerprint?.navigator?.userAgent || "");
2474
+ browserProfileCore = {
2475
+ fingerprint,
2476
+ timezone_id: timezoneId,
2477
+ locale,
2478
+ browser_major_version: currentBrowserMajorVersion > 0 ? currentBrowserMajorVersion : fingerprintBrowserMajorVersion,
2479
+ profile_key: String(nextState.envId || "").trim(),
2480
+ schema_version: DEFAULT_BROWSER_PROFILE_SCHEMA_VERSION
2481
+ };
2482
+ nextState = RuntimeEnv.setBrowserProfileCore(nextState, browserProfileCore);
2483
+ logger7.info(
2484
+ `\u5DF2\u751F\u6210\u6D4F\u89C8\u5668\u6307\u7EB9\u771F\u6E90 | env=${String(nextState.envId || "-")} | version=${browserProfileCore.browser_major_version || "-"} | timezone=${timezoneId}`
2485
+ );
2486
+ return { runtimeState: nextState, browserProfileCore };
2487
+ }
2488
+ const normalizedBrowserProfileCore = {
2489
+ ...browserProfileCore,
2490
+ timezone_id: timezoneId,
2491
+ locale,
2492
+ profile_key: String(browserProfileCore.profile_key || nextState.envId || "").trim(),
2493
+ schema_version: Number(browserProfileCore.schema_version || 0) > 0 ? Number(browserProfileCore.schema_version || 0) : DEFAULT_BROWSER_PROFILE_SCHEMA_VERSION,
2494
+ browser_major_version: currentBrowserMajorVersion > 0 ? currentBrowserMajorVersion : Number(browserProfileCore.browser_major_version || 0)
2495
+ };
2496
+ const currentCoreRaw = JSON.stringify(browserProfileCore || {});
2497
+ const nextCoreRaw = JSON.stringify(normalizedBrowserProfileCore);
2498
+ if (currentCoreRaw !== nextCoreRaw) {
2499
+ nextState = RuntimeEnv.setBrowserProfileCore(nextState, normalizedBrowserProfileCore);
2500
+ }
2501
+ return {
2502
+ runtimeState: nextState,
2503
+ browserProfileCore: normalizedBrowserProfileCore
2504
+ };
2505
+ };
2506
+ var buildReplayBrowserPoolOptions = (browserProfileCore) => {
2507
+ const fingerprintWithHeaders = browserProfileCore?.fingerprint;
2508
+ const fingerprint = fingerprintWithHeaders?.fingerprint;
2509
+ if (!fingerprintWithHeaders || !fingerprint) {
2510
+ return null;
2511
+ }
2512
+ return {
2513
+ useFingerprints: false,
2514
+ prePageCreateHooks: [
2515
+ (_pageId, _browserController, pageOptions = {}) => {
2516
+ if (!pageOptions || typeof pageOptions !== "object") return;
2517
+ const screen = fingerprint.screen || {};
2518
+ const userAgent = String(fingerprint.navigator?.userAgent || "").trim();
2519
+ if (userAgent) {
2520
+ pageOptions.userAgent = userAgent;
2521
+ }
2522
+ if (Number(screen.width || 0) > 0 && Number(screen.height || 0) > 0) {
2523
+ pageOptions.viewport = {
2524
+ width: Number(screen.width),
2525
+ height: Number(screen.height)
2526
+ };
2527
+ pageOptions.screen = {
2528
+ width: Number(screen.width),
2529
+ height: Number(screen.height)
2530
+ };
2531
+ }
2532
+ if (browserProfileCore.locale) {
2533
+ pageOptions.locale = browserProfileCore.locale;
2534
+ }
2535
+ if (browserProfileCore.timezone_id) {
2536
+ pageOptions.timezoneId = browserProfileCore.timezone_id;
2537
+ }
2538
+ }
2539
+ ],
2540
+ postPageCreateHooks: [
2541
+ async (page) => {
2542
+ const context = page?.context?.();
2543
+ if (!context) return;
2544
+ if (!injectedContexts.has(context)) {
2545
+ await fingerprintInjector.attachFingerprintToPlaywright(context, fingerprintWithHeaders);
2546
+ injectedContexts.add(context);
2547
+ }
2548
+ await page.emulateMedia({ colorScheme: "dark" }).catch(() => {
2549
+ });
2550
+ }
2551
+ ]
2552
+ };
2553
+ };
1776
2554
  var Launch = {
1777
2555
  getPlaywrightCrawlerOptions(options = {}) {
1778
2556
  const normalizedOptions = Array.isArray(options) ? { customArgs: options } : options || {};
@@ -1785,7 +2563,8 @@ var Launch = {
1785
2563
  isRunningOnApify = false,
1786
2564
  launcher = null,
1787
2565
  preNavigationHooks = [],
1788
- postNavigationHooks = []
2566
+ postNavigationHooks = [],
2567
+ runtimeState = null
1789
2568
  } = normalizedOptions;
1790
2569
  const { byPassDomains, enableProxy, proxyUrl } = resolveProxyLaunchOptions(proxyConfiguration);
1791
2570
  const byPassRules = ByPass.buildByPassDomainRules(byPassDomains);
@@ -1794,9 +2573,12 @@ var Launch = {
1794
2573
  if (launchProxy && byPassDomains.length > 0) {
1795
2574
  launchProxy.bypass = byPassDomains.join(",");
1796
2575
  }
2576
+ const replayContext = buildReplayableBrowserProfile(runtimeState, launcher);
2577
+ const replayBrowserPoolOptions = buildReplayBrowserPoolOptions(replayContext.browserProfileCore);
2578
+ const launchLocale = String(replayContext.browserProfileCore?.locale || DEFAULT_LOCALE).trim() || DEFAULT_LOCALE;
1797
2579
  const launchOptions = {
1798
2580
  args: [
1799
- ...AntiCheat.getLaunchArgs(),
2581
+ ...AntiCheat.getLaunchArgs({ locale: launchLocale }),
1800
2582
  ...customArgs
1801
2583
  ],
1802
2584
  ignoreDefaultArgs: ["--enable-automation"]
@@ -1808,7 +2590,7 @@ var Launch = {
1808
2590
  if (enableByPassLogger && launchProxy) {
1809
2591
  let upstreamLabel = "";
1810
2592
  try {
1811
- const parsedProxyUrl = new URL(proxyUrl);
2593
+ const parsedProxyUrl = new URL(proxyUrl.includes("://") ? proxyUrl : `http://${proxyUrl}`);
1812
2594
  upstreamLabel = `${parsedProxyUrl.protocol}//${parsedProxyUrl.host}`;
1813
2595
  } catch {
1814
2596
  }
@@ -1817,14 +2599,10 @@ var Launch = {
1817
2599
  );
1818
2600
  logger7.info(`[\u6D41\u91CF\u89C2\u6D4B] \u9010\u8BF7\u6C42\u8C03\u8BD5=${Boolean(debugMode) ? "\u5F00\u542F" : "\u5173\u95ED"}\uFF08\u6C47\u603B\u59CB\u7EC8\u5F00\u542F\uFF09`);
1819
2601
  } else if (enableByPassLogger && enableProxy && !launchProxy) {
1820
- logger7.info(
1821
- `[\u4EE3\u7406\u672A\u542F\u7528] enable_proxy=true \u4F46 proxy_url \u4E3A\u7A7A`
1822
- );
2602
+ logger7.info("[\u4EE3\u7406\u672A\u542F\u7528] enable_proxy=true \u4F46 proxy_url \u4E3A\u7A7A");
1823
2603
  logger7.info(`[\u6D41\u91CF\u89C2\u6D4B] \u9010\u8BF7\u6C42\u8C03\u8BD5=${Boolean(debugMode) ? "\u5F00\u542F" : "\u5173\u95ED"}\uFF08\u6C47\u603B\u59CB\u7EC8\u5F00\u542F\uFF09`);
1824
2604
  } else if (enableByPassLogger && !enableProxy && proxyUrl) {
1825
- logger7.info(
1826
- `[\u4EE3\u7406\u672A\u542F\u7528] enable_proxy=false \u4E14 proxy_url \u5DF2\u914D\u7F6E`
1827
- );
2605
+ logger7.info("[\u4EE3\u7406\u672A\u542F\u7528] enable_proxy=false \u4E14 proxy_url \u5DF2\u914D\u7F6E");
1828
2606
  logger7.info(`[\u6D41\u91CF\u89C2\u6D4B] \u9010\u8BF7\u6C42\u8C03\u8BD5=${Boolean(debugMode) ? "\u5F00\u542F" : "\u5173\u95ED"}\uFF08\u6C47\u603B\u59CB\u7EC8\u5F00\u542F\uFF09`);
1829
2607
  } else if (enableByPassLogger) {
1830
2608
  logger7.info(`[\u6D41\u91CF\u89C2\u6D4B] \u9010\u8BF7\u6C42\u8C03\u8BD5=${Boolean(debugMode) ? "\u5F00\u542F" : "\u5173\u95ED"}\uFF08\u6C47\u603B\u59CB\u7EC8\u5F00\u542F\uFF09`);
@@ -1864,7 +2642,8 @@ var Launch = {
1864
2642
  const crawlerBaseOptions = {
1865
2643
  ...DEFAULT_CRAWLER_BASE_OPTIONS,
1866
2644
  headless: !runInHeadfulMode || isRunningOnApify,
1867
- browserPoolOptions: {
2645
+ // 有 core.fingerprint 时走固定回放;否则退回 Crawlee 默认指纹模式。
2646
+ browserPoolOptions: replayBrowserPoolOptions || {
1868
2647
  useFingerprints: true,
1869
2648
  fingerprintOptions: {
1870
2649
  fingerprintGeneratorOptions: AntiCheat.getFingerprintGeneratorOptions()
@@ -2067,241 +2846,9 @@ var Captcha = {
2067
2846
  useCaptchaMonitor
2068
2847
  };
2069
2848
 
2070
- // src/sse.js
2071
- import https from "https";
2072
- import { URL as URL2 } from "url";
2073
- var logger10 = createInternalLogger("Sse");
2074
- var Sse = {
2075
- /**
2076
- * 解析 SSE 流文本
2077
- * 支持 `data: {...}` 和 `data:{...}` 两种格式
2078
- * @param {string} sseStreamText
2079
- * @returns {Array<Object>} events
2080
- */
2081
- parseSseStream(sseStreamText) {
2082
- const events = [];
2083
- const lines = sseStreamText.split("\n");
2084
- for (const line of lines) {
2085
- if (line.startsWith("data:")) {
2086
- try {
2087
- const jsonContent = line.substring(5).trim();
2088
- if (jsonContent) {
2089
- events.push(JSON.parse(jsonContent));
2090
- }
2091
- } catch (e) {
2092
- logger10.debug("parseSseStream", `JSON \u89E3\u6790\u5931\u8D25: ${e.message}, line: ${line.substring(0, 100)}...`);
2093
- }
2094
- }
2095
- }
2096
- logger10.success("parseSseStream", `\u89E3\u6790\u5B8C\u6210, events \u6570\u91CF: ${events.length}`);
2097
- return events;
2098
- },
2099
- /**
2100
- * 拦截网络请求并使用 Node.js 原生 https 模块转发,以解决流式数据捕获问题。
2101
- * @param {import('playwright').Page} page
2102
- * @param {string|RegExp} urlPattern - 拦截的 URL 模式
2103
- * @param {object} options
2104
- * @param {function(string, function, string): void} [options.onData] - (textChunk, resolve, accumulatedText) => void
2105
- * @param {function(string, function): void} [options.onEnd] - (fullText, resolve) => void
2106
- * @param {function(Error, function): void} [options.onTimeout] - (error, reject) => void
2107
- * @param {number} [options.initialTimeout=90000] - 初始数据接收超时 (ms),默认 90s
2108
- * @param {number} [options.overallTimeout=180000] - 整体请求超时时间 (ms),默认 180s
2109
- * @param {boolean} [options.autoUnroute=true] - resolve/reject 后是否自动取消拦截
2110
- * @param {boolean} [options.firstMatchOnly=true] - 只拦截第一个命中的请求,后续请求全部放行
2111
- * @returns {Promise<any>} - 返回 Promise,当流满足条件时 resolve
2112
- */
2113
- async intercept(page, urlPattern, options = {}) {
2114
- const {
2115
- onData,
2116
- onEnd,
2117
- onTimeout,
2118
- initialTimeout = 9e4,
2119
- overallTimeout = 18e4,
2120
- autoUnroute = true,
2121
- firstMatchOnly = true
2122
- } = options;
2123
- let initialTimer = null;
2124
- let overallTimer = null;
2125
- let hasReceivedInitialData = false;
2126
- const clearAllTimers = () => {
2127
- if (initialTimer) clearTimeout(initialTimer);
2128
- if (overallTimer) clearTimeout(overallTimer);
2129
- initialTimer = null;
2130
- overallTimer = null;
2131
- };
2132
- let safeResolve = () => {
2133
- };
2134
- let safeReject = () => {
2135
- };
2136
- let safeUnroute = () => {
2137
- };
2138
- const workPromise = new Promise((resolve, reject) => {
2139
- let finished = false;
2140
- let unrouteRequested = false;
2141
- let hasMatchedOnce = false;
2142
- safeUnroute = () => {
2143
- if (!autoUnroute) return;
2144
- if (unrouteRequested) return;
2145
- unrouteRequested = true;
2146
- logger10.info("[MITM] autoUnroute: \u53D6\u6D88\u540E\u7EED\u62E6\u622A");
2147
- page.unroute(urlPattern, routeHandler).catch(() => {
2148
- });
2149
- };
2150
- safeResolve = (value) => {
2151
- if (finished) return;
2152
- finished = true;
2153
- clearAllTimers();
2154
- safeUnroute();
2155
- resolve(value);
2156
- };
2157
- safeReject = (error) => {
2158
- if (finished) return;
2159
- finished = true;
2160
- clearAllTimers();
2161
- safeUnroute();
2162
- reject(error);
2163
- };
2164
- const routeHandler = async (route) => {
2165
- if (firstMatchOnly && hasMatchedOnce) {
2166
- logger10.info(`[MITM] firstMatchOnly: \u653E\u884C\u540E\u7EED\u8BF7\u6C42: ${route.request().url()}`);
2167
- route.continue().catch(() => {
2168
- });
2169
- return;
2170
- }
2171
- if (firstMatchOnly && !hasMatchedOnce) {
2172
- hasMatchedOnce = true;
2173
- logger10.info("[MITM] firstMatchOnly: \u547D\u4E2D\u9996\u4E2A\u8BF7\u6C42\uFF0C\u53D6\u6D88\u540E\u7EED\u62E6\u622A");
2174
- page.unroute(urlPattern, routeHandler).catch(() => {
2175
- });
2176
- }
2177
- const request = route.request();
2178
- logger10.info(`[MITM] \u5DF2\u62E6\u622A\u8BF7\u6C42: ${request.url()}`);
2179
- try {
2180
- const headers = await request.allHeaders();
2181
- const postData = request.postData();
2182
- const urlObj = new URL2(request.url());
2183
- delete headers["accept-encoding"];
2184
- delete headers["content-length"];
2185
- const reqOptions = {
2186
- hostname: urlObj.hostname,
2187
- port: 443,
2188
- path: urlObj.pathname + urlObj.search,
2189
- method: request.method(),
2190
- headers,
2191
- timeout: overallTimeout
2192
- };
2193
- const req = https.request(reqOptions, (res) => {
2194
- const chunks = [];
2195
- let accumulatedText = "";
2196
- res.on("data", (chunk) => {
2197
- if (!hasReceivedInitialData) {
2198
- hasReceivedInitialData = true;
2199
- if (initialTimer) {
2200
- clearTimeout(initialTimer);
2201
- initialTimer = null;
2202
- }
2203
- logger10.debug("[Intercept] \u5DF2\u63A5\u6536\u521D\u59CB\u6570\u636E");
2204
- }
2205
- chunks.push(chunk);
2206
- const textChunk = chunk.toString("utf-8");
2207
- accumulatedText += textChunk;
2208
- if (onData) {
2209
- try {
2210
- onData(textChunk, safeResolve, accumulatedText);
2211
- } catch (e) {
2212
- logger10.fail(`onData \u9519\u8BEF`, e);
2213
- }
2214
- }
2215
- });
2216
- res.on("end", () => {
2217
- logger10.info("[MITM] \u4E0A\u6E38\u54CD\u5E94\u7ED3\u675F");
2218
- clearAllTimers();
2219
- if (onEnd) {
2220
- try {
2221
- onEnd(accumulatedText, safeResolve);
2222
- } catch (e) {
2223
- logger10.fail(`onEnd \u9519\u8BEF`, e);
2224
- }
2225
- } else if (!onData) {
2226
- safeResolve(accumulatedText);
2227
- }
2228
- route.fulfill({
2229
- status: res.statusCode,
2230
- headers: res.headers,
2231
- body: Buffer.concat(chunks)
2232
- }).catch(() => {
2233
- });
2234
- });
2235
- });
2236
- req.on("error", (e) => {
2237
- clearAllTimers();
2238
- route.abort().catch(() => {
2239
- });
2240
- safeReject(e);
2241
- });
2242
- if (postData) req.write(postData);
2243
- req.end();
2244
- } catch (e) {
2245
- clearAllTimers();
2246
- route.continue().catch(() => {
2247
- });
2248
- safeReject(e);
2249
- }
2250
- };
2251
- page.route(urlPattern, routeHandler).catch(safeReject);
2252
- });
2253
- const timeoutPromise = new Promise((_, reject) => {
2254
- initialTimer = setTimeout(() => {
2255
- if (!hasReceivedInitialData) {
2256
- const error = new CrawlerError({
2257
- message: `\u521D\u59CB\u6570\u636E\u63A5\u6536\u8D85\u65F6 (${initialTimeout}ms)`,
2258
- code: Code.InitialTimeout,
2259
- context: { timeout: initialTimeout }
2260
- });
2261
- clearAllTimers();
2262
- if (onTimeout) {
2263
- try {
2264
- onTimeout(error, (err) => safeReject(err));
2265
- } catch (e) {
2266
- safeReject(e);
2267
- }
2268
- } else {
2269
- safeReject(error);
2270
- }
2271
- }
2272
- }, initialTimeout);
2273
- overallTimer = setTimeout(() => {
2274
- const error = new CrawlerError({
2275
- message: `\u6574\u4F53\u8BF7\u6C42\u8D85\u65F6 (${overallTimeout}ms)`,
2276
- code: Code.OverallTimeout,
2277
- context: { timeout: overallTimeout }
2278
- });
2279
- clearAllTimers();
2280
- if (onTimeout) {
2281
- try {
2282
- onTimeout(error, (err) => safeReject(err));
2283
- } catch (e) {
2284
- safeReject(e);
2285
- }
2286
- } else {
2287
- safeReject(error);
2288
- }
2289
- }, overallTimeout);
2290
- });
2291
- workPromise.catch(() => {
2292
- });
2293
- timeoutPromise.catch(() => {
2294
- });
2295
- const racePromise = Promise.race([workPromise, timeoutPromise]);
2296
- racePromise.catch(() => {
2297
- });
2298
- return racePromise;
2299
- }
2300
- };
2301
-
2302
2849
  // src/mutation.js
2303
2850
  import { v4 as uuidv42 } from "uuid";
2304
- var logger11 = createInternalLogger("Mutation");
2851
+ var logger10 = createInternalLogger("Mutation");
2305
2852
  var MUTATION_MONITOR_MODE = Object.freeze({
2306
2853
  Added: "added",
2307
2854
  Changed: "changed",
@@ -2334,14 +2881,14 @@ var Mutation = {
2334
2881
  const stableTime = options.stableTime ?? 5 * 1e3;
2335
2882
  const timeout = options.timeout ?? 120 * 1e3;
2336
2883
  const onMutation = options.onMutation;
2337
- logger11.start("waitForStable", `\u76D1\u63A7 ${selectorList.length} \u4E2A\u9009\u62E9\u5668, \u7A33\u5B9A\u65F6\u95F4=${stableTime}ms`);
2884
+ logger10.start("waitForStable", `\u76D1\u63A7 ${selectorList.length} \u4E2A\u9009\u62E9\u5668, \u7A33\u5B9A\u65F6\u95F4=${stableTime}ms`);
2338
2885
  if (initialTimeout > 0) {
2339
2886
  const selectorQuery = selectorList.join(",");
2340
2887
  try {
2341
2888
  await page.waitForSelector(selectorQuery, { timeout: initialTimeout });
2342
- logger11.info(`waitForStable \u5DF2\u68C0\u6D4B\u5230\u5143\u7D20: ${selectorQuery}`);
2889
+ logger10.info(`waitForStable \u5DF2\u68C0\u6D4B\u5230\u5143\u7D20: ${selectorQuery}`);
2343
2890
  } catch (e) {
2344
- logger11.warning(`waitForStable \u521D\u59CB\u7B49\u5F85\u8D85\u65F6 (${initialTimeout}ms): ${selectorQuery}`);
2891
+ logger10.warning(`waitForStable \u521D\u59CB\u7B49\u5F85\u8D85\u65F6 (${initialTimeout}ms): ${selectorQuery}`);
2345
2892
  throw e;
2346
2893
  }
2347
2894
  }
@@ -2357,7 +2904,7 @@ var Mutation = {
2357
2904
  return "__CONTINUE__";
2358
2905
  }
2359
2906
  });
2360
- logger11.info("waitForStable \u5DF2\u542F\u7528 onMutation \u56DE\u8C03");
2907
+ logger10.info("waitForStable \u5DF2\u542F\u7528 onMutation \u56DE\u8C03");
2361
2908
  } catch (e) {
2362
2909
  }
2363
2910
  }
@@ -2472,9 +3019,9 @@ var Mutation = {
2472
3019
  { selectorList, stableTime, timeout, callbackName, hasCallback: !!onMutation }
2473
3020
  );
2474
3021
  if (result.mutationCount === 0 && result.stableTime === 0) {
2475
- logger11.warning("waitForStable \u672A\u627E\u5230\u53EF\u76D1\u63A7\u7684\u5143\u7D20");
3022
+ logger10.warning("waitForStable \u672A\u627E\u5230\u53EF\u76D1\u63A7\u7684\u5143\u7D20");
2476
3023
  }
2477
- logger11.success("waitForStable", `DOM \u7A33\u5B9A, \u603B\u5171 ${result.mutationCount} \u6B21\u53D8\u5316${result.wasPaused ? ", \u66FE\u6682\u505C\u8BA1\u65F6" : ""}`);
3024
+ logger10.success("waitForStable", `DOM \u7A33\u5B9A, \u603B\u5171 ${result.mutationCount} \u6B21\u53D8\u5316${result.wasPaused ? ", \u66FE\u6682\u505C\u8BA1\u65F6" : ""}`);
2478
3025
  return result;
2479
3026
  },
2480
3027
  /**
@@ -2592,22 +3139,22 @@ var Mutation = {
2592
3139
  return "__CONTINUE__";
2593
3140
  }
2594
3141
  };
2595
- logger11.start(
3142
+ logger10.start(
2596
3143
  "waitForStableAcrossRoots",
2597
3144
  `\u76D1\u63A7 ${selectorList.length} \u4E2A\u9009\u62E9\u5668(\u8DE8 root), \u7A33\u5B9A\u65F6\u95F4=${waitForStableTime}ms`
2598
3145
  );
2599
3146
  if (initialTimeout > 0) {
2600
3147
  try {
2601
3148
  await page.waitForSelector(selectorQuery, { timeout: initialTimeout });
2602
- logger11.info(`waitForStableAcrossRoots \u5DF2\u68C0\u6D4B\u5230\u5143\u7D20: ${selectorQuery}`);
3149
+ logger10.info(`waitForStableAcrossRoots \u5DF2\u68C0\u6D4B\u5230\u5143\u7D20: ${selectorQuery}`);
2603
3150
  } catch (e) {
2604
- logger11.warning(`waitForStableAcrossRoots \u521D\u59CB\u7B49\u5F85\u8D85\u65F6 (${initialTimeout}ms): ${selectorQuery}`);
3151
+ logger10.warning(`waitForStableAcrossRoots \u521D\u59CB\u7B49\u5F85\u8D85\u65F6 (${initialTimeout}ms): ${selectorQuery}`);
2605
3152
  throw e;
2606
3153
  }
2607
3154
  }
2608
3155
  let state = await buildState();
2609
3156
  if (!state?.hasMatched) {
2610
- logger11.warning("waitForStableAcrossRoots \u672A\u627E\u5230\u53EF\u76D1\u63A7\u7684\u5143\u7D20");
3157
+ logger10.warning("waitForStableAcrossRoots \u672A\u627E\u5230\u53EF\u76D1\u63A7\u7684\u5143\u7D20");
2611
3158
  return { mutationCount: 0, stableTime: 0, wasPaused: false };
2612
3159
  }
2613
3160
  let mutationCount = 0;
@@ -2644,7 +3191,7 @@ var Mutation = {
2644
3191
  if (lastState.snapshotKey !== lastSnapshotKey) {
2645
3192
  lastSnapshotKey = lastState.snapshotKey;
2646
3193
  mutationCount += 1;
2647
- logger11.info(
3194
+ logger10.info(
2648
3195
  `waitForStableAcrossRoots \u53D8\u5316#${mutationCount}, len=${lastState.snapshotLength}, path=${lastState.primaryPath || "unknown"}, preview="${truncate(lastState.text, 120)}"`
2649
3196
  );
2650
3197
  const signal = await invokeMutationCallback({
@@ -2657,7 +3204,7 @@ var Mutation = {
2657
3204
  continue;
2658
3205
  }
2659
3206
  if (!isPaused && stableSince > 0 && Date.now() - stableSince >= waitForStableTime) {
2660
- logger11.success("waitForStableAcrossRoots", `DOM \u7A33\u5B9A, \u603B\u5171 ${mutationCount} \u6B21\u53D8\u5316${wasPaused ? ", \u66FE\u6682\u505C\u8BA1\u65F6" : ""}`);
3207
+ logger10.success("waitForStableAcrossRoots", `DOM \u7A33\u5B9A, \u603B\u5171 ${mutationCount} \u6B21\u53D8\u5316${wasPaused ? ", \u66FE\u6682\u505C\u8BA1\u65F6" : ""}`);
2661
3208
  return {
2662
3209
  mutationCount,
2663
3210
  stableTime: waitForStableTime,
@@ -2684,7 +3231,7 @@ var Mutation = {
2684
3231
  const onMutation = options.onMutation;
2685
3232
  const rawMode = String(options.mode || MUTATION_MONITOR_MODE.Added).toLowerCase();
2686
3233
  const mode = [MUTATION_MONITOR_MODE.Added, MUTATION_MONITOR_MODE.Changed, MUTATION_MONITOR_MODE.All].includes(rawMode) ? rawMode : MUTATION_MONITOR_MODE.Added;
2687
- logger11.start("useMonitor", `\u76D1\u63A7 ${selectorList.length} \u4E2A\u9009\u62E9\u5668, mode=${mode}`);
3234
+ logger10.start("useMonitor", `\u76D1\u63A7 ${selectorList.length} \u4E2A\u9009\u62E9\u5668, mode=${mode}`);
2688
3235
  const monitorKey = generateKey("pk_mon");
2689
3236
  const callbackName = generateKey("pk_mon_cb");
2690
3237
  const cleanerName = generateKey("pk_mon_clean");
@@ -2827,7 +3374,7 @@ var Mutation = {
2827
3374
  return total;
2828
3375
  };
2829
3376
  }, { selectorList, monitorKey, callbackName, cleanerName, hasCallback: !!onMutation, mode });
2830
- logger11.success("useMonitor", "\u76D1\u63A7\u5668\u5DF2\u542F\u52A8");
3377
+ logger10.success("useMonitor", "\u76D1\u63A7\u5668\u5DF2\u542F\u52A8");
2831
3378
  return {
2832
3379
  stop: async () => {
2833
3380
  let totalMutations = 0;
@@ -2840,7 +3387,7 @@ var Mutation = {
2840
3387
  }, cleanerName);
2841
3388
  } catch (e) {
2842
3389
  }
2843
- logger11.success("useMonitor.stop", `\u76D1\u63A7\u5DF2\u505C\u6B62, \u5171 ${totalMutations} \u6B21\u53D8\u5316`);
3390
+ logger10.success("useMonitor.stop", `\u76D1\u63A7\u5DF2\u505C\u6B62, \u5171 ${totalMutations} \u6B21\u53D8\u5316`);
2844
3391
  return { totalMutations };
2845
3392
  }
2846
3393
  };
@@ -2851,28 +3398,28 @@ var Mutation = {
2851
3398
  var Display = {
2852
3399
  parseTokenDisplayName(value) {
2853
3400
  if (!value) {
2854
- return { owner: "", accountType: "", note: "" };
3401
+ return { owner: "", envType: "", note: "" };
2855
3402
  }
2856
3403
  const parts = String(value).split(":");
2857
3404
  const owner = (parts[0] || "").trim();
2858
- const accountType = (parts[1] || "").trim();
3405
+ const envType = (parts[1] || "").trim();
2859
3406
  const note = parts.length > 2 ? parts.slice(2).join(":").trim() : "";
2860
- return { owner, accountType, note };
3407
+ return { owner, envType, note };
2861
3408
  },
2862
- parseAccountDisplayName(value) {
3409
+ parseEnvDisplayName(value) {
2863
3410
  if (!value) {
2864
- return { account: "", owner: "", accountType: "", note: "" };
3411
+ return { env: "", owner: "", envType: "", note: "" };
2865
3412
  }
2866
3413
  const parts = String(value).split(":");
2867
- const account = (parts[0] || "").trim();
3414
+ const env = (parts[0] || "").trim();
2868
3415
  const owner = (parts[1] || "").trim();
2869
- const accountType = (parts[2] || "").trim();
3416
+ const envType = (parts[2] || "").trim();
2870
3417
  const note = parts.length > 3 ? parts.slice(3).join(":").trim() : "";
2871
- return { account, owner, accountType, note };
3418
+ return { env, owner, envType, note };
2872
3419
  },
2873
- buildTokenDisplayName({ owner, accountType, note } = {}) {
3420
+ buildTokenDisplayName({ owner, envType, note } = {}) {
2874
3421
  const trimmedOwner = owner?.trim() || "";
2875
- const trimmedType = accountType?.trim() || "";
3422
+ const trimmedType = envType?.trim() || "";
2876
3423
  const trimmedNote = note?.trim() || "";
2877
3424
  const parts = [trimmedOwner, trimmedType];
2878
3425
  if (trimmedNote) {
@@ -2895,12 +3442,12 @@ var Display = {
2895
3442
  const secondary = hasName && short ? short : "";
2896
3443
  return { primary, secondary, fullId: cleanId, shortId: short };
2897
3444
  },
2898
- resolveAccountIdentity(displayName, accountId) {
2899
- const parts = this.parseAccountDisplayName(displayName);
2900
- const cleanId = String(accountId || "").trim();
3445
+ resolveEnvIdentity(displayName, envId) {
3446
+ const parts = this.parseEnvDisplayName(displayName);
3447
+ const cleanId = String(envId || "").trim();
2901
3448
  const short = this.shortId(cleanId, 8);
2902
- const hasName = parts.account !== "" && parts.account !== cleanId;
2903
- const primary = hasName ? parts.account : short || "-";
3449
+ const hasName = parts.env !== "" && parts.env !== cleanId;
3450
+ const primary = hasName ? parts.env : short || "-";
2904
3451
  const secondary = hasName && short ? short : "";
2905
3452
  return { primary, secondary, parts, fullId: cleanId, shortId: short };
2906
3453
  }
@@ -3709,7 +4256,7 @@ var createTemplateLogger = (baseLogger = createBaseLogger()) => {
3709
4256
  };
3710
4257
  var getDefaultBaseLogger = () => createBaseLogger("");
3711
4258
  var Logger = {
3712
- setLogger: (logger13) => setDefaultLogger(logger13),
4259
+ setLogger: (logger12) => setDefaultLogger(logger12),
3713
4260
  info: (message) => getDefaultBaseLogger().info(message),
3714
4261
  success: (message) => getDefaultBaseLogger().success(message),
3715
4262
  warning: (message) => getDefaultBaseLogger().warning(message),
@@ -3717,15 +4264,15 @@ var Logger = {
3717
4264
  error: (message) => getDefaultBaseLogger().error(message),
3718
4265
  debug: (message) => getDefaultBaseLogger().debug(message),
3719
4266
  start: (message) => getDefaultBaseLogger().start(message),
3720
- useTemplate: (logger13) => {
3721
- if (logger13) return createTemplateLogger(createBaseLogger("", logger13));
4267
+ useTemplate: (logger12) => {
4268
+ if (logger12) return createTemplateLogger(createBaseLogger("", logger12));
3722
4269
  return createTemplateLogger();
3723
4270
  }
3724
4271
  };
3725
4272
 
3726
4273
  // src/share.js
3727
4274
  import delay2 from "delay";
3728
- var logger12 = createInternalLogger("Share");
4275
+ var logger11 = createInternalLogger("Share");
3729
4276
  var DEFAULT_TIMEOUT_MS2 = 50 * 1e3;
3730
4277
  var DEFAULT_PAYLOAD_SNAPSHOT_MAX_LEN = 500;
3731
4278
  var DEFAULT_POLL_INTERVAL_MS = 120;
@@ -3845,7 +4392,7 @@ var createDomShareMonitor = async (page, options = {}) => {
3845
4392
  const onMatch = typeof options.onMatch === "function" ? options.onMatch : null;
3846
4393
  const onTelemetry = typeof options.onTelemetry === "function" ? options.onTelemetry : null;
3847
4394
  let matched = false;
3848
- logger12.info(`DOM \u76D1\u542C\u51C6\u5907\u6302\u8F7D: selectors=${toJsonInline(selectors, 120)}, mode=${mode}`);
4395
+ logger11.info(`DOM \u76D1\u542C\u51C6\u5907\u6302\u8F7D: selectors=${toJsonInline(selectors, 120)}, mode=${mode}`);
3849
4396
  const monitor = await Mutation.useMonitor(page, selectors, {
3850
4397
  mode,
3851
4398
  onMutation: (context = {}) => {
@@ -3863,12 +4410,12 @@ ${text}`;
3863
4410
  });
3864
4411
  }
3865
4412
  if (mutationCount <= 5 || mutationCount % 50 === 0) {
3866
- logger12.info(`DOM \u53D8\u5316\u5DF2\u6355\u83B7: mutationCount=${mutationCount}, mutationNodes=${mutationNodes.length}`);
4413
+ logger11.info(`DOM \u53D8\u5316\u5DF2\u6355\u83B7: mutationCount=${mutationCount}, mutationNodes=${mutationNodes.length}`);
3867
4414
  }
3868
4415
  const [candidate] = Utils.parseLinks(rawDom, { prefix }) || [];
3869
4416
  if (!candidate) return;
3870
4417
  matched = true;
3871
- logger12.success("captureLink.domHit", `DOM \u547D\u4E2D\u5206\u4EAB\u94FE\u63A5: mutationCount=${mutationCount}, link=${candidate}`);
4418
+ logger11.success("captureLink.domHit", `DOM \u547D\u4E2D\u5206\u4EAB\u94FE\u63A5: mutationCount=${mutationCount}, link=${candidate}`);
3872
4419
  if (onMatch) {
3873
4420
  onMatch({
3874
4421
  link: candidate,
@@ -3884,7 +4431,7 @@ ${text}`;
3884
4431
  return {
3885
4432
  stop: async () => {
3886
4433
  const result = await monitor.stop();
3887
- logger12.info(`DOM \u76D1\u542C\u5DF2\u505C\u6B62: totalMutations=${result?.totalMutations || 0}`);
4434
+ logger11.info(`DOM \u76D1\u542C\u5DF2\u505C\u6B62: totalMutations=${result?.totalMutations || 0}`);
3888
4435
  return result;
3889
4436
  }
3890
4437
  };
@@ -3924,8 +4471,8 @@ var Share = {
3924
4471
  if (share.mode === "response" && apiMatchers.length === 0) {
3925
4472
  throw new Error("Share.captureLink requires share.xurl[0] api matcher when mode=response");
3926
4473
  }
3927
- logger12.start("captureLink", `mode=${share.mode}, timeoutMs=${timeoutMs}, prefix=${share.prefix}`);
3928
- logger12.info(`captureLink \u914D\u7F6E: xurl=${toJsonInline(share.xurl)}, domMode=${domMode}, domSelectors=${toJsonInline(domSelectors, 120)}`);
4474
+ logger11.start("captureLink", `mode=${share.mode}, timeoutMs=${timeoutMs}, prefix=${share.prefix}`);
4475
+ logger11.info(`captureLink \u914D\u7F6E: xurl=${toJsonInline(share.xurl)}, domMode=${domMode}, domSelectors=${toJsonInline(domSelectors, 120)}`);
3929
4476
  const stats = {
3930
4477
  actionTimedOut: false,
3931
4478
  domMutationCount: 0,
@@ -3950,7 +4497,7 @@ var Share = {
3950
4497
  link: validated,
3951
4498
  payloadText: String(payloadText || "")
3952
4499
  };
3953
- logger12.info(`\u5019\u9009\u94FE\u63A5\u5DF2\u786E\u8BA4: source=${source}, link=${validated}`);
4500
+ logger11.info(`\u5019\u9009\u94FE\u63A5\u5DF2\u786E\u8BA4: source=${source}, link=${validated}`);
3954
4501
  return true;
3955
4502
  };
3956
4503
  const resolveResponseCandidate = (responseText) => {
@@ -3985,7 +4532,7 @@ var Share = {
3985
4532
  try {
3986
4533
  await monitor.stop();
3987
4534
  } catch (error) {
3988
- logger12.warning(`\u505C\u6B62 DOM \u76D1\u542C\u5931\u8D25: ${error instanceof Error ? error.message : String(error)}`);
4535
+ logger11.warning(`\u505C\u6B62 DOM \u76D1\u542C\u5931\u8D25: ${error instanceof Error ? error.message : String(error)}`);
3989
4536
  }
3990
4537
  };
3991
4538
  const onResponse = async (response) => {
@@ -3998,29 +4545,29 @@ var Share = {
3998
4545
  stats.responseSampleUrls.push(url);
3999
4546
  }
4000
4547
  if (stats.responseObserved <= 5) {
4001
- logger12.info(`\u63A5\u53E3\u54CD\u5E94\u91C7\u6837(${stats.responseObserved}): ${url}`);
4548
+ logger11.info(`\u63A5\u53E3\u54CD\u5E94\u91C7\u6837(${stats.responseObserved}): ${url}`);
4002
4549
  }
4003
4550
  if (!apiMatchers.some((matcher) => url.includes(matcher))) return;
4004
4551
  stats.responseMatched += 1;
4005
4552
  stats.lastMatchedUrl = url;
4006
- logger12.info(`\u63A5\u53E3\u547D\u4E2D\u5339\u914D(${stats.responseMatched}): ${url}`);
4553
+ logger11.info(`\u63A5\u53E3\u547D\u4E2D\u5339\u914D(${stats.responseMatched}): ${url}`);
4007
4554
  const text = await response.text();
4008
4555
  const hit = resolveResponseCandidate(text);
4009
4556
  if (!hit?.link) {
4010
4557
  if (stats.responseMatched <= 3) {
4011
- logger12.info(`\u63A5\u53E3\u89E3\u6790\u5B8C\u6210\u4F46\u672A\u63D0\u53D6\u5230\u5206\u4EAB\u94FE\u63A5: payloadSize=${text.length}`);
4558
+ logger11.info(`\u63A5\u53E3\u89E3\u6790\u5B8C\u6210\u4F46\u672A\u63D0\u53D6\u5230\u5206\u4EAB\u94FE\u63A5: payloadSize=${text.length}`);
4012
4559
  }
4013
4560
  return;
4014
4561
  }
4015
4562
  stats.responseResolved += 1;
4016
- logger12.success("captureLink.responseHit", `\u63A5\u53E3\u547D\u4E2D\u5206\u4EAB\u94FE\u63A5: url=${url}, link=${hit.link}`);
4563
+ logger11.success("captureLink.responseHit", `\u63A5\u53E3\u547D\u4E2D\u5206\u4EAB\u94FE\u63A5: url=${url}, link=${hit.link}`);
4017
4564
  setCandidate("response", hit.link, hit.payloadText);
4018
4565
  } catch (error) {
4019
- logger12.warning(`\u63A5\u53E3\u54CD\u5E94\u5904\u7406\u5F02\u5E38: ${error instanceof Error ? error.message : String(error)}`);
4566
+ logger11.warning(`\u63A5\u53E3\u54CD\u5E94\u5904\u7406\u5F02\u5E38: ${error instanceof Error ? error.message : String(error)}`);
4020
4567
  }
4021
4568
  };
4022
4569
  if (share.mode === "dom") {
4023
- logger12.info("\u5F53\u524D\u4E3A DOM \u6A21\u5F0F\uFF0C\u4EC5\u542F\u7528 DOM \u76D1\u542C");
4570
+ logger11.info("\u5F53\u524D\u4E3A DOM \u6A21\u5F0F\uFF0C\u4EC5\u542F\u7528 DOM \u76D1\u542C");
4024
4571
  domMonitor = await createDomShareMonitor(page, {
4025
4572
  prefix: share.prefix,
4026
4573
  selectors: domSelectors,
@@ -4035,14 +4582,14 @@ var Share = {
4035
4582
  });
4036
4583
  }
4037
4584
  if (share.mode === "response") {
4038
- logger12.info(`\u5F53\u524D\u4E3A\u63A5\u53E3\u6A21\u5F0F\uFF0C\u6302\u8F7D response \u76D1\u542C: apiMatchers=${toJsonInline(apiMatchers, 160)}`);
4585
+ logger11.info(`\u5F53\u524D\u4E3A\u63A5\u53E3\u6A21\u5F0F\uFF0C\u6302\u8F7D response \u76D1\u542C: apiMatchers=${toJsonInline(apiMatchers, 160)}`);
4039
4586
  page.on("response", onResponse);
4040
4587
  }
4041
4588
  const deadline = Date.now() + timeoutMs;
4042
4589
  const getRemainingMs = () => Math.max(0, deadline - Date.now());
4043
4590
  try {
4044
4591
  const actionTimeout = getRemainingMs();
4045
- logger12.start("captureLink.performActions", `\u6267\u884C\u52A8\u4F5C\u9884\u7B97=${actionTimeout}ms`);
4592
+ logger11.start("captureLink.performActions", `\u6267\u884C\u52A8\u4F5C\u9884\u7B97=${actionTimeout}ms`);
4046
4593
  if (actionTimeout > 0) {
4047
4594
  let timer = null;
4048
4595
  let actionError = null;
@@ -4056,21 +4603,21 @@ var Share = {
4056
4603
  const actionResult = await Promise.race([actionPromise, timeoutPromise]);
4057
4604
  if (timer) clearTimeout(timer);
4058
4605
  if (actionResult === "__ACTION_ERROR__") {
4059
- logger12.fail("captureLink.performActions", actionError);
4606
+ logger11.fail("captureLink.performActions", actionError);
4060
4607
  throw actionError;
4061
4608
  }
4062
4609
  if (actionResult === "__ACTION_TIMEOUT__") {
4063
4610
  stats.actionTimedOut = true;
4064
- logger12.warning(`performActions \u5DF2\u8D85\u65F6 (${actionTimeout}ms)\uFF0C\u52A8\u4F5C\u53EF\u80FD\u4ECD\u5728\u5F02\u6B65\u6267\u884C`);
4611
+ logger11.warning(`performActions \u5DF2\u8D85\u65F6 (${actionTimeout}ms)\uFF0C\u52A8\u4F5C\u53EF\u80FD\u4ECD\u5728\u5F02\u6B65\u6267\u884C`);
4065
4612
  } else {
4066
- logger12.success("captureLink.performActions", "\u6267\u884C\u52A8\u4F5C\u5B8C\u6210");
4613
+ logger11.success("captureLink.performActions", "\u6267\u884C\u52A8\u4F5C\u5B8C\u6210");
4067
4614
  }
4068
4615
  }
4069
4616
  let nextProgressLogTs = Date.now() + 3e3;
4070
4617
  while (true) {
4071
4618
  const selected = share.mode === "dom" ? candidates.dom : candidates.response;
4072
4619
  if (selected?.link) {
4073
- logger12.success("captureLink", `\u6355\u83B7\u6210\u529F: source=${share.mode}, link=${selected.link}`);
4620
+ logger11.success("captureLink", `\u6355\u83B7\u6210\u529F: source=${share.mode}, link=${selected.link}`);
4074
4621
  return {
4075
4622
  link: selected.link,
4076
4623
  payloadText: selected.payloadText,
@@ -4082,7 +4629,7 @@ var Share = {
4082
4629
  if (remaining <= 0) break;
4083
4630
  const now = Date.now();
4084
4631
  if (now >= nextProgressLogTs) {
4085
- logger12.info(
4632
+ logger11.info(
4086
4633
  `captureLink \u7B49\u5F85\u4E2D: remaining=${remaining}ms, domMutationCount=${stats.domMutationCount}, responseMatched=${stats.responseMatched}`
4087
4634
  );
4088
4635
  nextProgressLogTs = now + 5e3;
@@ -4090,11 +4637,11 @@ var Share = {
4090
4637
  await delay2(Math.max(0, Math.min(DEFAULT_POLL_INTERVAL_MS, remaining)));
4091
4638
  }
4092
4639
  if (share.mode === "response" && stats.responseMatched === 0) {
4093
- logger12.warning(
4640
+ logger11.warning(
4094
4641
  `\u63A5\u53E3\u76D1\u542C\u672A\u547D\u4E2D: apiMatchers=${toJsonInline(apiMatchers, 220)}, \u54CD\u5E94\u6837\u672CURLs=${toJsonInline(stats.responseSampleUrls, 420)}`
4095
4642
  );
4096
4643
  }
4097
- logger12.warning(
4644
+ logger11.warning(
4098
4645
  `captureLink \u8D85\u65F6\u672A\u62FF\u5230\u94FE\u63A5: mode=${share.mode}, actionTimedOut=${stats.actionTimedOut}, domMutationCount=${stats.domMutationCount}, responseObserved=${stats.responseObserved}, responseMatched=${stats.responseMatched}, lastMatchedUrl=${stats.lastMatchedUrl || "none"}`
4099
4646
  );
4100
4647
  return {
@@ -4106,7 +4653,7 @@ var Share = {
4106
4653
  } finally {
4107
4654
  if (share.mode === "response") {
4108
4655
  page.off("response", onResponse);
4109
- logger12.info("response \u76D1\u542C\u5DF2\u5378\u8F7D");
4656
+ logger11.info("response \u76D1\u542C\u5DF2\u5378\u8F7D");
4110
4657
  }
4111
4658
  await stopDomMonitor();
4112
4659
  }
@@ -4192,8 +4739,8 @@ var usePlaywrightToolKit = () => {
4192
4739
  LiveView,
4193
4740
  Constants: constants_exports,
4194
4741
  Utils,
4742
+ RuntimeEnv,
4195
4743
  Captcha,
4196
- Sse,
4197
4744
  Errors: errors_exports,
4198
4745
  Mutation,
4199
4746
  Display,