fix displayed email extraction boundaries
This commit is contained in:
@@ -259,6 +259,112 @@ function getActionText(el) {
|
|||||||
.trim();
|
.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hasEmailTokenBoundary(text, start, end) {
|
||||||
|
const before = start > 0 ? text[start - 1] : '';
|
||||||
|
const after = end < text.length ? text[end] : '';
|
||||||
|
return (!before || !/[A-Z0-9._%+-]/i.test(before))
|
||||||
|
&& (!after || !/[A-Z0-9_%+-]/i.test(after));
|
||||||
|
}
|
||||||
|
|
||||||
|
function isValidEmailCandidate(value = '') {
|
||||||
|
const candidate = String(value || '').trim();
|
||||||
|
const atIndex = candidate.lastIndexOf('@');
|
||||||
|
if (atIndex <= 0 || atIndex !== candidate.indexOf('@')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const local = candidate.slice(0, atIndex);
|
||||||
|
const domain = candidate.slice(atIndex + 1);
|
||||||
|
if (!local || !domain || local.startsWith('.') || local.endsWith('.') || local.includes('..')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!/^[A-Z0-9._%+-]+$/i.test(local)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const labels = domain.split('.');
|
||||||
|
if (labels.length < 2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (const label of labels) {
|
||||||
|
if (!/^[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?$/i.test(label)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const tld = labels[labels.length - 1];
|
||||||
|
return /^[A-Z]{2,63}$/i.test(tld) && !/[a-z][A-Z]/.test(tld);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEmailFromTextFragment(value = '') {
|
||||||
|
const text = String(value || '').replace(/\s+/g, ' ').trim();
|
||||||
|
if (!text) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
const pattern = /[A-Z0-9._%+-]+@(?:[A-Z0-9-]+\.)+[A-Z]{2,63}/ig;
|
||||||
|
for (const match of text.matchAll(pattern)) {
|
||||||
|
const candidate = String(match[0] || '');
|
||||||
|
const start = Number(match.index || 0);
|
||||||
|
const end = start + candidate.length;
|
||||||
|
if (hasEmailTokenBoundary(text, start, end) && isValidEmailCandidate(candidate)) {
|
||||||
|
return candidate.toLowerCase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getVisibleTextNodeEmail() {
|
||||||
|
if (typeof document?.createTreeWalker !== 'function' || !document.body) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
const showText = typeof NodeFilter !== 'undefined' ? NodeFilter.SHOW_TEXT : 4;
|
||||||
|
const walker = document.createTreeWalker(document.body, showText);
|
||||||
|
let node = walker.nextNode();
|
||||||
|
while (node) {
|
||||||
|
const parent = node.parentElement || node.parentNode || null;
|
||||||
|
const text = String(node.nodeValue || '').trim();
|
||||||
|
if (text && (!parent || typeof isVisibleElement !== 'function' || isVisibleElement(parent))) {
|
||||||
|
const email = getEmailFromTextFragment(text);
|
||||||
|
if (email) {
|
||||||
|
return email;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
node = walker.nextNode();
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDisplayedEmailFromDocument() {
|
||||||
|
const fieldCandidates = Array.from(document.querySelectorAll?.('input, textarea') || []);
|
||||||
|
for (const field of fieldCandidates) {
|
||||||
|
const email = getEmailFromTextFragment(field?.value || field?.getAttribute?.('value') || '');
|
||||||
|
if (email) {
|
||||||
|
return email;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const attributeCandidates = Array.from(document.querySelectorAll?.('[aria-label], [title]') || []);
|
||||||
|
for (const el of attributeCandidates) {
|
||||||
|
if (typeof isVisibleElement === 'function' && !isVisibleElement(el)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const email = getEmailFromTextFragment([
|
||||||
|
el?.getAttribute?.('aria-label'),
|
||||||
|
el?.getAttribute?.('title'),
|
||||||
|
].filter(Boolean).join(' '));
|
||||||
|
if (email) {
|
||||||
|
return email;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const textNodeEmail = getVisibleTextNodeEmail();
|
||||||
|
if (textNodeEmail) {
|
||||||
|
return textNodeEmail;
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
function isActionEnabled(el) {
|
function isActionEnabled(el) {
|
||||||
return Boolean(el)
|
return Boolean(el)
|
||||||
&& !el.disabled
|
&& !el.disabled
|
||||||
@@ -715,11 +821,7 @@ function findSignupEntryTrigger(options = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getSignupPasswordDisplayedEmail() {
|
function getSignupPasswordDisplayedEmail() {
|
||||||
const text = (document.body?.innerText || document.body?.textContent || '')
|
return getDisplayedEmailFromDocument();
|
||||||
.replace(/\s+/g, ' ')
|
|
||||||
.trim();
|
|
||||||
const matches = text.match(/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/ig);
|
|
||||||
return matches?.[0] ? String(matches[0]).trim().toLowerCase() : '';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function inspectSignupEntryState() {
|
function inspectSignupEntryState() {
|
||||||
@@ -3028,9 +3130,7 @@ function getPageTextSnapshot() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getLoginVerificationDisplayedEmail() {
|
function getLoginVerificationDisplayedEmail() {
|
||||||
const pageText = getPageTextSnapshot();
|
return getDisplayedEmailFromDocument();
|
||||||
const matches = pageText.match(/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/ig) || [];
|
|
||||||
return matches[0] ? String(matches[0]).trim().toLowerCase() : '';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPhoneVerificationDisplayedPhone() {
|
function getPhoneVerificationDisplayedPhone() {
|
||||||
|
|||||||
@@ -51,6 +51,12 @@ function extractFunction(name) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const bundle = [
|
const bundle = [
|
||||||
|
extractFunction('hasEmailTokenBoundary'),
|
||||||
|
extractFunction('isValidEmailCandidate'),
|
||||||
|
extractFunction('getEmailFromTextFragment'),
|
||||||
|
extractFunction('getVisibleTextNodeEmail'),
|
||||||
|
extractFunction('getDisplayedEmailFromDocument'),
|
||||||
|
extractFunction('getSignupPasswordDisplayedEmail'),
|
||||||
extractFunction('getPageTextSnapshot'),
|
extractFunction('getPageTextSnapshot'),
|
||||||
extractFunction('getLoginVerificationDisplayedEmail'),
|
extractFunction('getLoginVerificationDisplayedEmail'),
|
||||||
extractFunction('getPhoneVerificationDisplayedPhone'),
|
extractFunction('getPhoneVerificationDisplayedPhone'),
|
||||||
@@ -74,9 +80,39 @@ const document = {
|
|||||||
innerText: ${JSON.stringify(overrides.pageText || '')},
|
innerText: ${JSON.stringify(overrides.pageText || '')},
|
||||||
textContent: ${JSON.stringify(overrides.pageText || '')},
|
textContent: ${JSON.stringify(overrides.pageText || '')},
|
||||||
},
|
},
|
||||||
|
querySelectorAll(selector) {
|
||||||
|
if (selector === 'input, textarea') {
|
||||||
|
return ${JSON.stringify(overrides.fields || [])};
|
||||||
|
}
|
||||||
|
if (selector === '[aria-label], [title]') {
|
||||||
|
return ${JSON.stringify(overrides.attributeElements || [])};
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
},
|
||||||
querySelector() {
|
querySelector() {
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
|
createTreeWalker() {
|
||||||
|
const textNodeValues = ${JSON.stringify(
|
||||||
|
Object.prototype.hasOwnProperty.call(overrides, 'textNodes')
|
||||||
|
? overrides.textNodes
|
||||||
|
: [overrides.pageText || '']
|
||||||
|
)};
|
||||||
|
const nodes = textNodeValues.map((text) => ({
|
||||||
|
nodeValue: text,
|
||||||
|
parentElement: {
|
||||||
|
getBoundingClientRect() {
|
||||||
|
return { width: 1, height: 1 };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
let index = 0;
|
||||||
|
return {
|
||||||
|
nextNode() {
|
||||||
|
return nodes[index++] || null;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
function getLoginTimeoutErrorPageState() {
|
function getLoginTimeoutErrorPageState() {
|
||||||
@@ -149,6 +185,8 @@ return {
|
|||||||
inspectLoginAuthState,
|
inspectLoginAuthState,
|
||||||
isPhoneVerificationPageReady,
|
isPhoneVerificationPageReady,
|
||||||
normalizeStep6Snapshot,
|
normalizeStep6Snapshot,
|
||||||
|
getSignupPasswordDisplayedEmail,
|
||||||
|
getLoginVerificationDisplayedEmail,
|
||||||
};
|
};
|
||||||
`)();
|
`)();
|
||||||
}
|
}
|
||||||
@@ -179,6 +217,49 @@ return {
|
|||||||
assert.strictEqual(snapshot.displayedEmail, 'display.user@example.com');
|
assert.strictEqual(snapshot.displayedEmail, 'display.user@example.com');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const api = createApi({
|
||||||
|
verificationTarget: { id: 'otp' },
|
||||||
|
pageText: 'We emailed a code to skater-twine-carve@duck.comChange',
|
||||||
|
textNodes: ['We emailed a code to ', 'skater-twine-carve@duck.com', 'Change'],
|
||||||
|
});
|
||||||
|
|
||||||
|
const snapshot = api.inspectLoginAuthState();
|
||||||
|
assert.strictEqual(snapshot.displayedEmail, 'skater-twine-carve@duck.com');
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const api = createApi({
|
||||||
|
verificationTarget: { id: 'otp' },
|
||||||
|
pageText: 'We emailed a code to skater-twine-carve@duck.comChange',
|
||||||
|
textNodes: ['We emailed a code to skater-twine-carve@duck.comChange'],
|
||||||
|
});
|
||||||
|
|
||||||
|
const snapshot = api.inspectLoginAuthState();
|
||||||
|
assert.strictEqual(snapshot.displayedEmail, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const api = createApi({
|
||||||
|
pageText: 'Create your account skater-twine-carve@duck.comChange',
|
||||||
|
textNodes: ['Create your account ', 'skater-twine-carve@duck.com', 'Change'],
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.strictEqual(api.getSignupPasswordDisplayedEmail(), 'skater-twine-carve@duck.com');
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const api = createApi({
|
||||||
|
verificationTarget: { id: 'otp' },
|
||||||
|
pageText: 'We emailed a code to skater-twine-carve@duck.comChange',
|
||||||
|
textNodes: ['We emailed a code to skater-twine-carve@duck.comChange'],
|
||||||
|
fields: [{ value: 'skater-twine-carve@duck.com' }],
|
||||||
|
});
|
||||||
|
|
||||||
|
const snapshot = api.inspectLoginAuthState();
|
||||||
|
assert.strictEqual(snapshot.displayedEmail, 'skater-twine-carve@duck.com');
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
const api = createApi({
|
const api = createApi({
|
||||||
pathname: '/email-verification',
|
pathname: '/email-verification',
|
||||||
|
|||||||
Reference in New Issue
Block a user