crewly 1.20.160 → 1.20.161

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.
Files changed (32) hide show
  1. package/dist/backend/backend/src/constants.d.ts +41 -0
  2. package/dist/backend/backend/src/constants.d.ts.map +1 -1
  3. package/dist/backend/backend/src/constants.js +41 -0
  4. package/dist/backend/backend/src/constants.js.map +1 -1
  5. package/dist/backend/backend/src/controllers/browser/browser.controller.d.ts +27 -0
  6. package/dist/backend/backend/src/controllers/browser/browser.controller.d.ts.map +1 -1
  7. package/dist/backend/backend/src/controllers/browser/browser.controller.js +203 -1
  8. package/dist/backend/backend/src/controllers/browser/browser.controller.js.map +1 -1
  9. package/dist/backend/backend/src/controllers/browser/browser.routes.d.ts.map +1 -1
  10. package/dist/backend/backend/src/controllers/browser/browser.routes.js +4 -1
  11. package/dist/backend/backend/src/controllers/browser/browser.routes.js.map +1 -1
  12. package/dist/backend/backend/src/services/browser/browser-session.service.d.ts +15 -0
  13. package/dist/backend/backend/src/services/browser/browser-session.service.d.ts.map +1 -1
  14. package/dist/backend/backend/src/services/browser/browser-session.service.js +25 -0
  15. package/dist/backend/backend/src/services/browser/browser-session.service.js.map +1 -1
  16. package/dist/backend/backend/src/services/browser/owner-browser-input.d.ts +211 -0
  17. package/dist/backend/backend/src/services/browser/owner-browser-input.d.ts.map +1 -0
  18. package/dist/backend/backend/src/services/browser/owner-browser-input.js +373 -0
  19. package/dist/backend/backend/src/services/browser/owner-browser-input.js.map +1 -0
  20. package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.d.ts.map +1 -1
  21. package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.js +5 -3
  22. package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.js.map +1 -1
  23. package/dist/backend/build-info.json +2 -2
  24. package/dist/cli/backend/src/constants.d.ts +41 -0
  25. package/dist/cli/backend/src/constants.d.ts.map +1 -1
  26. package/dist/cli/backend/src/constants.js +41 -0
  27. package/dist/cli/backend/src/constants.js.map +1 -1
  28. package/frontend/dist/assets/index-0707e2ac.css +42 -0
  29. package/frontend/dist/assets/{index-f79676aa.js → index-8f30aa74.js} +265 -265
  30. package/frontend/dist/index.html +2 -2
  31. package/package.json +1 -1
  32. package/frontend/dist/assets/index-edd6f262.css +0 -42
