@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
- return { installed: true };
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: `(() => globalThis.__CCS_BROWSER_RECORDING_RECORDER__ || { events: [], warnings: [] })()`,
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';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaitranntt/ccs",
3
- "version": "7.79.1-dev.19",
3
+ "version": "7.79.1-dev.20",
4
4
  "description": "Claude Code Switch - Instant profile switching between Claude, GLM, Kimi, and more",
5
5
  "keywords": [
6
6
  "cli",