@protontech/autofill 0.0.22991789

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 (118) hide show
  1. package/README.md +1 -0
  2. package/cli.d.ts +2 -0
  3. package/cli.js +128 -0
  4. package/constants/heuristics.d.ts +15 -0
  5. package/constants/heuristics.js +21 -0
  6. package/constants/selectors.d.ts +18 -0
  7. package/constants/selectors.js +52 -0
  8. package/debug.d.ts +1 -0
  9. package/debug.js +17 -0
  10. package/dictionary/generate.d.ts +1 -0
  11. package/dictionary/generate.js +42 -0
  12. package/dictionary/generated/dictionary.d.ts +51 -0
  13. package/dictionary/generated/dictionary.js +51 -0
  14. package/dictionary/source/dictionary.d.ts +11 -0
  15. package/dictionary/source/dictionary.js +375 -0
  16. package/dictionary/source/patterns.d.ts +3 -0
  17. package/dictionary/source/patterns.js +3 -0
  18. package/features/abstract.field.d.ts +123 -0
  19. package/features/abstract.field.js +63 -0
  20. package/features/abstract.form.d.ts +98 -0
  21. package/features/abstract.form.js +281 -0
  22. package/features/field.email.d.ts +18 -0
  23. package/features/field.email.js +43 -0
  24. package/features/field.otp.d.ts +36 -0
  25. package/features/field.otp.js +116 -0
  26. package/features/field.password.d.ts +35 -0
  27. package/features/field.password.js +104 -0
  28. package/features/field.username-hidden.d.ts +15 -0
  29. package/features/field.username-hidden.js +40 -0
  30. package/features/field.username.d.ts +16 -0
  31. package/features/field.username.js +41 -0
  32. package/features/form.combined.d.ts +1 -0
  33. package/features/form.combined.js +6 -0
  34. package/index.d.ts +14 -0
  35. package/index.js +15 -0
  36. package/package.json +29 -0
  37. package/rulesets.d.ts +2 -0
  38. package/rulesets.js +10 -0
  39. package/trainees/field.email.d.ts +2 -0
  40. package/trainees/field.email.js +16 -0
  41. package/trainees/field.identity.d.ts +2 -0
  42. package/trainees/field.identity.js +9 -0
  43. package/trainees/field.otp.d.ts +2 -0
  44. package/trainees/field.otp.js +16 -0
  45. package/trainees/field.password.current.d.ts +2 -0
  46. package/trainees/field.password.current.js +16 -0
  47. package/trainees/field.password.new.d.ts +2 -0
  48. package/trainees/field.password.new.js +16 -0
  49. package/trainees/field.username-hidden.d.ts +2 -0
  50. package/trainees/field.username-hidden.js +22 -0
  51. package/trainees/field.username.d.ts +2 -0
  52. package/trainees/field.username.js +16 -0
  53. package/trainees/form.login.d.ts +2 -0
  54. package/trainees/form.login.js +16 -0
  55. package/trainees/form.noop.d.ts +1 -0
  56. package/trainees/form.noop.js +7 -0
  57. package/trainees/form.password-change.d.ts +2 -0
  58. package/trainees/form.password-change.js +16 -0
  59. package/trainees/form.recovery.d.ts +2 -0
  60. package/trainees/form.recovery.js +16 -0
  61. package/trainees/form.register.d.ts +2 -0
  62. package/trainees/form.register.js +16 -0
  63. package/trainees/index.d.ts +9 -0
  64. package/trainees/index.js +72 -0
  65. package/trainees/results/result.email.d.ts +2 -0
  66. package/trainees/results/result.email.js +17 -0
  67. package/trainees/results/result.login.d.ts +2 -0
  68. package/trainees/results/result.login.js +110 -0
  69. package/trainees/results/result.new-password.d.ts +2 -0
  70. package/trainees/results/result.new-password.js +36 -0
  71. package/trainees/results/result.otp.d.ts +2 -0
  72. package/trainees/results/result.otp.js +43 -0
  73. package/trainees/results/result.password-change.d.ts +2 -0
  74. package/trainees/results/result.password-change.js +110 -0
  75. package/trainees/results/result.password.d.ts +2 -0
  76. package/trainees/results/result.password.js +36 -0
  77. package/trainees/results/result.recovery.d.ts +2 -0
  78. package/trainees/results/result.recovery.js +110 -0
  79. package/trainees/results/result.register.d.ts +2 -0
  80. package/trainees/results/result.register.js +110 -0
  81. package/trainees/results/result.username-hidden.d.ts +2 -0
  82. package/trainees/results/result.username-hidden.js +15 -0
  83. package/trainees/results/result.username.d.ts +2 -0
  84. package/trainees/results/result.username.js +16 -0
  85. package/types/index.d.ts +38 -0
  86. package/types/index.js +20 -0
  87. package/utils/attributes.d.ts +9 -0
  88. package/utils/attributes.js +13 -0
  89. package/utils/clustering.d.ts +1 -0
  90. package/utils/clustering.js +81 -0
  91. package/utils/combinators.d.ts +6 -0
  92. package/utils/combinators.js +4 -0
  93. package/utils/dom.d.ts +25 -0
  94. package/utils/dom.js +104 -0
  95. package/utils/exclusion.d.ts +3 -0
  96. package/utils/exclusion.js +59 -0
  97. package/utils/extract.d.ts +13 -0
  98. package/utils/extract.js +59 -0
  99. package/utils/fathom.d.ts +38 -0
  100. package/utils/fathom.js +68 -0
  101. package/utils/field.d.ts +14 -0
  102. package/utils/field.js +50 -0
  103. package/utils/flags.d.ts +25 -0
  104. package/utils/flags.js +60 -0
  105. package/utils/form.d.ts +7 -0
  106. package/utils/form.js +25 -0
  107. package/utils/identity.d.ts +24 -0
  108. package/utils/identity.js +63 -0
  109. package/utils/memoize.d.ts +5 -0
  110. package/utils/memoize.js +12 -0
  111. package/utils/prepass.d.ts +2 -0
  112. package/utils/prepass.js +31 -0
  113. package/utils/re.d.ts +58 -0
  114. package/utils/re.js +64 -0
  115. package/utils/text.d.ts +3 -0
  116. package/utils/text.js +8 -0
  117. package/utils/visible.d.ts +13 -0
  118. package/utils/visible.js +143 -0
