@skrillex1224/playwright-toolkit 2.1.114 → 2.1.115

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.cjs CHANGED
@@ -3051,97 +3051,28 @@ var Logger = {
3051
3051
 
3052
3052
  // src/share.js
3053
3053
  var import_delay3 = __toESM(require("delay"), 1);
3054
-
3055
- // src/internals/monitor.js
3056
- var DEFAULT_DOM_SELECTORS = "html";
3057
- var Monitor = {
3058
- /**
3059
- * 使用 Mutation.useMonitor 监控 DOM,并提取分享链接
3060
- *
3061
- * @param {import('playwright').Page} page
3062
- * @param {Object} [options]
3063
- * @param {(string | RegExp)[]} [options.identities]
3064
- * @param {string | string[]} [options.selectors]
3065
- * @param {'added' | 'changed' | 'all'} [options.mode]
3066
- * @param {(text: string, options?: { identities?: (string | RegExp)[] }) => string[]} [options.parseLinks]
3067
- * @param {(payload: { link: string; rawDom: string; mutationCount: number; html: string; text: string; mutationNodes: Array<{ html: string; text: string; mutationType: string }> }) => void} [options.onMatch]
3068
- * @returns {Promise<{ stop: () => Promise<{ totalMutations: number }> }>}
3069
- */
3070
- async useShareLinkMonitor(page, options = {}) {
3071
- const identities = Array.isArray(options.identities) ? options.identities : [];
3072
- const selectors = options.selectors ?? DEFAULT_DOM_SELECTORS;
3073
- const mode = options.mode ?? Mutation.Mode.Added;
3074
- const parseLinks = typeof options.parseLinks === "function" ? options.parseLinks : Utils.parseLinks;
3075
- const onMatch = typeof options.onMatch === "function" ? options.onMatch : null;
3076
- let matched = false;
3077
- const monitor = await Mutation.useMonitor(page, selectors, {
3078
- mode,
3079
- onMutation: (context = {}) => {
3080
- if (matched) return;
3081
- const html = String(context.html || "");
3082
- const text = String(context.text || "");
3083
- const rawDom = `${html}
3084
- ${text}`;
3085
- const [candidate] = parseLinks(rawDom, { identities }) || [];
3086
- if (!candidate) return;
3087
- matched = true;
3088
- if (onMatch) {
3089
- onMatch({
3090
- link: candidate,
3091
- rawDom,
3092
- mutationCount: context.mutationCount || 0,
3093
- html,
3094
- text,
3095
- mutationNodes: Array.isArray(context.mutationNodes) ? context.mutationNodes : []
3096
- });
3097
- }
3098
- }
3099
- });
3100
- return {
3101
- stop: async () => {
3102
- return await monitor.stop();
3103
- }
3104
- };
3105
- }
3106
- };
3107
-
3108
- // src/share.js
3109
3054
  var DEFAULT_TIMEOUT_AFTER_ACTION_MS = 10 * 1e3;
3110
3055
  var DEFAULT_PAYLOAD_SNAPSHOT_MAX_LEN = 500;
3056
+ var DEFAULT_POLL_INTERVAL_MS = 120;
3111
3057
  var createRuntimeKey = (prefix) => `__${prefix}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
3112
3058
  var Share = {
3113
3059
  /**
3114
- * 捕获分享链接(剪切板 + DOM + 接口 三通道)
3115
- * 优先级:clipboard > dom > response
3060
+ * 捕获分享链接(仅被动剪切板写入通道)
3061
+ * 不做 clipboard-read,避免权限弹窗
3116
3062
  *
3117
3063
  * @param {import('playwright').Page} page
3118
3064
  * @param {Object} [options]
3119
3065
  * @param {(string | RegExp)[]} [options.identities]
3120
3066
  * @param {number} [options.timeoutAfterActionMs]
3121
3067
  * @param {number} [options.payloadSnapshotMaxLen]
3122
- * @param {boolean} [options.enableClipboard=true]
3123
- * @param {boolean} [options.enableDom=true]
3124
- * @param {boolean} [options.enableResponse=true]
3125
- * @param {string | string[]} [options.domSelectors='html']
3126
- * @param {'added' | 'changed' | 'all'} [options.domMode='added']
3127
- * @param {number} [options.clipboardPollIntervalMs=220]
3128
- * @param {(response: import('playwright').Response) => boolean | Promise<boolean>} [options.responseFilter]
3129
- * @param {(text: string, options?: { identities?: (string | RegExp)[] }) => string[]} [options.parseLinks]
3130
3068
  * @param {() => Promise<void>} [options.performActions]
3131
- * @returns {Promise<{ link: string | null; payloadText: string; payloadSnapshot: string; source: 'clipboard' | 'dom' | 'response' | 'none' }>}
3069
+ * @returns {Promise<{ link: string | null; payloadText: string; payloadSnapshot: string; source: 'clipboard' | 'none' }>}
3132
3070
  */
3133
3071
  async captureLink(page, options = {}) {
3134
3072
  const identities = Array.isArray(options.identities) ? options.identities : [];
3135
3073
  const timeoutAfterActionMs = options.timeoutAfterActionMs ?? DEFAULT_TIMEOUT_AFTER_ACTION_MS;
3074
+ const totalTimeoutMs = Math.max(0, Number(timeoutAfterActionMs) || 0);
3136
3075
  const payloadSnapshotMaxLen = options.payloadSnapshotMaxLen ?? DEFAULT_PAYLOAD_SNAPSHOT_MAX_LEN;
3137
- const enableClipboard = options.enableClipboard !== false;
3138
- const enableDom = options.enableDom !== false;
3139
- const enableResponse = options.enableResponse !== false;
3140
- const domSelectors = options.domSelectors ?? "html";
3141
- const domMode = options.domMode ?? Mutation.Mode.Added;
3142
- const clipboardPollIntervalMs = Math.max(80, options.clipboardPollIntervalMs ?? 220);
3143
- const responseFilter = typeof options.responseFilter === "function" ? options.responseFilter : null;
3144
- const parseLinks = typeof options.parseLinks === "function" ? options.parseLinks : Utils.parseLinks;
3145
3076
  const performActions = typeof options.performActions === "function" ? options.performActions : async () => {
3146
3077
  };
3147
3078
  const clipboardKeys = {
@@ -3149,59 +3080,17 @@ var Share = {
3149
3080
  hookReady: createRuntimeKey("pk_clip_hook_ready"),
3150
3081
  copyListener: createRuntimeKey("pk_clip_copy_listener"),
3151
3082
  execWrapped: createRuntimeKey("pk_clip_exec_wrapped"),
3083
+ writeTextWrapped: createRuntimeKey("pk_clip_write_text_wrapped"),
3152
3084
  writeWrapped: createRuntimeKey("pk_clip_write_wrapped")
3153
3085
  };
3154
- if (!enableClipboard && !enableResponse && !enableDom) {
3155
- throw new Error("Share.captureLink requires at least one channel: clipboard/dom/response");
3156
- }
3157
- const candidates = {
3158
- clipboard: null,
3159
- dom: null,
3160
- response: null
3161
- };
3162
- const setCandidate = (sourceKey, candidateLink, payload) => {
3163
- if (!candidateLink) return false;
3164
- if (candidates[sourceKey]?.link) return false;
3165
- candidates[sourceKey] = {
3166
- link: candidateLink,
3167
- payloadText: String(payload || "")
3168
- };
3169
- return true;
3170
- };
3171
- let domMonitor = null;
3172
- const stopDomMonitor = async () => {
3173
- if (!domMonitor) return;
3174
- const monitor = domMonitor;
3175
- domMonitor = null;
3176
- try {
3177
- await monitor.stop();
3178
- } catch {
3179
- }
3180
- };
3181
- const onResponse = async (response) => {
3182
- if (candidates.response?.link) return;
3183
- try {
3184
- if (responseFilter) {
3185
- const accepted = await responseFilter(response);
3186
- if (!accepted) return;
3187
- }
3188
- const text = await response.text();
3189
- if (!text) return;
3190
- const [candidate] = parseLinks(text, { identities }) || [];
3191
- if (!candidate) return;
3192
- setCandidate("response", candidate, text);
3193
- } catch {
3194
- }
3195
- };
3196
3086
  const ensureClipboardHooks = async () => {
3197
- if (!enableClipboard) return;
3198
3087
  try {
3199
3088
  await page.evaluate(({ keys }) => {
3200
3089
  if (document[keys.hookReady]) return;
3201
3090
  const state = document[keys.state] || { captured: "" };
3202
3091
  document[keys.state] = state;
3203
3092
  const normalize = (value) => String(value || "").trim();
3204
- const setClipboard = (value) => {
3093
+ const setCaptured = (value) => {
3205
3094
  const safe = normalize(value);
3206
3095
  if (!safe) return;
3207
3096
  state.captured = safe;
@@ -3209,12 +3098,38 @@ var Share = {
3209
3098
  try {
3210
3099
  if (navigator.clipboard && typeof navigator.clipboard.writeText === "function") {
3211
3100
  const clipboard = navigator.clipboard;
3212
- if (!clipboard[keys.writeWrapped]) {
3101
+ if (!clipboard[keys.writeTextWrapped]) {
3213
3102
  const originalWriteText = clipboard.writeText.bind(clipboard);
3214
3103
  clipboard.writeText = async (text) => {
3215
- setClipboard(text);
3104
+ setCaptured(text);
3216
3105
  return await originalWriteText(text);
3217
3106
  };
3107
+ clipboard[keys.writeTextWrapped] = true;
3108
+ }
3109
+ }
3110
+ } catch {
3111
+ }
3112
+ try {
3113
+ if (navigator.clipboard && typeof navigator.clipboard.write === "function") {
3114
+ const clipboard = navigator.clipboard;
3115
+ if (!clipboard[keys.writeWrapped]) {
3116
+ const originalWrite = clipboard.write.bind(clipboard);
3117
+ clipboard.write = async (data) => {
3118
+ try {
3119
+ if (Array.isArray(data) && data.length > 0) {
3120
+ for (const item of data) {
3121
+ if (!item?.types?.length) continue;
3122
+ if (!item.types.includes("text/plain")) continue;
3123
+ const blob = await item.getType("text/plain");
3124
+ const text = await blob.text();
3125
+ setCaptured(text);
3126
+ break;
3127
+ }
3128
+ }
3129
+ } catch {
3130
+ }
3131
+ return await originalWrite(data);
3132
+ };
3218
3133
  clipboard[keys.writeWrapped] = true;
3219
3134
  }
3220
3135
  }
@@ -3225,7 +3140,7 @@ var Share = {
3225
3140
  document.addEventListener("copy", (event) => {
3226
3141
  try {
3227
3142
  const copied = event?.clipboardData?.getData?.("text/plain") || "";
3228
- setClipboard(copied);
3143
+ setCaptured(copied);
3229
3144
  } catch {
3230
3145
  }
3231
3146
  }, true);
@@ -3240,7 +3155,7 @@ var Share = {
3240
3155
  try {
3241
3156
  if (String(command || "").toLowerCase() === "copy") {
3242
3157
  const selected = globalThis.getSelection?.().toString?.() || "";
3243
- setClipboard(selected);
3158
+ setCaptured(selected);
3244
3159
  }
3245
3160
  } catch {
3246
3161
  }
@@ -3255,99 +3170,59 @@ var Share = {
3255
3170
  } catch {
3256
3171
  }
3257
3172
  };
3258
- const probeClipboard = async () => {
3259
- if (!enableClipboard) return null;
3173
+ const probeCapturedLink = async () => {
3260
3174
  try {
3261
- const data = await page.evaluate(async ({ keys }) => {
3262
- const capturedText = String(document[keys.state]?.captured || "").trim();
3263
- let directText = "";
3264
- try {
3265
- if (navigator.clipboard && typeof navigator.clipboard.readText === "function") {
3266
- directText = String(await navigator.clipboard.readText() || "").trim();
3267
- }
3268
- } catch {
3269
- }
3270
- return { capturedText, directText };
3175
+ const capturedText = await page.evaluate(({ keys }) => {
3176
+ return String(document[keys.state]?.captured || "").trim();
3271
3177
  }, { keys: clipboardKeys });
3272
- const [capturedLink] = parseLinks(data?.capturedText || "", { identities }) || [];
3273
- if (capturedLink) {
3274
- return {
3275
- link: capturedLink,
3276
- payloadText: data.capturedText || ""
3277
- };
3278
- }
3279
- const [directLink] = parseLinks(data?.directText || "", { identities }) || [];
3280
- if (directLink) {
3281
- return {
3282
- link: directLink,
3283
- payloadText: data.directText || ""
3284
- };
3285
- }
3178
+ const [capturedLink] = Utils.parseLinks(capturedText || "", { identities }) || [];
3179
+ if (!capturedLink) return null;
3180
+ return {
3181
+ link: capturedLink,
3182
+ payloadText: capturedText || ""
3183
+ };
3286
3184
  } catch {
3185
+ return null;
3287
3186
  }
3288
- return null;
3289
3187
  };
3290
3188
  await ensureClipboardHooks();
3291
- if (enableDom) {
3292
- domMonitor = await Monitor.useShareLinkMonitor(page, {
3293
- identities,
3294
- selectors: domSelectors,
3295
- mode: domMode,
3296
- parseLinks,
3297
- onMatch: ({ link: domLink, rawDom }) => {
3298
- setCandidate("dom", domLink, rawDom);
3299
- }
3300
- });
3301
- }
3302
- if (enableResponse) {
3303
- page.on("response", onResponse);
3189
+ const deadline = Date.now() + totalTimeoutMs;
3190
+ const getRemainingMs = () => Math.max(0, deadline - Date.now());
3191
+ let timer = null;
3192
+ let actionError = null;
3193
+ const actionPromise = Promise.resolve().then(() => performActions()).then(() => "__ACTION_DONE__").catch((error) => {
3194
+ actionError = error;
3195
+ return "__ACTION_ERROR__";
3196
+ });
3197
+ const timeoutPromise = new Promise((resolve) => {
3198
+ timer = setTimeout(() => resolve("__ACTION_TIMEOUT__"), Math.max(1, getRemainingMs()));
3199
+ });
3200
+ const actionResult = await Promise.race([actionPromise, timeoutPromise]);
3201
+ if (timer) clearTimeout(timer);
3202
+ if (actionResult === "__ACTION_ERROR__") {
3203
+ throw actionError;
3304
3204
  }
3305
- try {
3306
- await performActions();
3307
- const deadline = Date.now() + Math.max(0, timeoutAfterActionMs);
3308
- const nonClipboardGraceMs = Math.max(120, Math.min(500, clipboardPollIntervalMs * 2));
3309
- let nonClipboardSeenAt = null;
3310
- while (true) {
3311
- if (enableClipboard) {
3312
- const clipboardResult = await probeClipboard();
3313
- if (clipboardResult?.link) {
3314
- setCandidate("clipboard", clipboardResult.link, clipboardResult.payloadText);
3315
- }
3316
- }
3317
- if (candidates.clipboard?.link) {
3318
- break;
3319
- }
3320
- if (candidates.dom?.link || candidates.response?.link) {
3321
- if (!nonClipboardSeenAt) {
3322
- nonClipboardSeenAt = Date.now();
3323
- } else if (Date.now() - nonClipboardSeenAt >= nonClipboardGraceMs) {
3324
- break;
3325
- }
3326
- }
3327
- if (Date.now() >= deadline) {
3328
- break;
3329
- }
3330
- const remaining = deadline - Date.now();
3331
- await new Promise((resolve) => {
3332
- setTimeout(resolve, Math.max(0, Math.min(clipboardPollIntervalMs, remaining)));
3333
- });
3334
- }
3335
- const finalMatch = candidates.clipboard || candidates.dom || candidates.response || null;
3336
- const finalSource = candidates.clipboard ? "clipboard" : candidates.dom ? "dom" : candidates.response ? "response" : "none";
3337
- const payloadText = finalMatch?.payloadText || "";
3338
- const payloadSnapshot = payloadText ? payloadText.replace(/\s+/g, " ").trim().slice(0, payloadSnapshotMaxLen) : "";
3339
- return {
3340
- link: finalMatch?.link || null,
3341
- payloadText,
3342
- payloadSnapshot,
3343
- source: finalSource
3344
- };
3345
- } finally {
3346
- if (enableResponse) {
3347
- page.off("response", onResponse);
3205
+ while (true) {
3206
+ const captured = await probeCapturedLink();
3207
+ if (captured?.link) {
3208
+ const payloadSnapshot = captured.payloadText ? captured.payloadText.replace(/\s+/g, " ").trim().slice(0, payloadSnapshotMaxLen) : "";
3209
+ return {
3210
+ link: captured.link,
3211
+ payloadText: captured.payloadText,
3212
+ payloadSnapshot,
3213
+ source: "clipboard"
3214
+ };
3348
3215
  }
3349
- await stopDomMonitor();
3216
+ const remaining = getRemainingMs();
3217
+ if (remaining <= 0) break;
3218
+ await (0, import_delay3.default)(Math.max(0, Math.min(DEFAULT_POLL_INTERVAL_MS, remaining)));
3350
3219
  }
3220
+ return {
3221
+ link: null,
3222
+ payloadText: "",
3223
+ payloadSnapshot: "",
3224
+ source: "none"
3225
+ };
3351
3226
  },
3352
3227
  /**
3353
3228
  * 全页面滚动截图(基础能力)