poke-browser 0.4.2 → 0.4.4

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.
@@ -661,13 +661,20 @@ async function clearFocusedFieldViaDebuggerKeys(tabId) {
661
661
  key: "a",
662
662
  code: "KeyA",
663
663
  windowsVirtualKeyCode: 65,
664
+ nativeVirtualKeyCode: 65,
665
+ unmodifiedText: "a",
666
+ text: "a",
664
667
  modifiers: mod,
668
+ autoRepeat: false,
665
669
  });
666
670
  await debuggerSend(tabId, "Input.dispatchKeyEvent", {
667
671
  type: "keyUp",
668
672
  key: "a",
669
673
  code: "KeyA",
670
674
  windowsVirtualKeyCode: 65,
675
+ nativeVirtualKeyCode: 65,
676
+ unmodifiedText: "a",
677
+ text: "a",
671
678
  modifiers: mod,
672
679
  });
673
680
  await debuggerSend(tabId, "Input.dispatchKeyEvent", {
@@ -675,15 +682,359 @@ async function clearFocusedFieldViaDebuggerKeys(tabId) {
675
682
  key: "Backspace",
676
683
  code: "Backspace",
677
684
  windowsVirtualKeyCode: 8,
685
+ nativeVirtualKeyCode: 8,
686
+ unmodifiedText: "",
687
+ text: "",
688
+ modifiers: 0,
689
+ autoRepeat: false,
678
690
  });
679
691
  await debuggerSend(tabId, "Input.dispatchKeyEvent", {
680
692
  type: "keyUp",
681
693
  key: "Backspace",
682
694
  code: "Backspace",
683
695
  windowsVirtualKeyCode: 8,
696
+ nativeVirtualKeyCode: 8,
697
+ unmodifiedText: "",
698
+ text: "",
699
+ modifiers: 0,
684
700
  });
685
701
  }
686
702
 
