@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 +81 -206
- package/dist/index.cjs.map +4 -4
- package/dist/index.js +81 -206
- package/dist/index.js.map +4 -4
- package/index.d.ts +2 -10
- package/package.json +1 -1
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
|
-
*
|
|
3115
|
-
*
|
|
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' | '
|
|
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
|
|
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.
|
|
3101
|
+
if (!clipboard[keys.writeTextWrapped]) {
|
|
3213
3102
|
const originalWriteText = clipboard.writeText.bind(clipboard);
|
|
3214
3103
|
clipboard.writeText = async (text) => {
|
|
3215
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
3259
|
-
if (!enableClipboard) return null;
|
|
3173
|
+
const probeCapturedLink = async () => {
|
|
3260
3174
|
try {
|
|
3261
|
-
const
|
|
3262
|
-
|
|
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(
|
|
3273
|
-
if (capturedLink)
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
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
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
}
|
|
3302
|
-
|
|
3303
|
-
|
|
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
|
-
|
|
3306
|
-
await
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
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
|
-
|
|
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
|
* 全页面滚动截图(基础能力)
|