@@ -0,0 +1,211 @@
1
+ /**
2
+ * Owner Browser Input
3
+ *
4
+ * Turns what the owner does to a picture of a page — tap here, type this,
5
+ * press Enter — into the browser operations an agent would use, aimed at the
6
+ * tab of the session the owner has taken over.
7
+ *
8
+ * The owner is usually not at the machine. They are on a phone, looking at a
9
+ * frame the live view captured, and "Take control" used to lock the agent
10
+ * out and then leave them with nothing they could do. This module is the
11
+ * pure half of fixing that: validating input, mapping a tap on the frame to a
12
+ * point on the page, and choosing the operation. Dispatch lives in the browser
13
+ * controller, which already owns transport selection.
14
+ *
15
+ * **Typed text is a password more often than not.** Nothing here logs it,
16
+ * describes it or keeps it; {@link describeOwnerInput} reports only how many
17
+ * characters were typed.
18
+ *
19
+ * @module services/browser/owner-browser-input
20
+ */
21
+ import { BROWSER_OWNER_INPUT_CONSTANTS } from '../../constants.js';
22
+ /** A key the owner can press from the control bar. */
23
+ export type OwnerKey = (typeof BROWSER_OWNER_INPUT_CONSTANTS.KEYS)[number];
24
+ /** One thing the owner did. */
25
+ export type OwnerInput = {
26
+ kind: 'tap';
27
+ /** X in the displayed frame's own pixels */
28
+ x: number;
29
+ /** Y in the displayed frame's own pixels */
30
+ y: number;
31
+ /** Natural width of the frame the owner tapped */
32
+ frameWidth: number;
33
+ /** Natural height of the frame the owner tapped */
34
+ frameHeight: number;
35
+ } | {
36
+ kind: 'type';
37
+ text: string;
38
+ } | {
39
+ kind: 'key';
40
+ key: OwnerKey;
41
+ } | {
42
+ kind: 'scroll';
43
+ dy: number;
44
+ } | {
45
+ kind: 'navigate';
46
+ url: string;
47
+ } | {
48
+ kind: 'back';
49
+ };
50
+ /** A page's visible area in CSS pixels. */
51
+ export interface Viewport {
52
+ width: number;
53
+ height: number;
54
+ }
55
+ /** One browser operation, as the extension names it. */
56
+ export interface OwnerCommand {
57
+ tool: string;
58
+ params: Record<string, unknown>;
59
+ }
60
+ /** Result of {@link parseOwnerInput}. */
61
+ export type ParsedOwnerInput = {
62
+ ok: true;
63
+ input: OwnerInput;
64
+ } | {
65
+ ok: false;
66
+ error: string;
67
+ };
68
+ /**
69
+ * Whether a string is one of the keys the control bar offers.
70
+ *
71
+ * @param key - Candidate key name
72
+ * @returns True when allowed
73
+ */
74
+ export declare function isOwnerKey(key: unknown): key is OwnerKey;
75
+ /**
76
+ * Normalise a URL the owner typed into the address field.
77
+ *
78
+ * A bare host (`login.gov`) gets `https://`. Anything that is not http(s)
79
+ * after that — `javascript:`, `file:`, `chrome:` — is refused: the address
80
+ * field opens pages, it does not run code or read the disk.
81
+ *
82
+ * @param raw - What the owner typed
83
+ * @returns The URL to open, or null when it is not an http(s) URL
84
+ */
85
+ export declare function normalizeOwnerUrl(raw: string): string | null;
86
+ /**
87
+ * Validate a request body into an {@link OwnerInput}.
88
+ *
89
+ * Error messages never quote the body: for `type` it is a password.
90
+ *
91
+ * @param body - Parsed JSON body
92
+ * @returns The input, or a reason it was refused
93
+ *
94
+ * @example
95
+ * ```typescript
96
+ * parseOwnerInput({ kind: 'key', key: 'Enter' }); // { ok: true, input: { kind: 'key', key: 'Enter' } }
97
+ * parseOwnerInput({ kind: 'key', key: 'F5' }); // { ok: false, error: '...' }
98
+ * ```
99
+ */
100
+ export declare function parseOwnerInput(body: unknown): ParsedOwnerInput;
101
+ /**
102
+ * Map a tap on the displayed frame to a point on the page.
103
+ *
104
+ * The frame is a downscaled capture of the visible viewport — at half scale
105
+ * on a 2× display its pixels happen to equal CSS pixels, at 1× they are half
106
+ * of them, and when the extension could not apply the scale they are device
107
+ * pixels. Rather than guess which, the tap is taken as a fraction of the
108
+ * frame and applied to the viewport's measured CSS size, which is the space
109
+ * CDP mouse events use. The frame covers exactly the visual viewport (the
110
+ * extension clips to it), so the fractions line up.
111
+ *
112
+ * @param tap - Where the owner tapped, in frame pixels, and the frame's size
113
+ * @param viewport - The page's visible area in CSS pixels
114
+ * @returns The point to click, in CSS pixels, clamped inside the viewport
115
+ *
116
+ * @example
117
+ * ```typescript
118
+ * // 1× display, half-scale frame: 640×400 frame over a 1280×800 viewport
119
+ * mapTapToViewport({ x: 320, y: 100, frameWidth: 640, frameHeight: 400 }, { width: 1280, height: 800 });
120
+ * // → { x: 640, y: 200 }
121
+ * ```
122
+ */
123
+ export declare function mapTapToViewport(tap: {
124
+ x: number;
125
+ y: number;
126
+ frameWidth: number;
127
+ frameHeight: number;
128
+ }, viewport: Viewport): {
129
+ x: number;
130
+ y: number;
131
+ };
132
+ /**
133
+ * Best guess at the viewport from the frame alone, for when the page could
134
+ * not be measured.
135
+ *
136
+ * The live view captures at `scale` of the CSS viewport, and the capture is
137
+ * in device pixels, so a frame is `css × scale × devicePixelRatio` wide.
138
+ *
139
+ * @param frameWidth - Natural width of the frame
140
+ * @param frameHeight - Natural height of the frame
141
+ * @param devicePixelRatio - DPR the frame was captured at, when known
142
+ * @param scale - The capture's downscale factor
143
+ * @returns The estimated viewport in CSS pixels
144
+ */
145
+ export declare function estimateViewportFromFrame(frameWidth: number, frameHeight: number, devicePixelRatio: number | undefined, scale: number): Viewport;
146
+ /**
147
+ * Page script that measures the visible viewport in CSS pixels.
148
+ *
149
+ * `visualViewport` is what the extension clips its capture to
150
+ * (`cssVisualViewport` in CDP terms); `clientWidth` is the fallback on pages
151
+ * where it is missing.
152
+ */
153
+ export declare const VIEWPORT_PROBE_SCRIPT: string;
154
+ /**
155
+ * Read the result of {@link VIEWPORT_PROBE_SCRIPT} out of an extension reply.
156
+ *
157
+ * @param result - The `result` field of the extension's response
158
+ * @returns The viewport, or null when the reply is not usable
159
+ */
160
+ export declare function parseViewportProbe(result: unknown): Viewport | null;
161
+ /**
162
+ * Page script that presses one key and performs what the key would do.
163
+ *
164
+ * The extension's `pressKey` dispatches synthetic keyboard events, which
165
+ * pages can listen to but which the browser does not act on: Enter does not
166
+ * submit, Backspace does not delete, Tab does not move focus. For an owner
167
+ * trying to sign in, those defaults are the whole point, so this script
168
+ * dispatches the events (so page handlers still run) and then, unless a
169
+ * handler cancelled the key, carries out the default itself. It runs through
170
+ * `executeJs`, which evaluates with a user gesture.
171
+ *
172
+ * Only reaches focus in the top document and same-origin frames; a field
173
+ * inside a cross-origin iframe is out of its reach.
174
+ *
175
+ * @param key - One of the allowed keys
176
+ * @returns JavaScript source to evaluate in the page
177
+ */
178
+ export declare function keyEffectScript(key: OwnerKey): string;
179
+ /** Script for the Back control. */
180
+ export declare const HISTORY_BACK_SCRIPT = "(() => { history.back(); return { back: true }; })()";
181
+ /**
182
+ * Choose the browser operation for one owner input.
183
+ *
184
+ * Typing uses `insertText`, which inserts at the focused element through CDP
185
+ * the way an IME does: the owner taps the field first, exactly as on a real
186
+ * screen, and needs no selector.
187
+ *
188
+ * @param input - What the owner did
189
+ * @param viewport - The page's viewport in CSS pixels; required for `tap`
190
+ * @returns The operation to dispatch
191
+ * @throws When a tap is planned without a viewport
192
+ */
193
+ export declare function planOwnerInput(input: OwnerInput, viewport?: Viewport): OwnerCommand;
194
+ /**
195
+ * Describe an owner input for the session's "last action" line.
196
+ *
197
+ * Never includes typed text — only its length.
198
+ *
199
+ * @param input - What the owner did
200
+ * @returns A short sentence
201
+ */
202
+ export declare function describeOwnerInput(input: OwnerInput): string;
203
+ /**
204
+ * What may be logged about an owner input: its kind, and for text only its
205
+ * length. Kept here so the controller cannot accidentally log the body.
206
+ *
207
+ * @param input - What the owner did
208
+ * @returns Log-safe fields
209
+ */
210
+ export declare function ownerInputLogFields(input: OwnerInput): Record<string, unknown>;
211
+ //# sourceMappingURL=owner-browser-input.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"owner-browser-input.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/browser/owner-browser-input.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,373 @@
1
+ /**
2
+ * Owner Browser Input
3
+ *
4
+ * Turns what the owner does to a picture of a page — tap here, type this,
5
+ * press Enter — into the browser operations an agent would use, aimed at the
6
+ * tab of the session the owner has taken over.
7
+ *
8
+ * The owner is usually not at the machine. They are on a phone, looking at a
9
+ * frame the live view captured, and "Take control" used to lock the agent
10
+ * out and then leave them with nothing they could do. This module is the
11
+ * pure half of fixing that: validating input, mapping a tap on the frame to a
12
+ * point on the page, and choosing the operation. Dispatch lives in the browser
13
+ * controller, which already owns transport selection.
14
+ *
15
+ * **Typed text is a password more often than not.** Nothing here logs it,
16
+ * describes it or keeps it; {@link describeOwnerInput} reports only how many
17
+ * characters were typed.
18
+ *
19
+ * @module services/browser/owner-browser-input
20
+ */
21
+ import { BROWSER_OWNER_INPUT_CONSTANTS } from '../../constants.js';
22
+ /**
23
+ * Whether a value is a finite number.
24
+ *
25
+ * @param v - Anything
26
+ * @returns True for a finite number
27
+ */
28
+ function isFiniteNumber(v) {
29
+ return typeof v === 'number' && Number.isFinite(v);
30
+ }
31
+ /**
32
+ * Whether a string is one of the keys the control bar offers.
33
+ *
34
+ * @param key - Candidate key name
35
+ * @returns True when allowed
36
+ */
37
+ export function isOwnerKey(key) {
38
+ return typeof key === 'string' && BROWSER_OWNER_INPUT_CONSTANTS.KEYS.includes(key);
39
+ }
40
+ /**
41
+ * Normalise a URL the owner typed into the address field.
42
+ *
43
+ * A bare host (`login.gov`) gets `https://`. Anything that is not http(s)
44
+ * after that — `javascript:`, `file:`, `chrome:` — is refused: the address
45
+ * field opens pages, it does not run code or read the disk.
46
+ *
47
+ * @param raw - What the owner typed
48
+ * @returns The URL to open, or null when it is not an http(s) URL
49
+ */
50
+ export function normalizeOwnerUrl(raw) {
51
+ const trimmed = raw.trim();
52
+ if (!trimmed || trimmed.length > BROWSER_OWNER_INPUT_CONSTANTS.MAX_URL_LENGTH)
53
+ return null;
54
+ const withScheme = /^[a-z][a-z0-9+.-]*:/i.test(trimmed) ? trimmed : `https://${trimmed}`;
55
+ try {
56
+ const url = new URL(withScheme);
57
+ if (url.protocol !== 'http:' && url.protocol !== 'https:')
58
+ return null;
59
+ return url.toString();
60
+ }
61
+ catch {
62
+ return null;
63
+ }
64
+ }
65
+ /**
66
+ * Validate a request body into an {@link OwnerInput}.
67
+ *
68
+ * Error messages never quote the body: for `type` it is a password.
69
+ *
70
+ * @param body - Parsed JSON body
71
+ * @returns The input, or a reason it was refused
72
+ *
73
+ * @example
74
+ * ```typescript
75
+ * parseOwnerInput({ kind: 'key', key: 'Enter' }); // { ok: true, input: { kind: 'key', key: 'Enter' } }
76
+ * parseOwnerInput({ kind: 'key', key: 'F5' }); // { ok: false, error: '...' }
77
+ * ```
78
+ */
79
+ export function parseOwnerInput(body) {
80
+ const b = (body ?? {});
81
+ switch (b.kind) {
82
+ case 'tap': {
83
+ const { x, y, frameWidth, frameHeight } = b;
84
+ if (!isFiniteNumber(x) || !isFiniteNumber(y) || !isFiniteNumber(frameWidth) || !isFiniteNumber(frameHeight)) {
85
+ return { ok: false, error: 'tap needs numeric x, y, frameWidth and frameHeight' };
86
+ }
87
+ if (frameWidth <= 0 || frameHeight <= 0) {
88
+ return { ok: false, error: 'frameWidth and frameHeight must be positive' };
89
+ }
90
+ if (x < 0 || y < 0 || x > frameWidth || y > frameHeight) {
91
+ return { ok: false, error: 'tap is outside the frame' };
92
+ }
93
+ return { ok: true, input: { kind: 'tap', x, y, frameWidth, frameHeight } };
94
+ }
95
+ case 'type': {
96
+ if (typeof b.text !== 'string' || b.text.length === 0) {
97
+ return { ok: false, error: 'type needs non-empty text' };
98
+ }
99
+ if (b.text.length > BROWSER_OWNER_INPUT_CONSTANTS.MAX_TEXT_LENGTH) {
100
+ return { ok: false, error: `text is longer than ${BROWSER_OWNER_INPUT_CONSTANTS.MAX_TEXT_LENGTH} characters` };
101
+ }
102
+ return { ok: true, input: { kind: 'type', text: b.text } };
103
+ }
104
+ case 'key': {
105
+ if (!isOwnerKey(b.key)) {
106
+ return { ok: false, error: `key must be one of ${BROWSER_OWNER_INPUT_CONSTANTS.KEYS.join(', ')}` };
107
+ }
108
+ return { ok: true, input: { kind: 'key', key: b.key } };
109
+ }
110
+ case 'scroll': {
111
+ if (!isFiniteNumber(b.dy) || b.dy === 0)
112
+ return { ok: false, error: 'scroll needs a non-zero numeric dy' };
113
+ const max = BROWSER_OWNER_INPUT_CONSTANTS.MAX_SCROLL_PX;
114
+ return { ok: true, input: { kind: 'scroll', dy: Math.max(-max, Math.min(max, Math.round(b.dy))) } };
115
+ }
116
+ case 'navigate': {
117
+ const url = typeof b.url === 'string' ? normalizeOwnerUrl(b.url) : null;
118
+ if (!url)
119
+ return { ok: false, error: 'navigate needs an http(s) URL' };
120
+ return { ok: true, input: { kind: 'navigate', url } };
121
+ }
122
+ case 'back':
123
+ return { ok: true, input: { kind: 'back' } };
124
+ default:
125
+ return { ok: false, error: 'kind must be one of tap, type, key, scroll, navigate, back' };
126
+ }
127
+ }
128
+ /**
129
+ * Map a tap on the displayed frame to a point on the page.
130
+ *
131
+ * The frame is a downscaled capture of the visible viewport — at half scale
132
+ * on a 2× display its pixels happen to equal CSS pixels, at 1× they are half
133
+ * of them, and when the extension could not apply the scale they are device
134
+ * pixels. Rather than guess which, the tap is taken as a fraction of the
135
+ * frame and applied to the viewport's measured CSS size, which is the space
136
+ * CDP mouse events use. The frame covers exactly the visual viewport (the
137
+ * extension clips to it), so the fractions line up.
138
+ *
139
+ * @param tap - Where the owner tapped, in frame pixels, and the frame's size
140
+ * @param viewport - The page's visible area in CSS pixels
141
+ * @returns The point to click, in CSS pixels, clamped inside the viewport
142
+ *
143
+ * @example
144
+ * ```typescript
145
+ * // 1× display, half-scale frame: 640×400 frame over a 1280×800 viewport
146
+ * mapTapToViewport({ x: 320, y: 100, frameWidth: 640, frameHeight: 400 }, { width: 1280, height: 800 });
147
+ * // → { x: 640, y: 200 }
148
+ * ```
149
+ */
150
+ export function mapTapToViewport(tap, viewport) {
151
+ const fx = Math.max(0, Math.min(1, tap.x / tap.frameWidth));
152
+ const fy = Math.max(0, Math.min(1, tap.y / tap.frameHeight));
153
+ // Keep the point strictly inside, so a tap on the very edge still lands on
154
+ // the page rather than on the boundary pixel outside it.
155
+ const x = Math.min(viewport.width - 1, Math.max(0, Math.round(fx * viewport.width)));
156
+ const y = Math.min(viewport.height - 1, Math.max(0, Math.round(fy * viewport.height)));
157
+ return { x, y };
158
+ }
159
+ /**
160
+ * Best guess at the viewport from the frame alone, for when the page could
161
+ * not be measured.
162
+ *
163
+ * The live view captures at `scale` of the CSS viewport, and the capture is
164
+ * in device pixels, so a frame is `css × scale × devicePixelRatio` wide.
165
+ *
166
+ * @param frameWidth - Natural width of the frame
167
+ * @param frameHeight - Natural height of the frame
168
+ * @param devicePixelRatio - DPR the frame was captured at, when known
169
+ * @param scale - The capture's downscale factor
170
+ * @returns The estimated viewport in CSS pixels
171
+ */
172
+ export function estimateViewportFromFrame(frameWidth, frameHeight, devicePixelRatio, scale) {
173
+ const dpr = devicePixelRatio && devicePixelRatio > 0 ? devicePixelRatio : 1;
174
+ const factor = scale > 0 ? scale * dpr : dpr;
175
+ return { width: Math.round(frameWidth / factor), height: Math.round(frameHeight / factor) };
176
+ }
177
+ /**
178
+ * Page script that measures the visible viewport in CSS pixels.
179
+ *
180
+ * `visualViewport` is what the extension clips its capture to
181
+ * (`cssVisualViewport` in CDP terms); `clientWidth` is the fallback on pages
182
+ * where it is missing.
183
+ */
184
+ export const VIEWPORT_PROBE_SCRIPT = '(() => { const v = window.visualViewport; const d = document.documentElement; ' +
185
+ 'return { width: (v && v.width) || d.clientWidth || window.innerWidth, ' +
186
+ 'height: (v && v.height) || d.clientHeight || window.innerHeight }; })()';
187
+ /**
188
+ * Read the result of {@link VIEWPORT_PROBE_SCRIPT} out of an extension reply.
189
+ *
190
+ * @param result - The `result` field of the extension's response
191
+ * @returns The viewport, or null when the reply is not usable
192
+ */
193
+ export function parseViewportProbe(result) {
194
+ const value = result?.value;
195
+ const width = value?.width;
196
+ const height = value?.height;
197
+ if (!isFiniteNumber(width) || !isFiniteNumber(height) || width <= 0 || height <= 0)
198
+ return null;
199
+ return { width, height };
200
+ }
201
+ /**
202
+ * Page script that presses one key and performs what the key would do.
203
+ *
204
+ * The extension's `pressKey` dispatches synthetic keyboard events, which
205
+ * pages can listen to but which the browser does not act on: Enter does not
206
+ * submit, Backspace does not delete, Tab does not move focus. For an owner
207
+ * trying to sign in, those defaults are the whole point, so this script
208
+ * dispatches the events (so page handlers still run) and then, unless a
209
+ * handler cancelled the key, carries out the default itself. It runs through
210
+ * `executeJs`, which evaluates with a user gesture.
211
+ *
212
+ * Only reaches focus in the top document and same-origin frames; a field
213
+ * inside a cross-origin iframe is out of its reach.
214
+ *
215
+ * @param key - One of the allowed keys
216
+ * @returns JavaScript source to evaluate in the page
217
+ */
218
+ export function keyEffectScript(key) {
219
+ const arrowPx = BROWSER_OWNER_INPUT_CONSTANTS.ARROW_SCROLL_PX;
220
+ return `(() => {
221
+ const key = ${JSON.stringify(key)};
222
+ let el = document.activeElement;
223
+ while (el && (el.tagName === 'IFRAME' || el.tagName === 'FRAME')) {
224
+ let inner = null;
225
+ try { inner = el.contentDocument && el.contentDocument.activeElement; } catch (e) { inner = null; }
226
+ if (!inner) break;
227
+ el = inner;
228
+ }
229
+ const target = el || document.body;
230
+ const doc = target.ownerDocument || document;
231
+ const win = doc.defaultView || window;
232
+ const tag = (target.tagName || '').toUpperCase();
233
+ const editable = tag === 'TEXTAREA' || target.isContentEditable ||
234
+ (tag === 'INPUT' && !/^(button|submit|reset|checkbox|radio|file|image|range|color)$/i.test(target.type || ''));
235
+ const opts = { key, code: key, bubbles: true, cancelable: true, composed: true };
236
+ const proceed = target.dispatchEvent(new win.KeyboardEvent('keydown', opts));
237
+ let effect = 'none';
238
+ if (proceed) {
239
+ if (key === 'Enter') {
240
+ target.dispatchEvent(new win.KeyboardEvent('keypress', opts));
241
+ const buttonLike = tag === 'BUTTON' || tag === 'A' || target.getAttribute('role') === 'button' ||
242
+ (tag === 'INPUT' && /^(submit|button|image|reset)$/i.test(target.type || ''));
243
+ if (tag === 'TEXTAREA' || target.isContentEditable) {
244
+ doc.execCommand('insertLineBreak'); effect = 'newline';
245
+ } else if (buttonLike) {
246
+ target.click(); effect = 'clicked';
247
+ } else if (tag === 'INPUT' && target.form) {
248
+ const f = target.form;
249
+ if (typeof f.requestSubmit === 'function') f.requestSubmit(); else f.submit();
250
+ effect = 'submitted';
251
+ }
252
+ } else if (key === 'Backspace') {
253
+ if (editable) { doc.execCommand('delete'); effect = 'deleted'; }
254
+ } else if (key === 'Tab') {
255
+ const sel = 'a[href],button:not([disabled]),input:not([disabled]):not([type=hidden]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"]),[contenteditable="true"]';
256
+ const all = Array.prototype.filter.call(doc.querySelectorAll(sel), (n) => {
257
+ const r = n.getBoundingClientRect(); return r.width > 0 && r.height > 0;
258
+ });
259
+ if (all.length > 0) {
260
+ const i = all.indexOf(target);
261
+ const next = all[(i + 1) % all.length];
262
+ next.focus(); effect = 'focused';
263
+ }
264
+ } else if (key === 'ArrowUp' || key === 'ArrowDown') {
265
+ if (!editable) { win.scrollBy(0, key === 'ArrowUp' ? -${arrowPx} : ${arrowPx}); effect = 'scrolled'; }
266
+ }
267
+ }
268
+ target.dispatchEvent(new win.KeyboardEvent('keyup', opts));
269
+ return { key, effect, target: tag };
270
+ })()`;
271
+ }
272
+ /** Script for the Back control. */
273
+ export const HISTORY_BACK_SCRIPT = '(() => { history.back(); return { back: true }; })()';
274
+ /**
275
+ * Choose the browser operation for one owner input.
276
+ *
277
+ * Typing uses `insertText`, which inserts at the focused element through CDP
278
+ * the way an IME does: the owner taps the field first, exactly as on a real
279
+ * screen, and needs no selector.
280
+ *
281
+ * @param input - What the owner did
282
+ * @param viewport - The page's viewport in CSS pixels; required for `tap`
283
+ * @returns The operation to dispatch
284
+ * @throws When a tap is planned without a viewport
285
+ */
286
+ export function planOwnerInput(input, viewport) {
287
+ switch (input.kind) {
288
+ case 'tap': {
289
+ if (!viewport)
290
+ throw new Error('A tap needs the page viewport');
291
+ const point = mapTapToViewport(input, viewport);
292
+ return {
293
+ tool: 'click',
294
+ params: {
295
+ x: point.x,
296
+ y: point.y,
297
+ reactIdleQuietMs: BROWSER_OWNER_INPUT_CONSTANTS.TAP_IDLE_QUIET_MS,
298
+ reactIdleMaxWaitMs: BROWSER_OWNER_INPUT_CONSTANTS.TAP_IDLE_MAX_WAIT_MS,
299
+ },
300
+ };
301
+ }
302
+ case 'type':
303
+ return { tool: 'insertText', params: { text: input.text } };
304
+ case 'key':
305
+ return { tool: 'executeJs', params: { code: keyEffectScript(input.key) } };
306
+ case 'scroll':
307
+ return { tool: 'scroll', params: { x: 0, y: input.dy } };
308
+ case 'navigate':
309
+ return { tool: 'navigate', params: { url: input.url } };
310
+ case 'back':
311
+ return { tool: 'executeJs', params: { code: HISTORY_BACK_SCRIPT } };
312
+ }
313
+ }
314
+ /**
315
+ * Describe an owner input for the session's "last action" line.
316
+ *
317
+ * Never includes typed text — only its length.
318
+ *
319
+ * @param input - What the owner did
320
+ * @returns A short sentence
321
+ */
322
+ export function describeOwnerInput(input) {
323
+ switch (input.kind) {
324
+ case 'tap':
325
+ return 'You tapped the page';
326
+ case 'type':
327
+ return `You typed ${input.text.length} character${input.text.length === 1 ? '' : 's'}`;
328
+ case 'key':
329
+ return `You pressed ${input.key}`;
330
+ case 'scroll':
331
+ return input.dy < 0 ? 'You scrolled up' : 'You scrolled down';
332
+ case 'navigate': {
333
+ let host = '';
334
+ try {
335
+ host = new URL(input.url).host;
336
+ }
337
+ catch {
338
+ host = '';
339
+ }
340
+ return host ? `You opened ${host}` : 'You opened a page';
341
+ }
342
+ case 'back':
343
+ return 'You went back';
344
+ }
345
+ }
346
+ /**
347
+ * What may be logged about an owner input: its kind, and for text only its
348
+ * length. Kept here so the controller cannot accidentally log the body.
349
+ *
350
+ * @param input - What the owner did
351
+ * @returns Log-safe fields
352
+ */
353
+ export function ownerInputLogFields(input) {
354
+ switch (input.kind) {
355
+ case 'type':
356
+ return { kind: 'type', length: input.text.length };
357
+ case 'key':
358
+ return { kind: 'key', key: input.key };
359
+ case 'navigate': {
360
+ let host = '';
361
+ try {
362
+ host = new URL(input.url).host;
363
+ }
364
+ catch {
365
+ host = '';
366
+ }
367
+ return { kind: 'navigate', host };
368
+ }
369
+ default:
370
+ return { kind: input.kind };
371
+ }
372
+ }
373
+ //# sourceMappingURL=owner-browser-input.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"owner-browser-input.js","sourceRoot":"","sources":["../../../../../../backend/src/services/browser/owner-browser-input.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"mobile-api-relay.service.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/cloud/mobile-api-relay.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAGH,OAAO,EAAiB,KAAK,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAShF,yEAAyE;AACzE,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,KAAK,GAAG,MAAM,CAAC;IACvB,oEAAoE;IACpE,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,iCAAiC;AACjC,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,OAAO,CAAC;CACf;AAED,gFAAgF;AAChF,MAAM,WAAW,qBAAqB;IACpC,EAAE,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,GAAG,EAAE,0BAA0B,KAAK,IAAI,GAAG,IAAI,CAAC;IAC/E,GAAG,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,GAAG,EAAE,0BAA0B,KAAK,IAAI,GAAG,IAAI,CAAC;IAChF,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAClF;;;;OAIG;IACH,iBAAiB,CAAC,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC3F;AAED,4DAA4D;AAC5D,MAAM,WAAW,0BAA0B;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAMD;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,EAAE,aAAa,CAAC;IAAE,MAAM,EAAE,KAAK,GAAG,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CA2G1F,CAAC;AAgBF;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAI5E;AAMD,2BAA2B;AAC3B,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,qBAAqB,CAAC;IACjC,0DAA0D;IAC1D,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,eAAe,CAAC;IACzB,gCAAgC;IAChC,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B;AAED;;;;GAIG;AACH,qBAAa,qBAAqB;IAChC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAwB;IAClD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;IACzC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAe;IACzC,OAAO,CAAC,OAAO,CAA4D;gBAE/D,OAAO,EAAE,qBAAqB;IAQ1C,6CAA6C;IAC7C,KAAK,IAAI,IAAI;IAab,uDAAuD;IACvD,IAAI,IAAI,IAAI;IAOZ;;;;;;OAMG;YACW,aAAa;CAuE5B"}
1
+ {"version":3,"file":"mobile-api-relay.service.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/cloud/mobile-api-relay.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAGH,OAAO,EAAiB,KAAK,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAShF,yEAAyE;AACzE,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,KAAK,GAAG,MAAM,CAAC;IACvB,oEAAoE;IACpE,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,iCAAiC;AACjC,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,OAAO,CAAC;CACf;AAED,gFAAgF;AAChF,MAAM,WAAW,qBAAqB;IACpC,EAAE,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,GAAG,EAAE,0BAA0B,KAAK,IAAI,GAAG,IAAI,CAAC;IAC/E,GAAG,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,GAAG,EAAE,0BAA0B,KAAK,IAAI,GAAG,IAAI,CAAC;IAChF,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAClF;;;;OAIG;IACH,iBAAiB,CAAC,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC3F;AAED,4DAA4D;AAC5D,MAAM,WAAW,0BAA0B;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAMD;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,EAAE,aAAa,CAAC;IAAE,MAAM,EAAE,KAAK,GAAG,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CA6G1F,CAAC;AAgBF;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAI5E;AAMD,2BAA2B;AAC3B,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,qBAAqB,CAAC;IACjC,0DAA0D;IAC1D,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,eAAe,CAAC;IACzB,gCAAgC;IAChC,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B;AAED;;;;GAIG;AACH,qBAAa,qBAAqB;IAChC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAwB;IAClD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;IACzC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAe;IACzC,OAAO,CAAC,OAAO,CAA4D;gBAE/D,OAAO,EAAE,qBAAqB;IAQ1C,6CAA6C;IAC7C,KAAK,IAAI,IAAI;IAab,uDAAuD;IACvD,IAAI,IAAI,IAAI;IAOZ;;;;;;OAMG;YACW,aAAa;CAuE5B"}
@@ -61,9 +61,11 @@ export const MOBILE_API_ALLOWLIST = [
61
61
  // Live browser view. Reads let an owner who is not at this machine see
62
62
  // what an agent is doing in a browser; the writes under /browser/sessions
63
63
  // are the owner-side controls only — stop, take the wheel, give it back,
64
- // answer a held action. The driving endpoints (/browser/navigate,
65
- // /browser/click, …) are deliberately NOT here: those are how an agent
66
- // acts, and nothing on the internet should be able to act as one.
64
+ // answer a held action, and `/browser/sessions/:id/input` (the owner's own
65
+ // taps and typing, accepted only while the owner has taken the wheel). The
66
+ // agent driving endpoints (/browser/navigate, /browser/click, …) are
67
+ // deliberately NOT here: those are how an agent acts, and nothing on the
68
+ // internet should be able to act as one.
67
69
  { method: 'GET', prefix: '/browser/sessions' },
68
70
  { method: 'POST', prefix: '/browser/sessions' },
69
71
  // "Back up now" from the portal: start a cloud backup of this machine and