@kaitranntt/ccs 7.79.1-dev.19 → 7.79.1-dev.20
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.
|
@@ -5492,7 +5492,10 @@ function buildRecordingInstallExpression(recordingPayload) {
|
|
|
5492
5492
|
const recordingPayload = JSON.parse(${JSON.stringify(JSON.stringify(recordingPayload))});
|
|
5493
5493
|
const existing = globalThis.__CCS_BROWSER_RECORDING_RECORDER__;
|
|
5494
5494
|
if (existing && existing.installed === true) {
|
|
5495
|
-
|
|
5495
|
+
if (typeof existing.teardown === 'function') {
|
|
5496
|
+
existing.teardown();
|
|
5497
|
+
}
|
|
5498
|
+
delete globalThis.__CCS_BROWSER_RECORDING_RECORDER__;
|
|
5496
5499
|
}
|
|
5497
5500
|
|
|
5498
5501
|
const events = Array.isArray(recordingPayload.events) ? [...recordingPayload.events] : [];
|
|
@@ -5524,8 +5527,42 @@ function buildRecordingInstallExpression(recordingPayload) {
|
|
|
5524
5527
|
timestamp: Date.now(),
|
|
5525
5528
|
});
|
|
5526
5529
|
};
|
|
5530
|
+
const sensitiveInputTypes = new Set(['password', 'hidden']);
|
|
5531
|
+
const sensitiveAttributePattern = /(?:pass(?:word|code|phrase)?|pwd|secret|token|api[-_ ]?key|access[-_ ]?key|private[-_ ]?key|credential|otp|one[-_ ]?time[-_ ]?(?:code|password)|verif(?:ication)?[-_ ]?code|security[-_ ]?code|pin|auth(?:orization)?[-_ ]?(?:code|token)|mfa|2fa)/i;
|
|
5532
|
+
const sensitiveAutocompleteValues = new Set([
|
|
5533
|
+
'current-password',
|
|
5534
|
+
'new-password',
|
|
5535
|
+
'one-time-code',
|
|
5536
|
+
'cc-number',
|
|
5537
|
+
'cc-csc',
|
|
5538
|
+
]);
|
|
5539
|
+
const isSensitiveTextTarget = (target) => {
|
|
5540
|
+
if (!target || !(target instanceof Element)) {
|
|
5541
|
+
return false;
|
|
5542
|
+
}
|
|
5543
|
+
if (target instanceof HTMLInputElement) {
|
|
5544
|
+
const inputType = String(target.type || '').toLowerCase();
|
|
5545
|
+
if (sensitiveInputTypes.has(inputType)) {
|
|
5546
|
+
return true;
|
|
5547
|
+
}
|
|
5548
|
+
}
|
|
5549
|
+
const autocompleteTokens = String(target.getAttribute('autocomplete') || '')
|
|
5550
|
+
.toLowerCase()
|
|
5551
|
+
.split(/\s+/)
|
|
5552
|
+
.filter(Boolean);
|
|
5553
|
+
if (autocompleteTokens.some((token) => sensitiveAutocompleteValues.has(token))) {
|
|
5554
|
+
return true;
|
|
5555
|
+
}
|
|
5556
|
+
const attributesToInspect = ['id', 'name', 'placeholder', 'aria-label', 'data-testid'];
|
|
5557
|
+
return attributesToInspect.some((attributeName) =>
|
|
5558
|
+
sensitiveAttributePattern.test(String(target.getAttribute(attributeName) || ''))
|
|
5559
|
+
);
|
|
5560
|
+
};
|
|
5527
5561
|
const onInput = (event) => {
|
|
5528
5562
|
const target = event.target;
|
|
5563
|
+
if (isSensitiveTextTarget(target)) {
|
|
5564
|
+
return;
|
|
5565
|
+
}
|
|
5529
5566
|
let text = '';
|
|
5530
5567
|
if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement) {
|
|
5531
5568
|
text = target.value;
|
|
@@ -5536,7 +5573,19 @@ function buildRecordingInstallExpression(recordingPayload) {
|
|
|
5536
5573
|
}
|
|
5537
5574
|
pushEvent({ kind: 'type', selector: getSelector(target), text, timestamp: Date.now() });
|
|
5538
5575
|
};
|
|
5576
|
+
const isRecordableKey = (event) => {
|
|
5577
|
+
if (isSensitiveTextTarget(event.target)) {
|
|
5578
|
+
return false;
|
|
5579
|
+
}
|
|
5580
|
+
if (typeof event.key !== 'string' || event.key.length !== 1) {
|
|
5581
|
+
return true;
|
|
5582
|
+
}
|
|
5583
|
+
return event.altKey === true || event.ctrlKey === true || event.metaKey === true;
|
|
5584
|
+
};
|
|
5539
5585
|
const onKeyDown = (event) => {
|
|
5586
|
+
if (!isRecordableKey(event)) {
|
|
5587
|
+
return;
|
|
5588
|
+
}
|
|
5540
5589
|
const modifiers = [];
|
|
5541
5590
|
if (event.altKey) modifiers.push('Alt');
|
|
5542
5591
|
if (event.ctrlKey) modifiers.push('Control');
|
|
@@ -5604,7 +5653,19 @@ async function finalizeRecordingCapture(session) {
|
|
|
5604
5653
|
};
|
|
5605
5654
|
|
|
5606
5655
|
const response = await sendCdpCommand(page, 'Runtime.evaluate', {
|
|
5607
|
-
expression: `(() =>
|
|
5656
|
+
expression: `(() => {
|
|
5657
|
+
const recorder = globalThis.__CCS_BROWSER_RECORDING_RECORDER__;
|
|
5658
|
+
if (!recorder) {
|
|
5659
|
+
return { events: [], warnings: [] };
|
|
5660
|
+
}
|
|
5661
|
+
const events = Array.isArray(recorder.events) ? [...recorder.events] : [];
|
|
5662
|
+
const warnings = Array.isArray(recorder.warnings) ? [...recorder.warnings] : [];
|
|
5663
|
+
if (typeof recorder.teardown === 'function') {
|
|
5664
|
+
recorder.teardown();
|
|
5665
|
+
}
|
|
5666
|
+
delete globalThis.__CCS_BROWSER_RECORDING_RECORDER__;
|
|
5667
|
+
return { events, warnings };
|
|
5668
|
+
})()`,
|
|
5608
5669
|
returnByValue: true,
|
|
5609
5670
|
awaitPromise: true,
|
|
5610
5671
|
});
|
|
@@ -5624,6 +5685,28 @@ async function finalizeRecordingCapture(session) {
|
|
|
5624
5685
|
session.warnings = warnings.map((warning) => String(warning.message || warning));
|
|
5625
5686
|
}
|
|
5626
5687
|
|
|
5688
|
+
async function teardownRecordingCapture(session) {
|
|
5689
|
+
if (!session || !session.pageWebSocketDebuggerUrl) {
|
|
5690
|
+
return;
|
|
5691
|
+
}
|
|
5692
|
+
const page = {
|
|
5693
|
+
id: session.pageId,
|
|
5694
|
+
webSocketDebuggerUrl: session.pageWebSocketDebuggerUrl,
|
|
5695
|
+
};
|
|
5696
|
+
await sendCdpCommand(page, 'Runtime.evaluate', {
|
|
5697
|
+
expression: `(() => {
|
|
5698
|
+
const recorder = globalThis.__CCS_BROWSER_RECORDING_RECORDER__;
|
|
5699
|
+
if (recorder && typeof recorder.teardown === 'function') {
|
|
5700
|
+
recorder.teardown();
|
|
5701
|
+
}
|
|
5702
|
+
delete globalThis.__CCS_BROWSER_RECORDING_RECORDER__;
|
|
5703
|
+
return { installed: false };
|
|
5704
|
+
})()`,
|
|
5705
|
+
returnByValue: true,
|
|
5706
|
+
awaitPromise: true,
|
|
5707
|
+
});
|
|
5708
|
+
}
|
|
5709
|
+
|
|
5627
5710
|
async function handleStartRecording(toolArgs) {
|
|
5628
5711
|
if (activeRecordingSession) {
|
|
5629
5712
|
throw new Error('recording already active');
|
|
@@ -5674,6 +5757,11 @@ async function handleStopRecording() {
|
|
|
5674
5757
|
} catch (error) {
|
|
5675
5758
|
finalizeError = error instanceof Error ? error : new Error(String(error));
|
|
5676
5759
|
session.warnings.push(`recording capture finalization failed: ${finalizeError.message}`);
|
|
5760
|
+
try {
|
|
5761
|
+
await teardownRecordingCapture(session);
|
|
5762
|
+
} catch {
|
|
5763
|
+
// Preserve the original finalization failure for the caller.
|
|
5764
|
+
}
|
|
5677
5765
|
}
|
|
5678
5766
|
session.status = 'stopped';
|
|
5679
5767
|
session.stoppedAt = new Date().toISOString();
|
|
@@ -5694,6 +5782,12 @@ async function handleClearRecording() {
|
|
|
5694
5782
|
if (!latestRecordingSession && !activeRecordingSession) {
|
|
5695
5783
|
throw new Error('no recording available');
|
|
5696
5784
|
}
|
|
5785
|
+
const session = activeRecordingSession || latestRecordingSession;
|
|
5786
|
+
try {
|
|
5787
|
+
await teardownRecordingCapture(session);
|
|
5788
|
+
} catch {
|
|
5789
|
+
// Clearing session-local recording state should still succeed if the page is already gone.
|
|
5790
|
+
}
|
|
5697
5791
|
activeRecordingSession = null;
|
|
5698
5792
|
latestRecordingSession = null;
|
|
5699
5793
|
return 'status: cleared';
|