@@ -0,0 +1,25 @@
1
+ import { Fnode } from "@protontech/fathom";
2
+ export declare const isCluster: (el: HTMLElement) => boolean;
3
+ export declare const flagCluster: (el: HTMLElement) => void;
4
+ export declare const isHidden: (el: HTMLElement) => boolean;
5
+ export declare const flagAsHidden: (el: HTMLElement) => boolean;
6
+ export declare const removeHiddenFlag: (el: HTMLElement) => boolean;
7
+ export declare const attrIgnored: (el: HTMLElement) => boolean;
8
+ export declare const isIgnored: (el: HTMLElement) => boolean;
9
+ export declare const getIgnoredParent: (el?: HTMLElement) => HTMLElement | null;
10
+ export declare const flagAsIgnored: (el: HTMLElement) => boolean;
11
+ export declare const removeIgnoredFlag: (el: HTMLElement) => boolean;
12
+ export declare const flagSubtreeAsIgnored: (el: HTMLElement) => void;
13
+ export declare const isProcessed: (el: HTMLElement) => boolean;
14
+ export declare const flagAsProcessed: (el: HTMLElement) => boolean;
15
+ export declare const removeProcessedFlag: (el: HTMLElement) => boolean;
16
+ export declare const isPrediction: (el: HTMLElement) => boolean;
17
+ export declare const removePredictionFlag: (el: HTMLElement) => boolean;
18
+ export declare const getParentFormPrediction: (el?: HTMLElement) => HTMLElement | null;
19
+ export declare const setCachedPredictionScore: (_el: HTMLElement, type: string, score: number) => void;
20
+ export declare const getCachedPredictionScore: (type: string) => (fnode: Fnode) => number;
21
+ export declare const isPredictedType: (type: string) => (fnode: Fnode) => boolean;
22
+ export declare const isClassifiable: (el: HTMLElement) => boolean;
23
+ export declare const removeClassifierFlags: (el: HTMLElement, options: {
24
+ preserveIgnored: boolean;
25
+ }) => void;
package/utils/flags.js ADDED
@@ -0,0 +1,60 @@
1
+ import { FORM_CLUSTER_ATTR, kFieldSelector } from "../constants/selectors";
2
+ import { closestParent } from "./dom";
3
+ export const isCluster = (el) => el.getAttribute(FORM_CLUSTER_ATTR) !== null;
4
+ export const flagCluster = (el) => el.setAttribute(FORM_CLUSTER_ATTR, "");
5
+ export const isHidden = (el) => el.__PP_HIDDEN__ === true;
6
+ export const flagAsHidden = (el) => (el.__PP_HIDDEN__ = true);
7
+ export const removeHiddenFlag = (el) => delete el.__PP_HIDDEN__;
8
+ export const attrIgnored = (el) => el.getAttribute("data-protonpass-ignore") !== null;
9
+ export const isIgnored = (el) => el.__PP_SKIP__ === true;
10
+ export const getIgnoredParent = (el) => (el ? closestParent(el, isIgnored) : null);
11
+ export const flagAsIgnored = (el) => (el.__PP_SKIP__ = true);
12
+ export const removeIgnoredFlag = (el) => delete el.__PP_SKIP__;
13
+ export const flagSubtreeAsIgnored = (el) => {
14
+ flagAsIgnored(el);
15
+ el.querySelectorAll(kFieldSelector).forEach(flagAsIgnored);
16
+ };
17
+ export const isProcessed = (el) => el.__PP_SEEN__ === true;
18
+ export const flagAsProcessed = (el) => (el.__PP_SEEN__ = true);
19
+ export const removeProcessedFlag = (el) => delete el.__PP_SEEN__;
20
+ export const isPrediction = (el) => el.__PP_TYPE__ !== undefined;
21
+ export const removePredictionFlag = (el) => delete el.__PP_TYPE__;
22
+ export const getParentFormPrediction = (el) => (el ? closestParent(el, isPrediction) : null);
23
+ const TYPE_SEPARATOR = ",";
24
+ const SCORE_SEPARATOR = ":";
25
+ export const setCachedPredictionScore = (_el, type, score) => {
26
+ const el = _el;
27
+ const currentType = el.__PP_TYPE__;
28
+ const flag = `${type}${SCORE_SEPARATOR}${score.toFixed(2)}`;
29
+ if (!currentType) {
30
+ el.__PP_TYPE__ = flag;
31
+ return;
32
+ }
33
+ const types = currentType.split(TYPE_SEPARATOR);
34
+ const existingIndex = types.findIndex((pred) => pred.startsWith(type));
35
+ if (existingIndex !== -1)
36
+ types[existingIndex] = flag;
37
+ else
38
+ types.push(flag);
39
+ el.__PP_TYPE__ = types.join(TYPE_SEPARATOR);
40
+ };
41
+ export const getCachedPredictionScore = (type) => (fnode) => {
42
+ const types = fnode.element.__PP_TYPE__;
43
+ if (!types)
44
+ return -1;
45
+ const predForType = types.split(TYPE_SEPARATOR).find((pred) => pred.startsWith(type));
46
+ if (!predForType)
47
+ return -1;
48
+ const [, scoreStr] = predForType.split(SCORE_SEPARATOR);
49
+ const score = parseFloat(scoreStr);
50
+ return Number.isFinite(score) ? score : -1;
51
+ };
52
+ export const isPredictedType = (type) => (fnode) => getCachedPredictionScore(type)(fnode) !== -1;
53
+ export const isClassifiable = (el) => !(isPrediction(el) || isIgnored(el) || attrIgnored(el));
54
+ export const removeClassifierFlags = (el, options) => {
55
+ removeProcessedFlag(el);
56
+ removePredictionFlag(el);
57
+ if (!options.preserveIgnored)
58
+ removeIgnoredFlag(el);
59
+ el.querySelectorAll(kFieldSelector).forEach((el) => removeClassifierFlags(el, options));
60
+ };
@@ -0,0 +1,7 @@
1
+ export declare const getFormParent: (form: HTMLElement) => HTMLElement;
2
+ export type FormInputIterator = ReturnType<typeof createInputIterator>;
3
+ export declare const createInputIterator: (form: HTMLElement) => {
4
+ prev(input: HTMLElement): HTMLElement | null;
5
+ next(input: HTMLElement): HTMLElement | null;
6
+ };
7
+ export declare const selectFormCandidates: (root?: Document | HTMLElement) => HTMLElement[];
package/utils/form.js ADDED
@@ -0,0 +1,25 @@
1
+ import { MAX_FORM_FIELD_WALK_UP } from "../constants/heuristics";
2
+ import { formCandidateSelector, inputCandidateSelector } from "../constants/selectors";
3
+ import { walkUpWhile } from "./dom";
4
+ import { attrIgnored, isIgnored } from "./flags";
5
+ import { isVisibleField } from "./visible";
6
+ export const getFormParent = (form) => walkUpWhile(form, MAX_FORM_FIELD_WALK_UP)((el) => el.querySelectorAll(formCandidateSelector).length <= 1);
7
+ export const createInputIterator = (form) => {
8
+ const formEls = Array.from(form.querySelectorAll(inputCandidateSelector)).filter(isVisibleField);
9
+ return {
10
+ prev(input) {
11
+ var _a;
12
+ const idx = formEls.indexOf(input);
13
+ return idx === -1 ? null : (_a = formEls === null || formEls === void 0 ? void 0 : formEls[idx - 1]) !== null && _a !== void 0 ? _a : null;
14
+ },
15
+ next(input) {
16
+ var _a;
17
+ const idx = formEls.indexOf(input);
18
+ return idx === -1 ? null : (_a = formEls === null || formEls === void 0 ? void 0 : formEls[idx + 1]) !== null && _a !== void 0 ? _a : null;
19
+ },
20
+ };
21
+ };
22
+ export const selectFormCandidates = (root = document) => {
23
+ const candidates = Array.from(root.querySelectorAll(formCandidateSelector));
24
+ return candidates.filter((form) => !isIgnored(form) && !attrIgnored(form));
25
+ };
@@ -0,0 +1,24 @@
1
+ import { Fnode } from "@protontech/fathom";
2
+ export declare enum IdentityFieldType {
3
+ FULLNAME = 1,
4
+ FIRSTNAME = 2,
5
+ MIDDLENAME = 3,
6
+ LASTNAME = 4,
7
+ TELEPHONE = 5,
8
+ ADDRESS = 6,
9
+ STATE = 7,
10
+ CITY = 8,
11
+ ZIPCODE = 9,
12
+ ORGANIZATION = 10,
13
+ COUNTRY = 11,
14
+ EMAIL = 12
15
+ }
16
+ export declare const getIdentityHaystack: (input: HTMLInputElement) => string;
17
+ export declare const getIdentityFieldType: (input: HTMLInputElement) => IdentityFieldType | undefined;
18
+ export declare const maybeIdentity: (fnode: Fnode) => boolean;
19
+ export declare const matchesIdentityField: (input: HTMLInputElement, options: {
20
+ isFormLogin: boolean;
21
+ searchField: boolean;
22
+ type: string | null;
23
+ visible: boolean;
24
+ }) => boolean;
@@ -0,0 +1,63 @@
1
+ import { matchAddress, matchCity, matchCountry, matchFirstName, matchFullName, matchLastName, matchMiddleName, matchOrganization, matchState, matchTelephone, matchZipCode, } from "./re";
2
+ import { sanitizeStringWithSpaces } from "./text";
3
+ export var IdentityFieldType;
4
+ (function (IdentityFieldType) {
5
+ IdentityFieldType[IdentityFieldType["FULLNAME"] = 1] = "FULLNAME";
6
+ IdentityFieldType[IdentityFieldType["FIRSTNAME"] = 2] = "FIRSTNAME";
7
+ IdentityFieldType[IdentityFieldType["MIDDLENAME"] = 3] = "MIDDLENAME";
8
+ IdentityFieldType[IdentityFieldType["LASTNAME"] = 4] = "LASTNAME";
9
+ IdentityFieldType[IdentityFieldType["TELEPHONE"] = 5] = "TELEPHONE";
10
+ IdentityFieldType[IdentityFieldType["ADDRESS"] = 6] = "ADDRESS";
11
+ IdentityFieldType[IdentityFieldType["STATE"] = 7] = "STATE";
12
+ IdentityFieldType[IdentityFieldType["CITY"] = 8] = "CITY";
13
+ IdentityFieldType[IdentityFieldType["ZIPCODE"] = 9] = "ZIPCODE";
14
+ IdentityFieldType[IdentityFieldType["ORGANIZATION"] = 10] = "ORGANIZATION";
15
+ IdentityFieldType[IdentityFieldType["COUNTRY"] = 11] = "COUNTRY";
16
+ IdentityFieldType[IdentityFieldType["EMAIL"] = 12] = "EMAIL";
17
+ })(IdentityFieldType || (IdentityFieldType = {}));
18
+ const IDENTITY_RE_MAP = [
19
+ [IdentityFieldType.FIRSTNAME, matchFirstName],
20
+ [IdentityFieldType.MIDDLENAME, matchMiddleName],
21
+ [IdentityFieldType.LASTNAME, matchLastName],
22
+ [IdentityFieldType.FULLNAME, matchFullName],
23
+ [IdentityFieldType.TELEPHONE, matchTelephone],
24
+ [IdentityFieldType.ORGANIZATION, matchOrganization],
25
+ [IdentityFieldType.CITY, matchCity],
26
+ [IdentityFieldType.ZIPCODE, matchZipCode],
27
+ [IdentityFieldType.STATE, matchState],
28
+ [IdentityFieldType.COUNTRY, matchCountry],
29
+ [IdentityFieldType.ADDRESS, matchAddress],
30
+ ];
31
+ const IDENTITY_ATTRIBUTES = ["autocomplete", "name", "id", "data-bhw"];
32
+ const IDENTITY_INPUT_TYPES = ["tel", "phone", "text", "number"];
33
+ export const getIdentityHaystack = (input) => {
34
+ const attrs = IDENTITY_ATTRIBUTES.map((attr) => { var _a; return (_a = input === null || input === void 0 ? void 0 : input.getAttribute(attr)) !== null && _a !== void 0 ? _a : ""; });
35
+ return sanitizeStringWithSpaces(attrs.join(" "));
36
+ };
37
+ export const getIdentityFieldType = (input) => {
38
+ var _a;
39
+ const haystack = getIdentityHaystack(input);
40
+ if (haystack)
41
+ return (_a = IDENTITY_RE_MAP.find(([, test]) => test(haystack))) === null || _a === void 0 ? void 0 : _a[0];
42
+ };
43
+ const isAutocompleteListInput = (el) => el.getAttribute("aria-autocomplete") === "list" || el.role === "combobox";
44
+ export const maybeIdentity = (fnode) => {
45
+ var _a;
46
+ const fieldFeatures = fnode.noteFor("field");
47
+ return (_a = fieldFeatures === null || fieldFeatures === void 0 ? void 0 : fieldFeatures.identityField) !== null && _a !== void 0 ? _a : false;
48
+ };
49
+ export const matchesIdentityField = (input, options) => {
50
+ const { visible, isFormLogin, type, searchField } = options;
51
+ if ((type && !IDENTITY_INPUT_TYPES.includes(type)) || !visible || isFormLogin)
52
+ return false;
53
+ const identityType = getIdentityFieldType(input);
54
+ if (!identityType)
55
+ return false;
56
+ if (isAutocompleteListInput(input))
57
+ return [IdentityFieldType.ADDRESS, IdentityFieldType.ZIPCODE].includes(identityType);
58
+ if (type === "number")
59
+ return [IdentityFieldType.TELEPHONE, IdentityFieldType.ZIPCODE].includes(identityType);
60
+ if (searchField)
61
+ return [IdentityFieldType.ADDRESS, IdentityFieldType.ZIPCODE, identityType === IdentityFieldType.CITY].includes(identityType);
62
+ return true;
63
+ };
@@ -0,0 +1,5 @@
1
+ type Memoised<F extends (arg: any) => any> = F & {
2
+ clearCache: () => void;
3
+ };
4
+ export declare const memoize: <F extends (arg: any) => any>(fn: F) => Memoised<F>;
5
+ export {};
@@ -0,0 +1,12 @@
1
+ export const memoize = (fn) => {
2
+ let cache = new WeakMap();
3
+ const memoisedFn = ((arg) => {
4
+ if (cache.has(arg))
5
+ return cache.get(arg);
6
+ const result = fn(arg);
7
+ cache.set(arg, result);
8
+ return result;
9
+ });
10
+ memoisedFn.clearCache = () => (cache = new WeakMap());
11
+ return memoisedFn;
12
+ };
@@ -0,0 +1,2 @@
1
+ export declare const prepass: (doc?: Document) => void;
2
+ export declare const shouldRunClassifier: () => boolean;
@@ -0,0 +1,31 @@
1
+ import { resolveFormClusters } from "./clustering";
2
+ import { excludeClusterableNodes, excludeFields, excludeForms } from "./exclusion";
3
+ import { isProcessableField, selectInputCandidates } from "./field";
4
+ import { isProcessed, removeClassifierFlags, removeHiddenFlag } from "./flags";
5
+ import { selectFormCandidates } from "./form";
6
+ import { isVisibleForm } from "./visible";
7
+ export const prepass = (doc = document) => {
8
+ excludeForms(doc);
9
+ excludeClusterableNodes(doc);
10
+ excludeFields(doc);
11
+ resolveFormClusters(doc);
12
+ };
13
+ export const shouldRunClassifier = () => {
14
+ const runForForms = selectFormCandidates().reduce((runDetection, form) => {
15
+ if (isProcessed(form)) {
16
+ const unprocessedFields = selectInputCandidates(form).some(isProcessableField);
17
+ if (unprocessedFields)
18
+ removeClassifierFlags(form, { preserveIgnored: false });
19
+ return runDetection || unprocessedFields;
20
+ }
21
+ if (isVisibleForm(form)) {
22
+ removeHiddenFlag(form);
23
+ return true;
24
+ }
25
+ return runDetection;
26
+ }, false);
27
+ if (runForForms)
28
+ return true;
29
+ const runForFields = selectInputCandidates().some(isProcessableField);
30
+ return runForFields;
31
+ };
package/utils/re.d.ts ADDED
@@ -0,0 +1,58 @@
1
+ export type ReTest = (str: string) => boolean;
2
+ type ReSanityOptions = {
3
+ maxLength?: number;
4
+ minLength?: number;
5
+ };
6
+ export declare const reSanityCheck: (cb: (str: string) => boolean, options: ReSanityOptions) => (str: string) => boolean;
7
+ export declare const test: (re: RegExp, options?: ReSanityOptions) => (str: string) => boolean;
8
+ export declare const matchLogin: (str: string) => boolean;
9
+ export declare const matchRegister: (str: string) => boolean;
10
+ export declare const matchUsername: (str: string) => boolean;
11
+ export declare const matchUsernameAttr: (value: string) => boolean;
12
+ export declare const matchUsernameValue: (str: string) => boolean;
13
+ export declare const matchUsernameOutlier: (str: string) => boolean;
14
+ export declare const matchEmailAttr: (value: string) => boolean;
15
+ export declare const matchEmailValue: (str: string) => boolean;
16
+ export declare const matchEmail: (value: string) => boolean;
17
+ export declare const matchTel: (str: string) => boolean;
18
+ export declare const matchTelValue: (str: string) => boolean;
19
+ export declare const matchRememberMe: (str: string) => boolean;
20
+ export declare const matchTOS: (str: string) => boolean;
21
+ export declare const matchTrouble: (str: string) => boolean;
22
+ export declare const matchRecovery: (value: string) => boolean;
23
+ export declare const matchMultiStep: (str: string) => boolean;
24
+ export declare const matchStepAction: (value: string) => boolean;
25
+ export declare const matchCreateAction: (str: string) => boolean;
26
+ export declare const matchConfirmAction: (str: string) => boolean;
27
+ export declare const matchOAuth: (str: string) => boolean;
28
+ export declare const matchCaptcha: (str: string) => boolean;
29
+ export declare const matchSearchAction: (str: string) => boolean;
30
+ export declare const matchPasswordReset: (value: string) => boolean;
31
+ export declare const matchPasswordResetAttr: (value: string) => boolean;
32
+ export declare const matchPasswordCreate: (value: string) => boolean;
33
+ export declare const matchPasswordCreateAttr: (value: string) => boolean;
34
+ export declare const matchPasswordConfirm: (value: string) => boolean;
35
+ export declare const matchPasswordConfirmAttr: (value: string) => boolean;
36
+ export declare const matchPasswordCurrent: (value: string) => boolean;
37
+ export declare const matchPasswordCurrentAttr: (value: string) => boolean;
38
+ export declare const matchPasswordOutlier: (str: string) => boolean;
39
+ export declare const matchHidden: (str: string) => boolean;
40
+ export declare const matchMfaAction: (str: string) => boolean;
41
+ export declare const matchMfa: (str: string) => boolean;
42
+ export declare const matchMfaAttr: (str: string) => boolean;
43
+ export declare const matchOtpAttr: (str: string) => boolean;
44
+ export declare const matchOtpOutlier: (value: string) => boolean;
45
+ export declare const matchNewsletter: (str: string) => boolean;
46
+ export declare const matchNewsletterAttr: (value: string) => boolean;
47
+ export declare const matchFullName: (value: string) => boolean;
48
+ export declare const matchFirstName: (str: string) => boolean;
49
+ export declare const matchMiddleName: (str: string) => boolean;
50
+ export declare const matchLastName: (str: string) => boolean;
51
+ export declare const matchTelephone: (value: string) => boolean;
52
+ export declare const matchOrganization: (str: string) => boolean;
53
+ export declare const matchCity: (str: string) => boolean;
54
+ export declare const matchZipCode: (str: string) => boolean;
55
+ export declare const matchState: (str: string) => boolean;
56
+ export declare const matchCountry: (value: string) => boolean;
57
+ export declare const matchAddress: (value: string) => boolean;
58
+ export {};
package/utils/re.js ADDED
@@ -0,0 +1,64 @@
1
+ import { CAPTCHA_ATTR_RE, CONFIRM_ACTION_ATTR_END_RE, CONFIRM_ACTION_RE, CREATE_ACTION_ATTR_END_RE, CREATE_ACTION_RE, CURRENT_VALUE_RE, EMAIL_ATTR_RE, EMAIL_RE, HIDDEN_ATTR_RE, IDENTITY_ADDRESS_ATTR_RE, IDENTITY_ADDRESS_LINES_ATTR_END_RE, IDENTITY_CITY_ATTR_RE, IDENTITY_COUNTRY_ATTR_RE, IDENTITY_COUNTRY_CODE_ATTR_RE, IDENTITY_FIRSTNAME_ATTR_RE, IDENTITY_FULLNAME_ATTR_RE, IDENTITY_LASTNAME_ATTR_RE, IDENTITY_MIDDLENAME_ATTR_RE, IDENTITY_ORGANIZATION_ATTR_RE, IDENTITY_STATE_ATTR_RE, IDENTITY_TELEPHONE_ATTR_RE, IDENTITY_TELEPHONE_PREFIX_ATTR_RE, IDENTITY_ZIPCODE_ATTR_RE, LOGIN_RE, MFA_ACTION_RE, MFA_ATTR_RE, MFA_RE, MULTI_STEP_RE, NEWSLETTER_ATTR_RE, NEWSLETTER_RE, OAUTH_ATTR_RE, OTP_ATTR_RE, OTP_OUTLIER_ATTR_RE, OTP_OUTLIER_RE, PASSWORD_OUTLIER_RE, PASSWORD_RE, RECOVERY_RE, REGISTER_RE, REMEMBER_ACTION_RE, RESET_ACTION_RE, SEARCH_ACTION_RE, STEP_ACTION_RE, TEL_RE, TOS_RE, TROUBLE_RE, USERNAME_ATTR_RE, USERNAME_OUTLIER_RE, USERNAME_RE, } from "../dictionary/generated/dictionary";
2
+ import { EMAIL_VALUE_RE, TEL_VALUE_RE, USERNAME_VALUE_RE } from "../dictionary/source/patterns";
3
+ import { and, or } from "./combinators";
4
+ export const reSanityCheck = (cb, options) => (str) => {
5
+ if (options.maxLength && str.length > options.maxLength)
6
+ return false;
7
+ if (options.minLength && str.length < options.minLength)
8
+ return false;
9
+ return cb(str);
10
+ };
11
+ export const test = (re, options) => reSanityCheck((str) => re.test(str), options !== null && options !== void 0 ? options : { maxLength: 5000, minLength: 2 });
12
+ const notRe = (reg, options) => (str) => !test(reg, options)(str);
13
+ const andRe = (reg, options) => and(...reg.map((re) => test(re, options)));
14
+ const orRe = (reg, options) => or(...reg.map((re) => test(re, options)));
15
+ export const matchLogin = test(LOGIN_RE);
16
+ export const matchRegister = test(REGISTER_RE);
17
+ export const matchUsername = test(USERNAME_RE);
18
+ export const matchUsernameAttr = orRe([USERNAME_ATTR_RE, USERNAME_RE]);
19
+ export const matchUsernameValue = test(USERNAME_VALUE_RE);
20
+ export const matchUsernameOutlier = test(USERNAME_OUTLIER_RE);
21
+ export const matchEmailAttr = orRe([EMAIL_ATTR_RE, EMAIL_RE]);
22
+ export const matchEmailValue = test(EMAIL_VALUE_RE, { maxLength: 230, minLength: 5 });
23
+ export const matchEmail = or(test(EMAIL_RE), matchEmailValue);
24
+ export const matchTel = test(TEL_RE);
25
+ export const matchTelValue = test(TEL_VALUE_RE, { maxLength: 25, minLength: 6 });
26
+ export const matchRememberMe = test(REMEMBER_ACTION_RE);
27
+ export const matchTOS = test(TOS_RE);
28
+ export const matchTrouble = test(TROUBLE_RE);
29
+ export const matchRecovery = orRe([RECOVERY_RE, TROUBLE_RE]);
30
+ export const matchMultiStep = test(MULTI_STEP_RE);
31
+ export const matchStepAction = orRe([STEP_ACTION_RE, MULTI_STEP_RE]);
32
+ export const matchCreateAction = test(CREATE_ACTION_RE);
33
+ export const matchConfirmAction = test(CONFIRM_ACTION_RE);
34
+ export const matchOAuth = test(OAUTH_ATTR_RE);
35
+ export const matchCaptcha = test(CAPTCHA_ATTR_RE);
36
+ export const matchSearchAction = test(SEARCH_ACTION_RE);
37
+ export const matchPasswordReset = and(andRe([PASSWORD_RE, RESET_ACTION_RE]), notRe(CONFIRM_ACTION_RE));
38
+ export const matchPasswordResetAttr = and(matchPasswordReset, notRe(CONFIRM_ACTION_ATTR_END_RE));
39
+ export const matchPasswordCreate = and(andRe([PASSWORD_RE, CREATE_ACTION_RE]), notRe(CONFIRM_ACTION_RE));
40
+ export const matchPasswordCreateAttr = and(matchPasswordCreate, notRe(CONFIRM_ACTION_ATTR_END_RE));
41
+ export const matchPasswordConfirm = andRe([PASSWORD_RE, CONFIRM_ACTION_RE]);
42
+ export const matchPasswordConfirmAttr = and(andRe([PASSWORD_RE, CONFIRM_ACTION_RE]), notRe(CREATE_ACTION_ATTR_END_RE));
43
+ export const matchPasswordCurrent = and(andRe([PASSWORD_RE, CURRENT_VALUE_RE]), notRe(CONFIRM_ACTION_RE));
44
+ export const matchPasswordCurrentAttr = and(matchPasswordCurrent, notRe(CONFIRM_ACTION_ATTR_END_RE));
45
+ export const matchPasswordOutlier = test(PASSWORD_OUTLIER_RE);
46
+ export const matchHidden = test(HIDDEN_ATTR_RE);
47
+ export const matchMfaAction = test(MFA_ACTION_RE);
48
+ export const matchMfa = test(MFA_RE);
49
+ export const matchMfaAttr = test(MFA_ATTR_RE);
50
+ export const matchOtpAttr = test(OTP_ATTR_RE);
51
+ export const matchOtpOutlier = orRe([OTP_OUTLIER_ATTR_RE, OTP_OUTLIER_RE]);
52
+ export const matchNewsletter = test(NEWSLETTER_RE);
53
+ export const matchNewsletterAttr = orRe([NEWSLETTER_RE, NEWSLETTER_ATTR_RE]);
54
+ export const matchFullName = and(test(IDENTITY_FULLNAME_ATTR_RE), (str) => !str.includes("[name]"));
55
+ export const matchFirstName = test(IDENTITY_FIRSTNAME_ATTR_RE);
56
+ export const matchMiddleName = test(IDENTITY_MIDDLENAME_ATTR_RE);
57
+ export const matchLastName = test(IDENTITY_LASTNAME_ATTR_RE);
58
+ export const matchTelephone = and(test(IDENTITY_TELEPHONE_ATTR_RE), notRe(IDENTITY_TELEPHONE_PREFIX_ATTR_RE));
59
+ export const matchOrganization = test(IDENTITY_ORGANIZATION_ATTR_RE);
60
+ export const matchCity = test(IDENTITY_CITY_ATTR_RE);
61
+ export const matchZipCode = test(IDENTITY_ZIPCODE_ATTR_RE);
62
+ export const matchState = test(IDENTITY_STATE_ATTR_RE);
63
+ export const matchCountry = and(test(IDENTITY_COUNTRY_ATTR_RE), notRe(IDENTITY_COUNTRY_CODE_ATTR_RE));
64
+ export const matchAddress = and(test(IDENTITY_ADDRESS_ATTR_RE), notRe(IDENTITY_ADDRESS_LINES_ATTR_END_RE));
@@ -0,0 +1,3 @@
1
+ export declare const normalizeString: (str: string, allowedChars?: string) => string;
2
+ export declare const sanitizeString: (str: string) => string;
3
+ export declare const sanitizeStringWithSpaces: (str: string) => string;
package/utils/text.js ADDED
@@ -0,0 +1,8 @@
1
+ export const normalizeString = (str, allowedChars = "") => str
2
+ .trim()
3
+ .toLowerCase()
4
+ .normalize("NFD")
5
+ .replace(/[\u0300-\u036f]/g, "")
6
+ .replace(new RegExp(`[^a-zA-Z0-9${allowedChars}]`, "g"), "");
7
+ export const sanitizeString = (str) => normalizeString(str, "\\[\\]");
8
+ export const sanitizeStringWithSpaces = (str) => normalizeString(str, "\\s\\[\\]");
@@ -0,0 +1,13 @@
1
+ import { Fnode } from "@protontech/fathom";
2
+ type VisibilityCache = WeakMap<HTMLElement, boolean>;
3
+ type IsVisibleOptions = {
4
+ opacity: boolean;
5
+ };
6
+ export declare const cacheContext: Record<string, VisibilityCache>;
7
+ export declare const getVisibilityCache: (key: string) => VisibilityCache;
8
+ export declare const clearVisibilityCache: () => void;
9
+ export declare const isVisible: (fnodeOrElement: Fnode | HTMLElement, options: IsVisibleOptions) => boolean;
10
+ export declare const isVisibleEl: (el: HTMLElement) => boolean;
11
+ export declare const isVisibleForm: (form: HTMLElement) => boolean;
12
+ export declare const isVisibleField: (field: HTMLElement) => boolean;
13
+ export {};
@@ -0,0 +1,143 @@
1
+ import { utils } from "@protontech/fathom";
2
+ import { MIN_FIELD_HEIGHT, MIN_FIELD_WIDTH } from "../constants/heuristics";
3
+ import { inputCandidateSelector } from "../constants/selectors";
4
+ import { any } from "./combinators";
5
+ import { flagAsHidden } from "./flags";
6
+ import { matchHidden } from "./re";
7
+ import { sanitizeStringWithSpaces } from "./text";
8
+ export const cacheContext = {};
9
+ export const getVisibilityCache = (key) => { var _a; return (cacheContext[key] = (_a = cacheContext[key]) !== null && _a !== void 0 ? _a : new WeakMap()); };
10
+ export const clearVisibilityCache = () => Object.keys(cacheContext).forEach((key) => delete cacheContext[key]);
11
+ const SCROLLBAR_WIDTH = 16;
12
+ const getCachedVisbility = (el, options) => {
13
+ var _a;
14
+ const opacityCache = getVisibilityCache("visibility:op");
15
+ const cache = getVisibilityCache("visibility");
16
+ if (options.opacity)
17
+ return (_a = opacityCache.get(el)) !== null && _a !== void 0 ? _a : cache.get(el);
18
+ else
19
+ return cache.get(el);
20
+ };
21
+ const setCachedVisibility = (cacheMap) => (els, visible) => els.forEach((el) => cacheMap.set(el, visible));
22
+ const containedInAncestor = (rect, ancestorRect) => rect.top <= ancestorRect.bottom && rect.bottom >= ancestorRect.top && rect.left <= ancestorRect.right && rect.right >= ancestorRect.left;
23
+ const isNegligibleRect = (rect) => rect.width <= 1 || rect.height <= 1;
24
+ export const isVisible = (fnodeOrElement, options) => {
25
+ const element = utils.toDomElement(fnodeOrElement);
26
+ const seen = [];
27
+ let transparent = false;
28
+ const cache = getVisibilityCache(options.opacity ? "visibility:op" : "visibility");
29
+ const cachedVisibility = getCachedVisbility(element, options);
30
+ if (cachedVisibility !== undefined)
31
+ return cachedVisibility;
32
+ const win = utils.windowForElement(element);
33
+ const doc = win.document;
34
+ const viewportWidth = win.innerWidth || doc.documentElement.clientWidth;
35
+ const viewportHeight = win.innerHeight || doc.documentElement.clientHeight;
36
+ const scrollWidth = Math.max(viewportWidth, doc.documentElement.scrollWidth, doc.body.scrollWidth);
37
+ const scrollHeight = Math.max(viewportHeight, doc.documentElement.scrollHeight, doc.body.scrollHeight);
38
+ const { scrollX, scrollY } = win;
39
+ const isOnScreen = ({ x, y, width, height }) => {
40
+ const left = x + scrollX;
41
+ const right = x + scrollX + width;
42
+ const top = y + scrollY;
43
+ const bottom = y + scrollY + height;
44
+ return right >= 0 && left <= scrollWidth - SCROLLBAR_WIDTH && bottom >= 0 && top <= scrollHeight - SCROLLBAR_WIDTH;
45
+ };
46
+ const check = () => {
47
+ var _a;
48
+ let prevRef = null;
49
+ for (const ancestor of utils.ancestors(element)) {
50
+ let rect = null;
51
+ const getRect = () => (rect = rect !== null && rect !== void 0 ? rect : ancestor.getBoundingClientRect());
52
+ if (ancestor === doc.body)
53
+ return (prevRef === null || prevRef === void 0 ? void 0 : prevRef.absolute) ? isOnScreen(prevRef.rect) : true;
54
+ const cachedVisibility = getCachedVisbility(ancestor, options);
55
+ if (cachedVisibility !== undefined)
56
+ return cachedVisibility;
57
+ const { opacity, display, position, overflow, visibility } = win.getComputedStyle(ancestor);
58
+ seen.push(ancestor);
59
+ if (opacity === "0" && options.opacity) {
60
+ transparent = true;
61
+ return false;
62
+ }
63
+ if (visibility === "hidden")
64
+ return false;
65
+ if (overflow === "hidden") {
66
+ if ((prevRef === null || prevRef === void 0 ? void 0 : prevRef.rect) && !containedInAncestor(prevRef.rect, getRect()))
67
+ return false;
68
+ if (isNegligibleRect(getRect()))
69
+ return false;
70
+ }
71
+ if ((prevRef === null || prevRef === void 0 ? void 0 : prevRef.absolute) && position === "static") {
72
+ seen.pop();
73
+ continue;
74
+ }
75
+ if (position === "absolute" && !isOnScreen(getRect()))
76
+ return false;
77
+ if (position === "fixed")
78
+ return isOnScreen((_a = prevRef === null || prevRef === void 0 ? void 0 : prevRef.rect) !== null && _a !== void 0 ? _a : getRect());
79
+ if (display === "contents") {
80
+ prevRef = null;
81
+ continue;
82
+ }
83
+ prevRef = prevRef !== null && prevRef !== void 0 ? prevRef : { rect: getRect() };
84
+ prevRef.rect = getRect();
85
+ prevRef.absolute = position === "absolute";
86
+ continue;
87
+ }
88
+ return true;
89
+ };
90
+ const visible = check();
91
+ if (options.opacity) {
92
+ if (visible || !transparent)
93
+ setCachedVisibility(getVisibilityCache("visibility"))(seen, visible);
94
+ else
95
+ setCachedVisibility(getVisibilityCache("visibility"))(seen.slice(0, -1), visible);
96
+ }
97
+ setCachedVisibility(cache)(seen, visible);
98
+ return visible;
99
+ };
100
+ const quickVisibilityCheck = (el, options) => {
101
+ const cache = getVisibilityCache(`${options.minWidth}:${options.minHeight}`);
102
+ if (cache.has(el))
103
+ return cache.get(el);
104
+ const check = () => {
105
+ const rect = el.getClientRects();
106
+ if (rect.length === 0)
107
+ return false;
108
+ const classList = Array.from(el.classList).map(sanitizeStringWithSpaces);
109
+ if (any(matchHidden)(classList))
110
+ return false;
111
+ const { visibility, display, maxHeight } = getComputedStyle(el);
112
+ if (visibility === "hidden" || display === "none" || maxHeight === "0px")
113
+ return false;
114
+ if (el.offsetHeight === 0 || el.offsetWidth === 0)
115
+ return false;
116
+ if (el.offsetHeight < options.minHeight || el.offsetWidth < options.minWidth)
117
+ return false;
118
+ return true;
119
+ };
120
+ const visible = check();
121
+ setCachedVisibility(cache)([el], visible);
122
+ return visible;
123
+ };
124
+ export const isVisibleEl = (el) => quickVisibilityCheck(el, { minHeight: 0, minWidth: 0 });
125
+ export const isVisibleForm = (form) => {
126
+ const visible = (() => {
127
+ if (!isVisible(form, { opacity: true }))
128
+ return false;
129
+ const inputs = Array.from(form.querySelectorAll(inputCandidateSelector)).filter((field) => !field.disabled);
130
+ return inputs.length > 0 && inputs.some((input) => isVisible(input, { opacity: false }));
131
+ })();
132
+ if (!visible)
133
+ flagAsHidden(form);
134
+ return visible;
135
+ };
136
+ export const isVisibleField = (field) => {
137
+ if (field instanceof HTMLInputElement) {
138
+ const { type, disabled, readOnly } = field;
139
+ if (type === "hidden" || disabled || readOnly || field.getAttribute("aria-hidden") === "true")
140
+ return false;
141
+ }
142
+ return quickVisibilityCheck(field, { minHeight: MIN_FIELD_HEIGHT, minWidth: MIN_FIELD_WIDTH });
143
+ };