teamshare-bridge 0.26.24 → 0.26.25
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/README.md +383 -383
- package/dist/bridge/install-verify.js +8 -8
- package/dist/bridge/wake-handler.js +5 -4
- package/dist/bridge/wake-handler.js.map +1 -1
- package/dist/lib/agent-scope.js +10 -10
- package/dist/lib/browser/browse-llm.js +44 -44
- package/dist/lib/browser/human-input-guard.js +106 -106
- package/dist/lib/browser/login.js +141 -141
- package/dist/lib/browser/otp.js +72 -72
- package/dist/lib/browser/page-context.js +592 -592
- package/dist/lib/browser/page-events.js +91 -91
- package/dist/lib/browser/refs.js +264 -264
- package/dist/lib/browser/screen-change.js +79 -79
- package/dist/lib/harness/harness-adapter.js +8 -0
- package/dist/lib/harness/harness-adapter.js.map +1 -1
- package/dist/lib/human-style.js +32 -32
- package/dist/lib/scope-guard.js +15 -5
- package/dist/lib/scope-guard.js.map +1 -1
- package/package.json +1 -1
- package/skills/THIRD_PARTY_NOTICES.md +26 -26
- package/skills/architect/LICENSE +21 -21
- package/skills/codebase-design/LICENSE +21 -21
- package/skills/diagnosing-bugs/LICENSE +21 -21
- package/skills/domain-modeling/LICENSE +21 -21
- package/skills/improve-codebase-architecture/LICENSE +21 -21
- package/skills/recover/LICENSE +21 -21
- package/skills/review/LICENSE +21 -21
- package/skills/tdd/LICENSE +21 -21
- package/skills/teamshare-auth-rbac/SKILL.md +78 -78
- package/skills/teamshare-cdd/SKILL.md +215 -215
- package/skills/teamshare-modularity/SKILL.md +146 -146
- package/skills/teamshare-orchestrator-ask/SKILL.md +77 -77
- package/skills/teamshare-scaffold/SKILL.md +116 -116
- package/templates/backend/.env.example.tpl +13 -13
- package/templates/backend/nest-cli.json.tpl +8 -8
- package/templates/backend/package.json.tpl +41 -41
- package/templates/backend/prisma/schema.prisma.tpl +39 -39
- package/templates/backend/src/app.module.ts.tpl +24 -24
- package/templates/backend/src/common/filters/all-exceptions.filter.ts.tpl +59 -59
- package/templates/backend/src/common/guards/jwt-auth.guard.ts.tpl +27 -27
- package/templates/backend/src/common/guards/rbac.guard.ts.tpl +48 -48
- package/templates/backend/src/common/interceptors/transform.interceptor.ts.tpl +44 -44
- package/templates/backend/src/common/pipes/zod-validation.pipe.ts.tpl +29 -29
- package/templates/backend/src/common/swagger/envelope.schemas.ts.tpl +53 -53
- package/templates/backend/src/common/swagger/error.schemas.ts.tpl +62 -62
- package/templates/backend/src/main.ts.tpl +27 -27
- package/templates/backend/src/modules/example/example.controller.ts.tpl +102 -102
- package/templates/backend/src/modules/example/example.dto.ts.tpl +14 -14
- package/templates/backend/src/modules/example/example.module.ts.tpl +10 -10
- package/templates/backend/src/modules/example/example.service.ts.tpl +45 -45
- package/templates/backend/tsconfig.json.tpl +26 -26
- package/templates/common/.gitignore.tpl +42 -42
- package/templates/common/AGENTS.md.tpl +58 -58
- package/templates/common/docs/adr/README.md +25 -25
- package/templates/common/docs/api-contract.md.tpl +72 -72
- package/templates/web/next.config.ts.tpl +7 -7
- package/templates/web/package.json.tpl +37 -37
- package/templates/web/src/app/globals.css.tpl +48 -48
- package/templates/web/src/app/layout.tsx.tpl +25 -25
- package/templates/web/src/components/shared/app-button.tsx.tpl +59 -59
- package/templates/web/src/components/shared/app-empty-state.tsx.tpl +39 -39
- package/templates/web/src/components/shared/app-list-toolbar.tsx.tpl +63 -63
- package/templates/web/src/components/shared/app-skeleton.tsx.tpl +14 -14
- package/templates/web/src/components/shared/app-table.tsx.tpl +128 -128
- package/templates/web/src/components/shared/index.ts.tpl +5 -5
- package/templates/web/src/hooks/use-list-state.ts.tpl +72 -72
- package/templates/web/src/hooks/use-url-tab.ts.tpl +25 -25
- package/templates/web/src/lib/api/client.ts.tpl +123 -123
- package/templates/web/src/lib/providers.tsx.tpl +35 -35
- package/templates/web/src/lib/query-keys.ts.tpl +21 -21
- package/templates/web/src/lib/utils.ts.tpl +7 -7
- package/templates/web/src/lib/validation/schemas.ts.tpl +15 -15
- package/templates/web/src/modules/example/views/example-list-view.tsx.tpl +97 -97
- package/templates/web/tsconfig.json.tpl +23 -23
- package/tools/workspace-arbitration-check.mjs +359 -359
|
@@ -171,12 +171,12 @@ class TypingGuard {
|
|
|
171
171
|
async markOwn(char, key) {
|
|
172
172
|
if (!this.enabled || this.osLevel || this.stoppedFlag)
|
|
173
173
|
return;
|
|
174
|
-
await (0, cdp_1.evaluateJs)(this.page, `(() => {
|
|
175
|
-
const s = window[${JSON.stringify(this.key)}];
|
|
176
|
-
if (!s) return false;
|
|
177
|
-
s.exp = { ch: ${JSON.stringify(char)}, key: ${JSON.stringify(key ?? '')} };
|
|
178
|
-
s.until = performance.now() + ${OS_BRACKET_MS};
|
|
179
|
-
return true;
|
|
174
|
+
await (0, cdp_1.evaluateJs)(this.page, `(() => {
|
|
175
|
+
const s = window[${JSON.stringify(this.key)}];
|
|
176
|
+
if (!s) return false;
|
|
177
|
+
s.exp = { ch: ${JSON.stringify(char)}, key: ${JSON.stringify(key ?? '')} };
|
|
178
|
+
s.until = performance.now() + ${OS_BRACKET_MS};
|
|
179
|
+
return true;
|
|
180
180
|
})()`).catch(() => false);
|
|
181
181
|
}
|
|
182
182
|
async dispose() {
|
|
@@ -184,20 +184,20 @@ class TypingGuard {
|
|
|
184
184
|
return;
|
|
185
185
|
const key = this.key;
|
|
186
186
|
this.key = '';
|
|
187
|
-
await (0, cdp_1.evaluateJs)(this.page, `(() => {
|
|
188
|
-
const s = window[${JSON.stringify(key)}];
|
|
189
|
-
if (s) {
|
|
190
|
-
if (s.el && s.handler) s.el.removeEventListener('keydown', s.handler, true);
|
|
191
|
-
if (s.el && s.handler) s.el.removeEventListener('input', s.handler, true);
|
|
192
|
-
if (document && s.handler) {
|
|
193
|
-
document.removeEventListener('keydown', s.handler, true);
|
|
194
|
-
document.removeEventListener('beforeinput', s.handler, true);
|
|
195
|
-
document.removeEventListener('input', s.handler, true);
|
|
196
|
-
document.removeEventListener('paste', s.handler, true);
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
try { delete window[${JSON.stringify(key)}]; } catch (e) {}
|
|
200
|
-
return true;
|
|
187
|
+
await (0, cdp_1.evaluateJs)(this.page, `(() => {
|
|
188
|
+
const s = window[${JSON.stringify(key)}];
|
|
189
|
+
if (s) {
|
|
190
|
+
if (s.el && s.handler) s.el.removeEventListener('keydown', s.handler, true);
|
|
191
|
+
if (s.el && s.handler) s.el.removeEventListener('input', s.handler, true);
|
|
192
|
+
if (document && s.handler) {
|
|
193
|
+
document.removeEventListener('keydown', s.handler, true);
|
|
194
|
+
document.removeEventListener('beforeinput', s.handler, true);
|
|
195
|
+
document.removeEventListener('input', s.handler, true);
|
|
196
|
+
document.removeEventListener('paste', s.handler, true);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
try { delete window[${JSON.stringify(key)}]; } catch (e) {}
|
|
200
|
+
return true;
|
|
201
201
|
})()`).catch(() => false);
|
|
202
202
|
}
|
|
203
203
|
fail(reason) {
|
|
@@ -228,95 +228,95 @@ class TypingGuard {
|
|
|
228
228
|
buildInstallScript(target, key) {
|
|
229
229
|
const selector = JSON.stringify(target.selector ?? '');
|
|
230
230
|
const isEditor = target.isCodeEditor === true;
|
|
231
|
-
return `(() => {
|
|
232
|
-
const KEY = ${JSON.stringify(key)};
|
|
233
|
-
const findEl = () => {
|
|
234
|
-
const sel = ${selector};
|
|
235
|
-
if (sel) { try { const e = document.querySelector(sel); if (e) return e; } catch (err) {} }
|
|
236
|
-
const a = document.activeElement;
|
|
237
|
-
if (a && a !== document.body) return a;
|
|
238
|
-
return document.body;
|
|
239
|
-
};
|
|
240
|
-
const readText = (el) => {
|
|
241
|
-
try {
|
|
242
|
-
const monacoNs = typeof monaco !== 'undefined' ? monaco : window.monaco;
|
|
243
|
-
const editors = monacoNs && monacoNs.editor && monacoNs.editor.getEditors ? monacoNs.editor.getEditors() : null;
|
|
244
|
-
if (${isEditor} && editors && editors.length) return editors[editors.length - 1].getValue();
|
|
245
|
-
const cm6 = document.querySelector('.cm-content[contenteditable="true"]');
|
|
246
|
-
if (${isEditor} && cm6 && (cm6 === el || cm6.contains(el))) return cm6.innerText || '';
|
|
247
|
-
const cm5 = document.querySelector('.CodeMirror');
|
|
248
|
-
if (${isEditor} && cm5 && cm5.CodeMirror) return cm5.CodeMirror.getValue();
|
|
249
|
-
const ace = document.querySelector('.ace_editor');
|
|
250
|
-
if (${isEditor} && ace && ace.env && ace.env.editor) return ace.env.editor.getValue();
|
|
251
|
-
} catch (err) {}
|
|
252
|
-
if (!el) return '';
|
|
253
|
-
if (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA') return el.value || '';
|
|
254
|
-
return el.innerText || el.textContent || '';
|
|
255
|
-
};
|
|
256
|
-
const el = findEl();
|
|
257
|
-
const state = { ext: false, info: '', exp: null, until: 0, el: el, handler: null };
|
|
258
|
-
const describe = (ev) => {
|
|
259
|
-
const t = ev.target && ev.target.tagName ? ev.target.tagName : '?';
|
|
260
|
-
const k = ev.key || ev.data || ev.inputType || ev.type;
|
|
261
|
-
return ev.type + ':' + t + ':' + String(k).slice(0, 24);
|
|
262
|
-
};
|
|
263
|
-
const handler = (ev) => {
|
|
264
|
-
const s = window[KEY];
|
|
265
|
-
if (!s) return;
|
|
266
|
-
// Only events ON the guarded field (or inside it) count. A keystroke in
|
|
267
|
-
// another field, a tab-switch key or any global shortcut must never stop
|
|
268
|
-
// the stream — that was a real false stop. When the guard fell back to
|
|
269
|
-
// <body> we cannot scope, so we do not filter.
|
|
270
|
-
const t = ev.target;
|
|
271
|
-
const guarded = s.el;
|
|
272
|
-
const scoped = guarded && guarded !== document.body && guarded !== document.documentElement;
|
|
273
|
-
if (scoped && t && t !== guarded && !(guarded.contains && guarded.contains(t))) return;
|
|
274
|
-
const now = performance.now();
|
|
275
|
-
if (s.exp && now <= s.until) {
|
|
276
|
-
const k = ev.key || '';
|
|
277
|
-
const d = ev.data || '';
|
|
278
|
-
if ((s.exp.ch && d === s.exp.ch) || (s.exp.key && k === s.exp.key) || (s.exp.ch && k === s.exp.ch)) {
|
|
279
|
-
s.exp = null;
|
|
280
|
-
return;
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
s.ext = true;
|
|
284
|
-
s.info = describe(ev);
|
|
285
|
-
};
|
|
286
|
-
state.handler = handler;
|
|
287
|
-
Object.defineProperty(window, KEY, { value: state, enumerable: false, configurable: true });
|
|
288
|
-
document.addEventListener('keydown', handler, true);
|
|
289
|
-
document.addEventListener('beforeinput', handler, true);
|
|
290
|
-
document.addEventListener('input', handler, true);
|
|
291
|
-
document.addEventListener('paste', handler, true);
|
|
292
|
-
return { text: readText(el) };
|
|
231
|
+
return `(() => {
|
|
232
|
+
const KEY = ${JSON.stringify(key)};
|
|
233
|
+
const findEl = () => {
|
|
234
|
+
const sel = ${selector};
|
|
235
|
+
if (sel) { try { const e = document.querySelector(sel); if (e) return e; } catch (err) {} }
|
|
236
|
+
const a = document.activeElement;
|
|
237
|
+
if (a && a !== document.body) return a;
|
|
238
|
+
return document.body;
|
|
239
|
+
};
|
|
240
|
+
const readText = (el) => {
|
|
241
|
+
try {
|
|
242
|
+
const monacoNs = typeof monaco !== 'undefined' ? monaco : window.monaco;
|
|
243
|
+
const editors = monacoNs && monacoNs.editor && monacoNs.editor.getEditors ? monacoNs.editor.getEditors() : null;
|
|
244
|
+
if (${isEditor} && editors && editors.length) return editors[editors.length - 1].getValue();
|
|
245
|
+
const cm6 = document.querySelector('.cm-content[contenteditable="true"]');
|
|
246
|
+
if (${isEditor} && cm6 && (cm6 === el || cm6.contains(el))) return cm6.innerText || '';
|
|
247
|
+
const cm5 = document.querySelector('.CodeMirror');
|
|
248
|
+
if (${isEditor} && cm5 && cm5.CodeMirror) return cm5.CodeMirror.getValue();
|
|
249
|
+
const ace = document.querySelector('.ace_editor');
|
|
250
|
+
if (${isEditor} && ace && ace.env && ace.env.editor) return ace.env.editor.getValue();
|
|
251
|
+
} catch (err) {}
|
|
252
|
+
if (!el) return '';
|
|
253
|
+
if (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA') return el.value || '';
|
|
254
|
+
return el.innerText || el.textContent || '';
|
|
255
|
+
};
|
|
256
|
+
const el = findEl();
|
|
257
|
+
const state = { ext: false, info: '', exp: null, until: 0, el: el, handler: null };
|
|
258
|
+
const describe = (ev) => {
|
|
259
|
+
const t = ev.target && ev.target.tagName ? ev.target.tagName : '?';
|
|
260
|
+
const k = ev.key || ev.data || ev.inputType || ev.type;
|
|
261
|
+
return ev.type + ':' + t + ':' + String(k).slice(0, 24);
|
|
262
|
+
};
|
|
263
|
+
const handler = (ev) => {
|
|
264
|
+
const s = window[KEY];
|
|
265
|
+
if (!s) return;
|
|
266
|
+
// Only events ON the guarded field (or inside it) count. A keystroke in
|
|
267
|
+
// another field, a tab-switch key or any global shortcut must never stop
|
|
268
|
+
// the stream — that was a real false stop. When the guard fell back to
|
|
269
|
+
// <body> we cannot scope, so we do not filter.
|
|
270
|
+
const t = ev.target;
|
|
271
|
+
const guarded = s.el;
|
|
272
|
+
const scoped = guarded && guarded !== document.body && guarded !== document.documentElement;
|
|
273
|
+
if (scoped && t && t !== guarded && !(guarded.contains && guarded.contains(t))) return;
|
|
274
|
+
const now = performance.now();
|
|
275
|
+
if (s.exp && now <= s.until) {
|
|
276
|
+
const k = ev.key || '';
|
|
277
|
+
const d = ev.data || '';
|
|
278
|
+
if ((s.exp.ch && d === s.exp.ch) || (s.exp.key && k === s.exp.key) || (s.exp.ch && k === s.exp.ch)) {
|
|
279
|
+
s.exp = null;
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
s.ext = true;
|
|
284
|
+
s.info = describe(ev);
|
|
285
|
+
};
|
|
286
|
+
state.handler = handler;
|
|
287
|
+
Object.defineProperty(window, KEY, { value: state, enumerable: false, configurable: true });
|
|
288
|
+
document.addEventListener('keydown', handler, true);
|
|
289
|
+
document.addEventListener('beforeinput', handler, true);
|
|
290
|
+
document.addEventListener('input', handler, true);
|
|
291
|
+
document.addEventListener('paste', handler, true);
|
|
292
|
+
return { text: readText(el) };
|
|
293
293
|
})()`;
|
|
294
294
|
}
|
|
295
295
|
buildReadScript(key) {
|
|
296
|
-
return `(() => {
|
|
297
|
-
const s = window[${JSON.stringify(key)}];
|
|
298
|
-
if (!s) return null;
|
|
299
|
-
const el = s.el && document.contains(s.el) ? s.el : document.activeElement;
|
|
300
|
-
const read = (e) => {
|
|
301
|
-
try {
|
|
302
|
-
const monacoNs = typeof monaco !== 'undefined' ? monaco : window.monaco;
|
|
303
|
-
const editors = monacoNs && monacoNs.editor && monacoNs.editor.getEditors ? monacoNs.editor.getEditors() : null;
|
|
304
|
-
if (editors && editors.length) return editors[editors.length - 1].getValue();
|
|
305
|
-
const cm6 = document.querySelector('.cm-content[contenteditable="true"]');
|
|
306
|
-
if (cm6) return cm6.innerText || '';
|
|
307
|
-
const cm5 = document.querySelector('.CodeMirror');
|
|
308
|
-
if (cm5 && cm5.CodeMirror) return cm5.CodeMirror.getValue();
|
|
309
|
-
const ace = document.querySelector('.ace_editor');
|
|
310
|
-
if (ace && ace.env && ace.env.editor) return ace.env.editor.getValue();
|
|
311
|
-
} catch (err) {}
|
|
312
|
-
if (!e) return '';
|
|
313
|
-
if (e.tagName === 'INPUT' || e.tagName === 'TEXTAREA') return e.value || '';
|
|
314
|
-
return e.innerText || e.textContent || '';
|
|
315
|
-
};
|
|
316
|
-
const out = { text: read(el), external: s.ext === true, externalInfo: s.info || '' };
|
|
317
|
-
s.ext = false;
|
|
318
|
-
s.info = '';
|
|
319
|
-
return out;
|
|
296
|
+
return `(() => {
|
|
297
|
+
const s = window[${JSON.stringify(key)}];
|
|
298
|
+
if (!s) return null;
|
|
299
|
+
const el = s.el && document.contains(s.el) ? s.el : document.activeElement;
|
|
300
|
+
const read = (e) => {
|
|
301
|
+
try {
|
|
302
|
+
const monacoNs = typeof monaco !== 'undefined' ? monaco : window.monaco;
|
|
303
|
+
const editors = monacoNs && monacoNs.editor && monacoNs.editor.getEditors ? monacoNs.editor.getEditors() : null;
|
|
304
|
+
if (editors && editors.length) return editors[editors.length - 1].getValue();
|
|
305
|
+
const cm6 = document.querySelector('.cm-content[contenteditable="true"]');
|
|
306
|
+
if (cm6) return cm6.innerText || '';
|
|
307
|
+
const cm5 = document.querySelector('.CodeMirror');
|
|
308
|
+
if (cm5 && cm5.CodeMirror) return cm5.CodeMirror.getValue();
|
|
309
|
+
const ace = document.querySelector('.ace_editor');
|
|
310
|
+
if (ace && ace.env && ace.env.editor) return ace.env.editor.getValue();
|
|
311
|
+
} catch (err) {}
|
|
312
|
+
if (!e) return '';
|
|
313
|
+
if (e.tagName === 'INPUT' || e.tagName === 'TEXTAREA') return e.value || '';
|
|
314
|
+
return e.innerText || e.textContent || '';
|
|
315
|
+
};
|
|
316
|
+
const out = { text: read(el), external: s.ext === true, externalInfo: s.info || '' };
|
|
317
|
+
s.ext = false;
|
|
318
|
+
s.info = '';
|
|
319
|
+
return out;
|
|
320
320
|
})()`;
|
|
321
321
|
}
|
|
322
322
|
/**
|
|
@@ -39,102 +39,102 @@ exports.isCredentialFocused = isCredentialFocused;
|
|
|
39
39
|
*/
|
|
40
40
|
const cdp_1 = require("./cdp");
|
|
41
41
|
/** DOM heuristic: locate the login controls, scoped to the topmost modal. */
|
|
42
|
-
exports.FIND_LOGIN_FIELDS_JS = `(() => {
|
|
43
|
-
const vis = (el) => {
|
|
44
|
-
if (!el) return false;
|
|
45
|
-
const r = el.getBoundingClientRect();
|
|
46
|
-
if (r.width === 0 || r.height === 0) return false;
|
|
47
|
-
const cs = getComputedStyle(el);
|
|
48
|
-
return cs.visibility !== 'hidden' && cs.display !== 'none' && cs.opacity !== '0';
|
|
49
|
-
};
|
|
50
|
-
const hintOf = (el) => [el.getAttribute('name'), el.id, el.getAttribute('autocomplete')].filter(Boolean).join('/');
|
|
51
|
-
const lowerType = (el) => (el.type || '').toLowerCase();
|
|
52
|
-
const autocomplete = (el) => (el.getAttribute('autocomplete') || '').toLowerCase();
|
|
53
|
-
|
|
54
|
-
// MODAL SCOPE (2026-10): a login opened in a dialog must win over a footer
|
|
55
|
-
// newsletter input or the page's nav Login button. Prefer the first visible
|
|
56
|
-
// dialog/frame that actually contains a login-ish field; otherwise the last
|
|
57
|
-
// visible dialog; otherwise the document.
|
|
58
|
-
const dialogSel = 'dialog[open],[role="dialog"],[aria-modal="true"],' +
|
|
59
|
-
'[class*="modal"],[class*="Modal"],[class*="dialog"],[class*="Dialog"]';
|
|
60
|
-
const dialogEls = [...document.querySelectorAll(dialogSel)].filter(vis);
|
|
61
|
-
const hasStrongLogin = (d) => !!d.querySelector('input[type="password"],input[autocomplete="username"],input[autocomplete="current-password"],input[autocomplete="new-password"],input[autocomplete="one-time-code"]');
|
|
62
|
-
const hasEmailAndContinue = (d) => {
|
|
63
|
-
if (!d.querySelector('input[type="email"],input[autocomplete="email"]')) return false;
|
|
64
|
-
return [...d.querySelectorAll('button,[role="button"],input[type="submit"]')].some((el) =>
|
|
65
|
-
/continue|next|sign|log\\s?in|verify|submit/i.test((el.innerText || el.textContent || el.value || '').trim()),
|
|
66
|
-
);
|
|
67
|
-
};
|
|
68
|
-
let scope =
|
|
69
|
-
dialogEls.find(hasStrongLogin) ||
|
|
70
|
-
dialogEls.find(hasEmailAndContinue) ||
|
|
71
|
-
dialogEls.find((d) => d.querySelector('input,textarea')) ||
|
|
72
|
-
null;
|
|
73
|
-
if (!scope && dialogEls.length) scope = dialogEls[dialogEls.length - 1];
|
|
74
|
-
const root = scope || document;
|
|
75
|
-
|
|
76
|
-
// Clear the previous scan's tags so a re-rendered modal cannot be addressed
|
|
77
|
-
// through a stale element.
|
|
78
|
-
document.querySelectorAll('[data-ts-login]').forEach((e) => e.removeAttribute('data-ts-login'));
|
|
79
|
-
|
|
80
|
-
const inputs = [...root.querySelectorAll('input,textarea')]
|
|
81
|
-
.filter(vis)
|
|
82
|
-
.filter((el) => lowerType(el) !== 'hidden' && !el.disabled && !el.readOnly);
|
|
83
|
-
|
|
84
|
-
const pw =
|
|
85
|
-
inputs.find((el) => lowerType(el) === 'password') ||
|
|
86
|
-
inputs.find((el) => ['current-password', 'new-password'].includes(autocomplete(el))) ||
|
|
87
|
-
null;
|
|
88
|
-
|
|
89
|
-
let user =
|
|
90
|
-
inputs.find((el) => autocomplete(el) === 'username') ||
|
|
91
|
-
inputs.find((el) => lowerType(el) === 'email') ||
|
|
92
|
-
null;
|
|
93
|
-
if (!user) {
|
|
94
|
-
const textish = inputs.filter((el) => ['', 'text', 'email', 'tel'].includes(lowerType(el)));
|
|
95
|
-
if (pw) {
|
|
96
|
-
user = textish.find((el) => (el.compareDocumentPosition(pw) & Node.DOCUMENT_POSITION_PRECEDING)) || null;
|
|
97
|
-
}
|
|
98
|
-
if (!user && textish.length) user = textish[0];
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const otp =
|
|
102
|
-
inputs.find((el) => autocomplete(el) === 'one-time-code') ||
|
|
103
|
-
inputs.find((el) => /otp|one[- ]?time|verif|code/i.test(hintOf(el) || '')) ||
|
|
104
|
-
null;
|
|
105
|
-
|
|
106
|
-
// The same element can match several heuristics; one role wins.
|
|
107
|
-
if (user && otp && user === otp) user = null;
|
|
108
|
-
if (user && pw && user === pw) user = null;
|
|
109
|
-
|
|
110
|
-
const submitRe = /sign\\s?in|log\\s?in|login|continue|next|submit|log\\s?on|enter|proceed|verify|confirm/i;
|
|
111
|
-
const clickable = [...root.querySelectorAll('button,input[type=submit],input[type=button],[role=button],a')]
|
|
112
|
-
.filter(vis)
|
|
113
|
-
.filter((el) => el.disabled !== true && el.getAttribute('aria-disabled') !== 'true');
|
|
114
|
-
let submit =
|
|
115
|
-
clickable.find((el) => (el.getAttribute('type') || '').toLowerCase() === 'submit') ||
|
|
116
|
-
clickable.find((el) => submitRe.test((el.innerText || el.textContent || el.value || '').trim())) ||
|
|
117
|
-
clickable.find((el) => /submit/i.test(hintOf(el) || '')) ||
|
|
118
|
-
null;
|
|
119
|
-
|
|
120
|
-
const box = (el, hint, role) => {
|
|
121
|
-
el.setAttribute('data-ts-login', role);
|
|
122
|
-
const r = el.getBoundingClientRect();
|
|
123
|
-
return {
|
|
124
|
-
x: Math.round(r.x + r.width / 2),
|
|
125
|
-
y: Math.round(r.y + r.height / 2),
|
|
126
|
-
hint: hint || '',
|
|
127
|
-
role: role,
|
|
128
|
-
};
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
return {
|
|
132
|
-
username: user && vis(user) ? box(user, hintOf(user), 'username') : null,
|
|
133
|
-
password: pw && vis(pw) ? box(pw, hintOf(pw), 'password') : null,
|
|
134
|
-
submit: submit ? box(submit, (submit.innerText || submit.textContent || submit.value || '').trim().slice(0, 40), 'submit') : null,
|
|
135
|
-
otp: otp && vis(otp) ? box(otp, hintOf(otp), 'otp') : null,
|
|
136
|
-
modal: !!scope,
|
|
137
|
-
};
|
|
42
|
+
exports.FIND_LOGIN_FIELDS_JS = `(() => {
|
|
43
|
+
const vis = (el) => {
|
|
44
|
+
if (!el) return false;
|
|
45
|
+
const r = el.getBoundingClientRect();
|
|
46
|
+
if (r.width === 0 || r.height === 0) return false;
|
|
47
|
+
const cs = getComputedStyle(el);
|
|
48
|
+
return cs.visibility !== 'hidden' && cs.display !== 'none' && cs.opacity !== '0';
|
|
49
|
+
};
|
|
50
|
+
const hintOf = (el) => [el.getAttribute('name'), el.id, el.getAttribute('autocomplete')].filter(Boolean).join('/');
|
|
51
|
+
const lowerType = (el) => (el.type || '').toLowerCase();
|
|
52
|
+
const autocomplete = (el) => (el.getAttribute('autocomplete') || '').toLowerCase();
|
|
53
|
+
|
|
54
|
+
// MODAL SCOPE (2026-10): a login opened in a dialog must win over a footer
|
|
55
|
+
// newsletter input or the page's nav Login button. Prefer the first visible
|
|
56
|
+
// dialog/frame that actually contains a login-ish field; otherwise the last
|
|
57
|
+
// visible dialog; otherwise the document.
|
|
58
|
+
const dialogSel = 'dialog[open],[role="dialog"],[aria-modal="true"],' +
|
|
59
|
+
'[class*="modal"],[class*="Modal"],[class*="dialog"],[class*="Dialog"]';
|
|
60
|
+
const dialogEls = [...document.querySelectorAll(dialogSel)].filter(vis);
|
|
61
|
+
const hasStrongLogin = (d) => !!d.querySelector('input[type="password"],input[autocomplete="username"],input[autocomplete="current-password"],input[autocomplete="new-password"],input[autocomplete="one-time-code"]');
|
|
62
|
+
const hasEmailAndContinue = (d) => {
|
|
63
|
+
if (!d.querySelector('input[type="email"],input[autocomplete="email"]')) return false;
|
|
64
|
+
return [...d.querySelectorAll('button,[role="button"],input[type="submit"]')].some((el) =>
|
|
65
|
+
/continue|next|sign|log\\s?in|verify|submit/i.test((el.innerText || el.textContent || el.value || '').trim()),
|
|
66
|
+
);
|
|
67
|
+
};
|
|
68
|
+
let scope =
|
|
69
|
+
dialogEls.find(hasStrongLogin) ||
|
|
70
|
+
dialogEls.find(hasEmailAndContinue) ||
|
|
71
|
+
dialogEls.find((d) => d.querySelector('input,textarea')) ||
|
|
72
|
+
null;
|
|
73
|
+
if (!scope && dialogEls.length) scope = dialogEls[dialogEls.length - 1];
|
|
74
|
+
const root = scope || document;
|
|
75
|
+
|
|
76
|
+
// Clear the previous scan's tags so a re-rendered modal cannot be addressed
|
|
77
|
+
// through a stale element.
|
|
78
|
+
document.querySelectorAll('[data-ts-login]').forEach((e) => e.removeAttribute('data-ts-login'));
|
|
79
|
+
|
|
80
|
+
const inputs = [...root.querySelectorAll('input,textarea')]
|
|
81
|
+
.filter(vis)
|
|
82
|
+
.filter((el) => lowerType(el) !== 'hidden' && !el.disabled && !el.readOnly);
|
|
83
|
+
|
|
84
|
+
const pw =
|
|
85
|
+
inputs.find((el) => lowerType(el) === 'password') ||
|
|
86
|
+
inputs.find((el) => ['current-password', 'new-password'].includes(autocomplete(el))) ||
|
|
87
|
+
null;
|
|
88
|
+
|
|
89
|
+
let user =
|
|
90
|
+
inputs.find((el) => autocomplete(el) === 'username') ||
|
|
91
|
+
inputs.find((el) => lowerType(el) === 'email') ||
|
|
92
|
+
null;
|
|
93
|
+
if (!user) {
|
|
94
|
+
const textish = inputs.filter((el) => ['', 'text', 'email', 'tel'].includes(lowerType(el)));
|
|
95
|
+
if (pw) {
|
|
96
|
+
user = textish.find((el) => (el.compareDocumentPosition(pw) & Node.DOCUMENT_POSITION_PRECEDING)) || null;
|
|
97
|
+
}
|
|
98
|
+
if (!user && textish.length) user = textish[0];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const otp =
|
|
102
|
+
inputs.find((el) => autocomplete(el) === 'one-time-code') ||
|
|
103
|
+
inputs.find((el) => /otp|one[- ]?time|verif|code/i.test(hintOf(el) || '')) ||
|
|
104
|
+
null;
|
|
105
|
+
|
|
106
|
+
// The same element can match several heuristics; one role wins.
|
|
107
|
+
if (user && otp && user === otp) user = null;
|
|
108
|
+
if (user && pw && user === pw) user = null;
|
|
109
|
+
|
|
110
|
+
const submitRe = /sign\\s?in|log\\s?in|login|continue|next|submit|log\\s?on|enter|proceed|verify|confirm/i;
|
|
111
|
+
const clickable = [...root.querySelectorAll('button,input[type=submit],input[type=button],[role=button],a')]
|
|
112
|
+
.filter(vis)
|
|
113
|
+
.filter((el) => el.disabled !== true && el.getAttribute('aria-disabled') !== 'true');
|
|
114
|
+
let submit =
|
|
115
|
+
clickable.find((el) => (el.getAttribute('type') || '').toLowerCase() === 'submit') ||
|
|
116
|
+
clickable.find((el) => submitRe.test((el.innerText || el.textContent || el.value || '').trim())) ||
|
|
117
|
+
clickable.find((el) => /submit/i.test(hintOf(el) || '')) ||
|
|
118
|
+
null;
|
|
119
|
+
|
|
120
|
+
const box = (el, hint, role) => {
|
|
121
|
+
el.setAttribute('data-ts-login', role);
|
|
122
|
+
const r = el.getBoundingClientRect();
|
|
123
|
+
return {
|
|
124
|
+
x: Math.round(r.x + r.width / 2),
|
|
125
|
+
y: Math.round(r.y + r.height / 2),
|
|
126
|
+
hint: hint || '',
|
|
127
|
+
role: role,
|
|
128
|
+
};
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
username: user && vis(user) ? box(user, hintOf(user), 'username') : null,
|
|
133
|
+
password: pw && vis(pw) ? box(pw, hintOf(pw), 'password') : null,
|
|
134
|
+
submit: submit ? box(submit, (submit.innerText || submit.textContent || submit.value || '').trim().slice(0, 40), 'submit') : null,
|
|
135
|
+
otp: otp && vis(otp) ? box(otp, hintOf(otp), 'otp') : null,
|
|
136
|
+
modal: !!scope,
|
|
137
|
+
};
|
|
138
138
|
})()`;
|
|
139
139
|
async function findLoginFields(page) {
|
|
140
140
|
const empty = {
|
|
@@ -223,16 +223,16 @@ async function contentText(page) {
|
|
|
223
223
|
*/
|
|
224
224
|
async function setNativeActiveValue(page, value) {
|
|
225
225
|
try {
|
|
226
|
-
const ok = await (0, cdp_1.evaluateJs)(page, `(() => {
|
|
227
|
-
const a = document.activeElement;
|
|
228
|
-
if (!a || typeof a.value !== 'string') return false;
|
|
229
|
-
const proto = a.tagName === 'TEXTAREA' ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;
|
|
230
|
-
const desc = Object.getOwnPropertyDescriptor(proto, 'value');
|
|
231
|
-
if (!desc || !desc.set) return false;
|
|
232
|
-
desc.set.call(a, ${JSON.stringify(value)});
|
|
233
|
-
a.dispatchEvent(new Event('input', { bubbles: true }));
|
|
234
|
-
a.dispatchEvent(new Event('change', { bubbles: true }));
|
|
235
|
-
return true;
|
|
226
|
+
const ok = await (0, cdp_1.evaluateJs)(page, `(() => {
|
|
227
|
+
const a = document.activeElement;
|
|
228
|
+
if (!a || typeof a.value !== 'string') return false;
|
|
229
|
+
const proto = a.tagName === 'TEXTAREA' ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;
|
|
230
|
+
const desc = Object.getOwnPropertyDescriptor(proto, 'value');
|
|
231
|
+
if (!desc || !desc.set) return false;
|
|
232
|
+
desc.set.call(a, ${JSON.stringify(value)});
|
|
233
|
+
a.dispatchEvent(new Event('input', { bubbles: true }));
|
|
234
|
+
a.dispatchEvent(new Event('change', { bubbles: true }));
|
|
235
|
+
return true;
|
|
236
236
|
})()`);
|
|
237
237
|
return ok === true;
|
|
238
238
|
}
|
|
@@ -311,16 +311,16 @@ async function pressBackspace(page) {
|
|
|
311
311
|
/** Set a tagged input's value through the prototype setter (React-safe). */
|
|
312
312
|
async function setNativeControlValue(page, role, value) {
|
|
313
313
|
try {
|
|
314
|
-
const ok = await (0, cdp_1.evaluateJs)(page, `(() => {
|
|
315
|
-
const el = document.querySelector('[data-ts-login="${role}"]');
|
|
316
|
-
if (!el || typeof el.value !== 'string') return false;
|
|
317
|
-
const proto = el.tagName === 'TEXTAREA' ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;
|
|
318
|
-
const desc = Object.getOwnPropertyDescriptor(proto, 'value');
|
|
319
|
-
if (!desc || !desc.set) return false;
|
|
320
|
-
desc.set.call(el, ${JSON.stringify(value)});
|
|
321
|
-
el.dispatchEvent(new Event('input', { bubbles: true }));
|
|
322
|
-
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
323
|
-
return true;
|
|
314
|
+
const ok = await (0, cdp_1.evaluateJs)(page, `(() => {
|
|
315
|
+
const el = document.querySelector('[data-ts-login="${role}"]');
|
|
316
|
+
if (!el || typeof el.value !== 'string') return false;
|
|
317
|
+
const proto = el.tagName === 'TEXTAREA' ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;
|
|
318
|
+
const desc = Object.getOwnPropertyDescriptor(proto, 'value');
|
|
319
|
+
if (!desc || !desc.set) return false;
|
|
320
|
+
desc.set.call(el, ${JSON.stringify(value)});
|
|
321
|
+
el.dispatchEvent(new Event('input', { bubbles: true }));
|
|
322
|
+
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
323
|
+
return true;
|
|
324
324
|
})()`);
|
|
325
325
|
return ok === true;
|
|
326
326
|
}
|
|
@@ -581,31 +581,31 @@ async function pressEnter(page) {
|
|
|
581
581
|
*/
|
|
582
582
|
async function visibleLoginError(page) {
|
|
583
583
|
try {
|
|
584
|
-
const value = await (0, cdp_1.evaluateJs)(page, `(() => {
|
|
585
|
-
const vis = (el) => {
|
|
586
|
-
if (!el) return false;
|
|
587
|
-
const r = el.getBoundingClientRect();
|
|
588
|
-
if (r.width === 0 || r.height === 0) return false;
|
|
589
|
-
const cs = getComputedStyle(el);
|
|
590
|
-
return cs.visibility !== 'hidden' && cs.display !== 'none';
|
|
591
|
-
};
|
|
592
|
-
const dialogs = [...document.querySelectorAll('dialog[open],[role="dialog"],[aria-modal="true"]')].filter(vis);
|
|
593
|
-
const scope = dialogs.length ? dialogs[dialogs.length - 1] : document;
|
|
594
|
-
const out = [];
|
|
595
|
-
const add = (t) => {
|
|
596
|
-
const text = String(t == null ? '' : t).replace(/\\s+/g, ' ').trim().slice(0, 240);
|
|
597
|
-
if (text.length < 3) return;
|
|
598
|
-
if (/^(error|errors|warning|warnings)$/i.test(text)) return;
|
|
599
|
-
if (!out.includes(text) && out.length < 3) out.push(text);
|
|
600
|
-
};
|
|
601
|
-
for (const el of [...scope.querySelectorAll('[role="alert"],[aria-invalid="true"]')].filter(vis).slice(0, 4)) {
|
|
602
|
-
add(el.innerText || el.textContent || el.getAttribute('aria-label') || '');
|
|
603
|
-
}
|
|
604
|
-
const sels = '[class*="field-error"],[class*="form-error"],[class*="validation-error"],[class*="error-message"],[class*="invalid-feedback"],[class*="error"],[class*="danger"]';
|
|
605
|
-
for (const el of [...scope.querySelectorAll(sels)].filter(vis).slice(0, 12)) {
|
|
606
|
-
add(el.innerText || el.textContent || '');
|
|
607
|
-
}
|
|
608
|
-
return out.join(' | ');
|
|
584
|
+
const value = await (0, cdp_1.evaluateJs)(page, `(() => {
|
|
585
|
+
const vis = (el) => {
|
|
586
|
+
if (!el) return false;
|
|
587
|
+
const r = el.getBoundingClientRect();
|
|
588
|
+
if (r.width === 0 || r.height === 0) return false;
|
|
589
|
+
const cs = getComputedStyle(el);
|
|
590
|
+
return cs.visibility !== 'hidden' && cs.display !== 'none';
|
|
591
|
+
};
|
|
592
|
+
const dialogs = [...document.querySelectorAll('dialog[open],[role="dialog"],[aria-modal="true"]')].filter(vis);
|
|
593
|
+
const scope = dialogs.length ? dialogs[dialogs.length - 1] : document;
|
|
594
|
+
const out = [];
|
|
595
|
+
const add = (t) => {
|
|
596
|
+
const text = String(t == null ? '' : t).replace(/\\s+/g, ' ').trim().slice(0, 240);
|
|
597
|
+
if (text.length < 3) return;
|
|
598
|
+
if (/^(error|errors|warning|warnings)$/i.test(text)) return;
|
|
599
|
+
if (!out.includes(text) && out.length < 3) out.push(text);
|
|
600
|
+
};
|
|
601
|
+
for (const el of [...scope.querySelectorAll('[role="alert"],[aria-invalid="true"]')].filter(vis).slice(0, 4)) {
|
|
602
|
+
add(el.innerText || el.textContent || el.getAttribute('aria-label') || '');
|
|
603
|
+
}
|
|
604
|
+
const sels = '[class*="field-error"],[class*="form-error"],[class*="validation-error"],[class*="error-message"],[class*="invalid-feedback"],[class*="error"],[class*="danger"]';
|
|
605
|
+
for (const el of [...scope.querySelectorAll(sels)].filter(vis).slice(0, 12)) {
|
|
606
|
+
add(el.innerText || el.textContent || '');
|
|
607
|
+
}
|
|
608
|
+
return out.join(' | ');
|
|
609
609
|
})()`);
|
|
610
610
|
return typeof value === 'string' && value.trim() ? value.trim() : null;
|
|
611
611
|
}
|