teamshare-bridge 0.26.23 → 0.26.24
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/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/human-style.js +32 -32
- package/dist/lib/llm/usage-reader.js +4 -4
- package/dist/lib/orchestrator/worktree.js +118 -50
- package/dist/lib/orchestrator/worktree.js.map +1 -1
- package/dist/lib/workspace/mirror.d.ts +13 -0
- package/dist/lib/workspace/mirror.js +200 -43
- package/dist/lib/workspace/mirror.js.map +1 -1
- package/dist/lib/workspace/workspace-audit.d.ts +2 -2
- package/dist/lib/workspace/workspace-audit.js +27 -17
- package/dist/lib/workspace/workspace-audit.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
- package/dist/lib/git/ensure-excludes.d.ts +0 -5
- package/dist/lib/git/ensure-excludes.js +0 -55
- package/dist/lib/git/ensure-excludes.js.map +0 -1
|
@@ -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
|
}
|
package/dist/lib/browser/otp.js
CHANGED
|
@@ -18,57 +18,57 @@ exports.fillOtpGroup = fillOtpGroup;
|
|
|
18
18
|
* and only submits when the widget did NOT auto-submit itself.
|
|
19
19
|
*/
|
|
20
20
|
const cdp_1 = require("./cdp");
|
|
21
|
-
const FILL_OTP_JS = `(() => {
|
|
22
|
-
const vis = (el) => {
|
|
23
|
-
if (!el) return false;
|
|
24
|
-
const r = el.getBoundingClientRect();
|
|
25
|
-
if (r.width === 0 || r.height === 0) return false;
|
|
26
|
-
const cs = getComputedStyle(el);
|
|
27
|
-
return cs.visibility !== 'hidden' && cs.display !== 'none' && cs.opacity !== '0';
|
|
28
|
-
};
|
|
29
|
-
const dialogSel = 'dialog[open],[role="dialog"],[aria-modal="true"],' +
|
|
30
|
-
'[class*="modal"],[class*="Modal"],[class*="dialog"],[class*="Dialog"]';
|
|
31
|
-
const dialogs = [...document.querySelectorAll(dialogSel)].filter(vis);
|
|
32
|
-
const scope = dialogs.length ? dialogs[dialogs.length - 1] : document;
|
|
33
|
-
const inputs = [...scope.querySelectorAll('input')]
|
|
34
|
-
.filter(vis)
|
|
35
|
-
.filter((el) => !el.disabled && !el.readOnly && (el.type || '').toLowerCase() !== 'hidden');
|
|
36
|
-
const single = inputs.filter((el) => {
|
|
37
|
-
const ml = el.getAttribute('maxlength') ?? el.getAttribute('maxLength');
|
|
38
|
-
const n = ml === null || ml === undefined ? NaN : Number(ml);
|
|
39
|
-
return n === 1;
|
|
40
|
-
});
|
|
41
|
-
// A segmented OTP widget: at least 4 single-char inputs, or an explicit
|
|
42
|
-
// one-time-code input with siblings of the same shape.
|
|
43
|
-
let group = [];
|
|
44
|
-
if (single.length >= 4) {
|
|
45
|
-
// Keep the largest run of adjacent single-char inputs in DOM order.
|
|
46
|
-
group = single;
|
|
47
|
-
} else {
|
|
48
|
-
const otpish = inputs.filter((el) => {
|
|
49
|
-
const ac = (el.getAttribute('autocomplete') || '').toLowerCase();
|
|
50
|
-
if (ac === 'one-time-code') return true;
|
|
51
|
-
const hint = [el.getAttribute('name'), el.id, el.getAttribute('aria-label')].filter(Boolean).join(' ');
|
|
52
|
-
return /otp|one[- ]?time|verif|code/i.test(hint);
|
|
53
|
-
});
|
|
54
|
-
if (otpish.length >= 4) group = otpish;
|
|
55
|
-
else if (otpish.length === 1 && single.length >= 2) group = [otpish[0], ...single.filter((el) => el !== otpish[0])];
|
|
56
|
-
}
|
|
57
|
-
if (group.length < 4) return null;
|
|
58
|
-
// Sort by on-screen position (top then left) so auto-advance order is right.
|
|
59
|
-
group = group.slice().sort((a, b) => {
|
|
60
|
-
const ra = a.getBoundingClientRect();
|
|
61
|
-
const rb = b.getBoundingClientRect();
|
|
62
|
-
if (Math.abs(ra.y - rb.y) > ra.height / 2) return ra.y - rb.y;
|
|
63
|
-
return ra.x - rb.x;
|
|
64
|
-
});
|
|
65
|
-
group.forEach((el, i) => el.setAttribute('data-ts-otp', String(i)));
|
|
66
|
-
return {
|
|
67
|
-
length: group.length,
|
|
68
|
-
tags: group.map((el) => String(el.tagName || '').toLowerCase()),
|
|
69
|
-
modal: scope !== document,
|
|
70
|
-
values: group.map((el) => typeof el.value === 'string' ? el.value : ''),
|
|
71
|
-
};
|
|
21
|
+
const FILL_OTP_JS = `(() => {
|
|
22
|
+
const vis = (el) => {
|
|
23
|
+
if (!el) return false;
|
|
24
|
+
const r = el.getBoundingClientRect();
|
|
25
|
+
if (r.width === 0 || r.height === 0) return false;
|
|
26
|
+
const cs = getComputedStyle(el);
|
|
27
|
+
return cs.visibility !== 'hidden' && cs.display !== 'none' && cs.opacity !== '0';
|
|
28
|
+
};
|
|
29
|
+
const dialogSel = 'dialog[open],[role="dialog"],[aria-modal="true"],' +
|
|
30
|
+
'[class*="modal"],[class*="Modal"],[class*="dialog"],[class*="Dialog"]';
|
|
31
|
+
const dialogs = [...document.querySelectorAll(dialogSel)].filter(vis);
|
|
32
|
+
const scope = dialogs.length ? dialogs[dialogs.length - 1] : document;
|
|
33
|
+
const inputs = [...scope.querySelectorAll('input')]
|
|
34
|
+
.filter(vis)
|
|
35
|
+
.filter((el) => !el.disabled && !el.readOnly && (el.type || '').toLowerCase() !== 'hidden');
|
|
36
|
+
const single = inputs.filter((el) => {
|
|
37
|
+
const ml = el.getAttribute('maxlength') ?? el.getAttribute('maxLength');
|
|
38
|
+
const n = ml === null || ml === undefined ? NaN : Number(ml);
|
|
39
|
+
return n === 1;
|
|
40
|
+
});
|
|
41
|
+
// A segmented OTP widget: at least 4 single-char inputs, or an explicit
|
|
42
|
+
// one-time-code input with siblings of the same shape.
|
|
43
|
+
let group = [];
|
|
44
|
+
if (single.length >= 4) {
|
|
45
|
+
// Keep the largest run of adjacent single-char inputs in DOM order.
|
|
46
|
+
group = single;
|
|
47
|
+
} else {
|
|
48
|
+
const otpish = inputs.filter((el) => {
|
|
49
|
+
const ac = (el.getAttribute('autocomplete') || '').toLowerCase();
|
|
50
|
+
if (ac === 'one-time-code') return true;
|
|
51
|
+
const hint = [el.getAttribute('name'), el.id, el.getAttribute('aria-label')].filter(Boolean).join(' ');
|
|
52
|
+
return /otp|one[- ]?time|verif|code/i.test(hint);
|
|
53
|
+
});
|
|
54
|
+
if (otpish.length >= 4) group = otpish;
|
|
55
|
+
else if (otpish.length === 1 && single.length >= 2) group = [otpish[0], ...single.filter((el) => el !== otpish[0])];
|
|
56
|
+
}
|
|
57
|
+
if (group.length < 4) return null;
|
|
58
|
+
// Sort by on-screen position (top then left) so auto-advance order is right.
|
|
59
|
+
group = group.slice().sort((a, b) => {
|
|
60
|
+
const ra = a.getBoundingClientRect();
|
|
61
|
+
const rb = b.getBoundingClientRect();
|
|
62
|
+
if (Math.abs(ra.y - rb.y) > ra.height / 2) return ra.y - rb.y;
|
|
63
|
+
return ra.x - rb.x;
|
|
64
|
+
});
|
|
65
|
+
group.forEach((el, i) => el.setAttribute('data-ts-otp', String(i)));
|
|
66
|
+
return {
|
|
67
|
+
length: group.length,
|
|
68
|
+
tags: group.map((el) => String(el.tagName || '').toLowerCase()),
|
|
69
|
+
modal: scope !== document,
|
|
70
|
+
values: group.map((el) => typeof el.value === 'string' ? el.value : ''),
|
|
71
|
+
};
|
|
72
72
|
})()`;
|
|
73
73
|
async function detectOtpGroup(page) {
|
|
74
74
|
try {
|
|
@@ -153,15 +153,15 @@ async function typeOtpDigit(page, digit) {
|
|
|
153
153
|
}
|
|
154
154
|
async function visibleOtpError(page) {
|
|
155
155
|
try {
|
|
156
|
-
const v = await (0, cdp_1.evaluateJs)(page, `(() => {
|
|
157
|
-
const sel = '[role="alert"],.error,.text-red-500,.text-destructive,[class*="error" i]';
|
|
158
|
-
const els = [...document.querySelectorAll(sel)].filter((el) => {
|
|
159
|
-
const r = el.getBoundingClientRect();
|
|
160
|
-
if (r.width === 0 || r.height === 0) return false;
|
|
161
|
-
const t = (el.innerText || '').trim();
|
|
162
|
-
return t.length > 0 && t.length < 300;
|
|
163
|
-
});
|
|
164
|
-
return els.length ? els[0].innerText.trim().slice(0, 300) : null;
|
|
156
|
+
const v = await (0, cdp_1.evaluateJs)(page, `(() => {
|
|
157
|
+
const sel = '[role="alert"],.error,.text-red-500,.text-destructive,[class*="error" i]';
|
|
158
|
+
const els = [...document.querySelectorAll(sel)].filter((el) => {
|
|
159
|
+
const r = el.getBoundingClientRect();
|
|
160
|
+
if (r.width === 0 || r.height === 0) return false;
|
|
161
|
+
const t = (el.innerText || '').trim();
|
|
162
|
+
return t.length > 0 && t.length < 300;
|
|
163
|
+
});
|
|
164
|
+
return els.length ? els[0].innerText.trim().slice(0, 300) : null;
|
|
165
165
|
})()`);
|
|
166
166
|
return typeof v === 'string' && v ? v : null;
|
|
167
167
|
}
|
|
@@ -171,18 +171,18 @@ async function visibleOtpError(page) {
|
|
|
171
171
|
}
|
|
172
172
|
async function clickOtpSubmit(page) {
|
|
173
173
|
try {
|
|
174
|
-
const clicked = await (0, cdp_1.evaluateJs)(page, `(() => {
|
|
175
|
-
const scope = document;
|
|
176
|
-
const btns = [...scope.querySelectorAll('button,input[type="submit"],[role="button"]')].filter((el) => {
|
|
177
|
-
const r = el.getBoundingClientRect();
|
|
178
|
-
if (r.width === 0 || r.height === 0) return false;
|
|
179
|
-
if (el.disabled) return false;
|
|
180
|
-
const t = ((el.innerText || el.textContent || el.value || '')).trim();
|
|
181
|
-
return /verify|confirm|continue|submit|next/i.test(t);
|
|
182
|
-
});
|
|
183
|
-
if (!btns.length) return false;
|
|
184
|
-
btns[0].click();
|
|
185
|
-
return true;
|
|
174
|
+
const clicked = await (0, cdp_1.evaluateJs)(page, `(() => {
|
|
175
|
+
const scope = document;
|
|
176
|
+
const btns = [...scope.querySelectorAll('button,input[type="submit"],[role="button"]')].filter((el) => {
|
|
177
|
+
const r = el.getBoundingClientRect();
|
|
178
|
+
if (r.width === 0 || r.height === 0) return false;
|
|
179
|
+
if (el.disabled) return false;
|
|
180
|
+
const t = ((el.innerText || el.textContent || el.value || '')).trim();
|
|
181
|
+
return /verify|confirm|continue|submit|next/i.test(t);
|
|
182
|
+
});
|
|
183
|
+
if (!btns.length) return false;
|
|
184
|
+
btns[0].click();
|
|
185
|
+
return true;
|
|
186
186
|
})()`);
|
|
187
187
|
return clicked === true;
|
|
188
188
|
}
|