703
+ /**
704
+ * US QWERTY CDP key fields for printable ASCII (Input.dispatchKeyEvent).
705
+ * @typedef {{ key: string, code: string, windowsVirtualKeyCode: number, nativeVirtualKeyCode: number, modifiers: number, unmodifiedText: string, text: string }} CdpPrintableDescriptor
706
+ */
707
+
708
+ /**
709
+ * @param {string} ch Single UTF-16 code unit
710
+ * @returns {CdpPrintableDescriptor}
711
+ */
712
+ function cdpKeyDescriptorForPrintableChar(ch) {
713
+ const cp = ch.codePointAt(0);
714
+
715
+ if (cp >= 97 && cp <= 122) {
716
+ const up = String.fromCharCode(cp - 32);
717
+ return {
718
+ key: ch,
719
+ code: `Key${up}`,
720
+ windowsVirtualKeyCode: up.charCodeAt(0),
721
+ nativeVirtualKeyCode: up.charCodeAt(0),
722
+ modifiers: 0,
723
+ unmodifiedText: ch,
724
+ text: ch,
725
+ };
726
+ }
727
+ if (cp >= 65 && cp <= 90) {
728
+ const low = String.fromCharCode(cp + 32);
729
+ return {
730
+ key: ch,
731
+ code: `Key${ch}`,
732
+ windowsVirtualKeyCode: cp,
733
+ nativeVirtualKeyCode: cp,
734
+ modifiers: 8,
735
+ unmodifiedText: low,
736
+ text: ch,
737
+ };
738
+ }
739
+ if (cp >= 48 && cp <= 57) {
740
+ return {
741
+ key: ch,
742
+ code: `Digit${ch}`,
743
+ windowsVirtualKeyCode: cp,
744
+ nativeVirtualKeyCode: cp,
745
+ modifiers: 0,
746
+ unmodifiedText: ch,
747
+ text: ch,
748
+ };
749
+ }
750
+ if (ch === " ") {
751
+ return {
752
+ key: " ",
753
+ code: "Space",
754
+ windowsVirtualKeyCode: 32,
755
+ nativeVirtualKeyCode: 32,
756
+ modifiers: 0,
757
+ unmodifiedText: " ",
758
+ text: " ",
759
+ };
760
+ }
761
+
762
+ /** @type {Record<string, Omit<CdpPrintableDescriptor, "key">>} */
763
+ const map = {
764
+ "`": {
765
+ code: "Backquote",
766
+ windowsVirtualKeyCode: 192,
767
+ nativeVirtualKeyCode: 192,
768
+ modifiers: 0,
769
+ unmodifiedText: "`",
770
+ text: "`",
771
+ },
772
+ "-": {
773
+ code: "Minus",
774
+ windowsVirtualKeyCode: 189,
775
+ nativeVirtualKeyCode: 189,
776
+ modifiers: 0,
777
+ unmodifiedText: "-",
778
+ text: "-",
779
+ },
780
+ "=": {
781
+ code: "Equal",
782
+ windowsVirtualKeyCode: 187,
783
+ nativeVirtualKeyCode: 187,
784
+ modifiers: 0,
785
+ unmodifiedText: "=",
786
+ text: "=",
787
+ },
788
+ "[": {
789
+ code: "BracketLeft",
790
+ windowsVirtualKeyCode: 219,
791
+ nativeVirtualKeyCode: 219,
792
+ modifiers: 0,
793
+ unmodifiedText: "[",
794
+ text: "[",
795
+ },
796
+ "]": {
797
+ code: "BracketRight",
798
+ windowsVirtualKeyCode: 221,
799
+ nativeVirtualKeyCode: 221,
800
+ modifiers: 0,
801
+ unmodifiedText: "]",
802
+ text: "]",
803
+ },
804
+ "\\": {
805
+ code: "Backslash",
806
+ windowsVirtualKeyCode: 220,
807
+ nativeVirtualKeyCode: 220,
808
+ modifiers: 0,
809
+ unmodifiedText: "\\",
810
+ text: "\\",
811
+ },
812
+ ";": {
813
+ code: "Semicolon",
814
+ windowsVirtualKeyCode: 186,
815
+ nativeVirtualKeyCode: 186,
816
+ modifiers: 0,
817
+ unmodifiedText: ";",
818
+ text: ";",
819
+ },
820
+ "'": {
821
+ code: "Quote",
822
+ windowsVirtualKeyCode: 222,
823
+ nativeVirtualKeyCode: 222,
824
+ modifiers: 0,
825
+ unmodifiedText: "'",
826
+ text: "'",
827
+ },
828
+ ",": {
829
+ code: "Comma",
830
+ windowsVirtualKeyCode: 188,
831
+ nativeVirtualKeyCode: 188,
832
+ modifiers: 0,
833
+ unmodifiedText: ",",
834
+ text: ",",
835
+ },
836
+ ".": {
837
+ code: "Period",
838
+ windowsVirtualKeyCode: 190,
839
+ nativeVirtualKeyCode: 190,
840
+ modifiers: 0,
841
+ unmodifiedText: ".",
842
+ text: ".",
843
+ },
844
+ "/": {
845
+ code: "Slash",
846
+ windowsVirtualKeyCode: 191,
847
+ nativeVirtualKeyCode: 191,
848
+ modifiers: 0,
849
+ unmodifiedText: "/",
850
+ text: "/",
851
+ },
852
+ "!": {
853
+ code: "Digit1",
854
+ windowsVirtualKeyCode: 49,
855
+ nativeVirtualKeyCode: 49,
856
+ modifiers: 8,
857
+ unmodifiedText: "1",
858
+ text: "!",
859
+ },
860
+ "@": {
861
+ code: "Digit2",
862
+ windowsVirtualKeyCode: 50,
863
+ nativeVirtualKeyCode: 50,
864
+ modifiers: 8,
865
+ unmodifiedText: "2",
866
+ text: "@",
867
+ },
868
+ "#": {
869
+ code: "Digit3",
870
+ windowsVirtualKeyCode: 51,
871
+ nativeVirtualKeyCode: 51,
872
+ modifiers: 8,
873
+ unmodifiedText: "3",
874
+ text: "#",
875
+ },
876
+ $: {
877
+ code: "Digit4",
878
+ windowsVirtualKeyCode: 52,
879
+ nativeVirtualKeyCode: 52,
880
+ modifiers: 8,
881
+ unmodifiedText: "4",
882
+ text: "$",
883
+ },
884
+ "%": {
885
+ code: "Digit5",
886
+ windowsVirtualKeyCode: 53,
887
+ nativeVirtualKeyCode: 53,
888
+ modifiers: 8,
889
+ unmodifiedText: "5",
890
+ text: "%",
891
+ },
892
+ "^": {
893
+ code: "Digit6",
894
+ windowsVirtualKeyCode: 54,
895
+ nativeVirtualKeyCode: 54,
896
+ modifiers: 8,
897
+ unmodifiedText: "6",
898
+ text: "^",
899
+ },
900
+ "&": {
901
+ code: "Digit7",
902
+ windowsVirtualKeyCode: 55,
903
+ nativeVirtualKeyCode: 55,
904
+ modifiers: 8,
905
+ unmodifiedText: "7",
906
+ text: "&",
907
+ },
908
+ "*": {
909
+ code: "Digit8",
910
+ windowsVirtualKeyCode: 56,
911
+ nativeVirtualKeyCode: 56,
912
+ modifiers: 8,
913
+ unmodifiedText: "8",
914
+ text: "*",
915
+ },
916
+ "(": {
917
+ code: "Digit9",
918
+ windowsVirtualKeyCode: 57,
919
+ nativeVirtualKeyCode: 57,
920
+ modifiers: 8,
921
+ unmodifiedText: "9",
922
+ text: "(",
923
+ },
924
+ ")": {
925
+ code: "Digit0",
926
+ windowsVirtualKeyCode: 48,
927
+ nativeVirtualKeyCode: 48,
928
+ modifiers: 8,
929
+ unmodifiedText: "0",
930
+ text: ")",
931
+ },
932
+ _: {
933
+ code: "Minus",
934
+ windowsVirtualKeyCode: 189,
935
+ nativeVirtualKeyCode: 189,
936
+ modifiers: 8,
937
+ unmodifiedText: "-",
938
+ text: "_",
939
+ },
940
+ "+": {
941
+ code: "Equal",
942
+ windowsVirtualKeyCode: 187,
943
+ nativeVirtualKeyCode: 187,
944
+ modifiers: 8,
945
+ unmodifiedText: "=",
946
+ text: "+",
947
+ },
948
+ "{": {
949
+ code: "BracketLeft",
950
+ windowsVirtualKeyCode: 219,
951
+ nativeVirtualKeyCode: 219,
952
+ modifiers: 8,
953
+ unmodifiedText: "[",
954
+ text: "{",
955
+ },
956
+ "}": {
957
+ code: "BracketRight",
958
+ windowsVirtualKeyCode: 221,
959
+ nativeVirtualKeyCode: 221,
960
+ modifiers: 8,
961
+ unmodifiedText: "]",
962
+ text: "}",
963
+ },
964
+ "|": {
965
+ code: "Backslash",
966
+ windowsVirtualKeyCode: 220,
967
+ nativeVirtualKeyCode: 220,
968
+ modifiers: 8,
969
+ unmodifiedText: "\\",
970
+ text: "|",
971
+ },
972
+ ":": {
973
+ code: "Semicolon",
974
+ windowsVirtualKeyCode: 186,
975
+ nativeVirtualKeyCode: 186,
976
+ modifiers: 8,
977
+ unmodifiedText: ";",
978
+ text: ":",
979
+ },
980
+ '"': {
981
+ code: "Quote",
982
+ windowsVirtualKeyCode: 222,
983
+ nativeVirtualKeyCode: 222,
984
+ modifiers: 8,
985
+ unmodifiedText: "'",
986
+ text: '"',
987
+ },
988
+ "<": {
989
+ code: "Comma",
990
+ windowsVirtualKeyCode: 188,
991
+ nativeVirtualKeyCode: 188,
992
+ modifiers: 8,
993
+ unmodifiedText: ",",
994
+ text: "<",
995
+ },
996
+ ">": {
997
+ code: "Period",
998
+ windowsVirtualKeyCode: 190,
999
+ nativeVirtualKeyCode: 190,
1000
+ modifiers: 8,
1001
+ unmodifiedText: ".",
1002
+ text: ">",
1003
+ },
1004
+ "?": {
1005
+ code: "Slash",
1006
+ windowsVirtualKeyCode: 191,
1007
+ nativeVirtualKeyCode: 191,
1008
+ modifiers: 8,
1009
+ unmodifiedText: "/",
1010
+ text: "?",
1011
+ },
1012
+ "~": {
1013
+ code: "Backquote",
1014
+ windowsVirtualKeyCode: 192,
1015
+ nativeVirtualKeyCode: 192,
1016
+ modifiers: 8,
1017
+ unmodifiedText: "`",
1018
+ text: "~",
1019
+ },
1020
+ };
1021
+
1022
+ const row = map[ch];
1023
+ if (row) {
1024
+ return { key: ch, ...row };
1025
+ }
1026
+
1027
+ return {
1028
+ key: ch,
1029
+ code: "",
1030
+ windowsVirtualKeyCode: 0,
1031
+ nativeVirtualKeyCode: 0,
1032
+ modifiers: 0,
1033
+ unmodifiedText: ch,
1034
+ text: ch,
1035
+ };
1036
+ }
1037
+
687
1038
  /**
688
1039
  * @param {number} tabId
689
1040
  * @param {string} text
@@ -696,30 +1047,41 @@ async function typeTextViaDebugger(tabId, text, clearField) {
696
1047
  await clearFocusedFieldViaDebuggerKeys(tabId);
697
1048
  }
698
1049
  for (const ch of text) {
699
- if (ch === "\n" || ch === "\r") {
700
- if (ch === "\r") continue;
701
- await debuggerSend(tabId, "Input.dispatchKeyEvent", {
702
- type: "keyDown",
1050
+ if (ch === "\r") continue;
1051
+ if (ch === "\n") {
1052
+ const enterBase = {
703
1053
  key: "Enter",
704
1054
  code: "Enter",
705
1055
  windowsVirtualKeyCode: 13,
706
- });
1056
+ nativeVirtualKeyCode: 13,
1057
+ unmodifiedText: "\r",
1058
+ text: "\r",
1059
+ modifiers: 0,
1060
+ };
707
1061
  await debuggerSend(tabId, "Input.dispatchKeyEvent", {
708
- type: "keyUp",
709
- key: "Enter",
710
- code: "Enter",
711
- windowsVirtualKeyCode: 13,
1062
+ type: "keyDown",
1063
+ ...enterBase,
1064
+ autoRepeat: false,
712
1065
  });
1066
+ await debuggerSend(tabId, "Input.dispatchKeyEvent", { type: "keyUp", ...enterBase });
713
1067
  continue;
714
1068
  }
1069
+ const d = cdpKeyDescriptorForPrintableChar(ch);
1070
+ const payload = {
1071
+ key: d.key,
1072
+ code: d.code,
1073
+ windowsVirtualKeyCode: d.windowsVirtualKeyCode,
1074
+ nativeVirtualKeyCode: d.nativeVirtualKeyCode,
1075
+ unmodifiedText: d.unmodifiedText,
1076
+ text: d.text,
1077
+ modifiers: d.modifiers,
1078
+ };
715
1079
  await debuggerSend(tabId, "Input.dispatchKeyEvent", {
716
1080
  type: "keyDown",
717
- text: ch,
718
- });
719
- await debuggerSend(tabId, "Input.dispatchKeyEvent", {
720
- type: "keyUp",
721
- text: ch,
1081
+ ...payload,
1082
+ autoRepeat: false,
722
1083
  });
1084
+ await debuggerSend(tabId, "Input.dispatchKeyEvent", { type: "keyUp", ...payload });
723
1085
  }
724
1086
  return { success: true, charsTyped: text.length };
725
1087
  } finally {
@@ -256,6 +256,89 @@ function handleResolveClickPoint(message, sendResponse) {
256
256
  sendResponse({ success: true, x, y });
257
257
  }
258
258
 
259
+ /**
260
+ * React / Draft.js-style editors listen for `beforeinput` + `input` (InputEvent) rather than only
261
+ * mutating textContent. Mirror native insertion order: beforeinput → DOM update → input → change.
262
+ * Clears placeholder text and enables Post buttons on Draft.js editors (X.com, LinkedIn, etc.).
263
+ * @param {HTMLElement} el
264
+ * @param {string} text
265
+ * @param {boolean} shouldClear
266
+ */
267
+ function insertTextIntoContentEditable(el, text, shouldClear) {
268
+ /** `focus()` can drop a programmatic selection; preserve caret for insert-at-cursor. */
269
+ let savedRange = null;
270
+ if (!shouldClear) {
271
+ const pre = window.getSelection();
272
+ if (pre && pre.rangeCount > 0) {
273
+ const r = pre.getRangeAt(0);
274
+ if (el.contains(r.commonAncestorContainer)) {
275
+ savedRange = r.cloneRange();
276
+ }
277
+ }
278
+ }
279
+
280
+ el.focus();
281
+ const sel = window.getSelection();
282
+ if (shouldClear) {
283
+ if (sel) {
284
+ const range = document.createRange();
285
+ range.selectNodeContents(el);
286
+ sel.removeAllRanges();
287
+ sel.addRange(range);
288
+ }
289
+ document.execCommand("delete");
290
+ } else if (savedRange && sel) {
291
+ sel.removeAllRanges();
292
+ sel.addRange(savedRange);
293
+ }
294
+
295
+ const evInit = /** @type {InputEventInit} */ ({
296
+ bubbles: true,
297
+ composed: true,
298
+ inputType: "insertText",
299
+ data: text,
300
+ });
301
+ el.dispatchEvent(
302
+ new InputEvent("beforeinput", { ...evInit, cancelable: true })
303
+ );
304
+
305
+ if (shouldClear) {
306
+ el.textContent = text;
307
+ } else if (sel && sel.rangeCount > 0) {
308
+ const range = sel.getRangeAt(0);
309
+ if (el.contains(range.commonAncestorContainer)) {
310
+ const cc = range.commonAncestorContainer;
311
+ if (cc.nodeType === 3) {
312
+ const node = /** @type {Text} */ (cc);
313
+ const offset = range.startOffset;
314
+ const t = node.textContent ?? "";
315
+ const before = t.slice(0, offset);
316
+ const after = t.slice(offset);
317
+ node.textContent = before + text + after;
318
+ range.setStart(node, before.length + text.length);
319
+ range.collapse(true);
320
+ sel.removeAllRanges();
321
+ sel.addRange(range);
322
+ } else {
323
+ range.deleteContents();
324
+ const tn = document.createTextNode(text);
325
+ range.insertNode(tn);
326
+ range.setStartAfter(tn);
327
+ range.collapse(true);
328
+ sel.removeAllRanges();
329
+ sel.addRange(range);
330
+ }
331
+ } else {
332
+ el.textContent = (el.textContent || "") + text;
333
+ }
334
+ } else {
335
+ el.textContent = (el.textContent || "") + text;
336
+ }
337
+
338
+ el.dispatchEvent(new InputEvent("input", { ...evInit, cancelable: false }));
339
+ el.dispatchEvent(new Event("change", { bubbles: true }));
340
+ }
341
+
259
342
  /**
260
343
  * @param {unknown} message
261
344
  * @param {(r: unknown) => void} sendResponse
@@ -278,22 +361,7 @@ function handleTypeText(message, sendResponse) {
278
361
 
279
362
  try {
280
363
  if (el.isContentEditable) {
281
- el.focus();
282
- if (shouldClear) {
283
- const sel = window.getSelection();
284
- if (sel && el.firstChild) {
285
- const range = document.createRange();
286
- range.selectNodeContents(el);
287
- sel.removeAllRanges();
288
- sel.addRange(range);
289
- }
290
- document.execCommand("delete");
291
- el.textContent = text;
292
- } else {
293
- el.textContent = (el.textContent || "") + text;
294
- }
295
- el.dispatchEvent(new InputEvent("input", { bubbles: true, data: text, inputType: "insertText" }));
296
- el.dispatchEvent(new Event("change", { bubbles: true }));
364
+ insertTextIntoContentEditable(el, text, shouldClear);
297
365
  sendResponse({ success: true, charsTyped: text.length });
298
366
  return;
299
367
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "poke-browser",
4
- "version": "0.4.1",
4
+ "version": "0.4.3",
5
5
  "description": "Browser automation bridge for MCP agents via WebSocket.",
6
6
  "permissions": [
7
7
  "tabs",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "poke-browser",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
4
4
  "description": "MCP server + WebSocket bridge for the poke-browser Chrome extension",
5
5
  "type": "module",
6
6
  "engines": {
@@ -62,6 +62,7 @@
62
62
  "@types/express": "^5.0.0",
63
63
  "@types/node": "^22.13.10",
64
64
  "@types/ws": "^8.18.1",
65
+ "jsdom": "^29.0.1",
65
66
  "semantic-release": "^24.2.0",
66
67
  "tsx": "^4.19.3",
67
68
  "typescript": "^5.8.2",