@protontech/autofill 0.0.22991789 → 0.0.33835493
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/constants/features.d.ts +1 -0
- package/constants/features.js +1 -0
- package/constants/heuristics.d.ts +5 -4
- package/constants/heuristics.js +5 -10
- package/constants/selectors.d.ts +13 -7
- package/constants/selectors.js +29 -17
- package/debug.d.ts +24 -1
- package/debug.js +15 -13
- package/dictionary/generate.js +30 -21
- package/dictionary/generated/dictionary.d.ts +18 -4
- package/dictionary/generated/dictionary.js +31 -17
- package/dictionary/source/dictionary.d.ts +4 -3
- package/dictionary/source/dictionary.js +155 -89
- package/dictionary/source/patterns.js +2 -2
- package/features/feature.d.ts +18 -0
- package/features/feature.js +79 -0
- package/features/feature.spec.d.ts +1 -0
- package/features/feature.spec.js +55 -0
- package/features/v1/abstract.field.d.ts +20401 -0
- package/features/v1/abstract.field.js +95 -0
- package/features/v1/abstract.form.d.ts +1226 -0
- package/features/v1/abstract.form.js +336 -0
- package/features/v1/field.email.d.ts +62 -0
- package/features/v1/field.email.js +27 -0
- package/features/v1/field.otp.d.ts +142 -0
- package/features/v1/field.otp.js +105 -0
- package/features/v1/field.password.d.ts +162 -0
- package/features/v1/field.password.js +81 -0
- package/features/v1/field.username-hidden.d.ts +62 -0
- package/features/v1/field.username-hidden.js +25 -0
- package/features/v1/field.username.d.ts +72 -0
- package/features/v1/field.username.js +31 -0
- package/features/v1/index.d.ts +4428 -0
- package/features/v1/index.js +22 -0
- package/features/v1/index.spec.d.ts +1 -0
- package/features/v1/index.spec.js +30 -0
- package/index.d.ts +7 -2
- package/index.js +11 -3
- package/models/perceptron/index.d.ts +2 -0
- package/models/perceptron/index.js +42 -0
- package/models/perceptron/params/email-model.json +53 -0
- package/models/perceptron/params/login-model.json +465 -0
- package/models/perceptron/params/new-password-model.json +133 -0
- package/models/perceptron/params/otp-model.json +117 -0
- package/models/perceptron/params/password-change-model.json +465 -0
- package/models/perceptron/params/password-model.json +133 -0
- package/models/perceptron/params/recovery-model.json +465 -0
- package/models/perceptron/params/register-model.json +465 -0
- package/models/perceptron/params/username-hidden-model.json +53 -0
- package/models/perceptron/params/username-model.json +61 -0
- package/models/random_forest/index.d.ts +2 -0
- package/models/random_forest/index.js +42 -0
- package/models/random_forest/params/email-model.json +1456 -0
- package/models/random_forest/params/login-model.json +4194 -0
- package/models/random_forest/params/new-password-model.json +1448 -0
- package/models/random_forest/params/otp-model.json +2004 -0
- package/models/random_forest/params/password-change-model.json +1422 -0
- package/models/random_forest/params/password-model.json +1292 -0
- package/models/random_forest/params/recovery-model.json +2754 -0
- package/models/random_forest/params/register-model.json +3678 -0
- package/models/random_forest/params/username-hidden-model.json +1108 -0
- package/models/random_forest/params/username-model.json +1052 -0
- package/package.json +17 -15
- package/rules/v1/index.d.ts +4 -0
- package/rules/v1/index.js +66 -0
- package/rulesets.d.ts +9 -2
- package/rulesets.js +14 -9
- package/types/index.d.ts +70 -17
- package/types/index.js +34 -1
- package/utils/attributes.js +1 -1
- package/utils/clustering.js +18 -5
- package/utils/credit-card.d.ts +32 -0
- package/utils/credit-card.js +259 -0
- package/utils/credit-card.samples.spec.d.ts +1 -0
- package/utils/credit-card.samples.spec.js +452 -0
- package/utils/credit-card.spec.d.ts +1 -0
- package/utils/credit-card.spec.js +296 -0
- package/utils/dom.d.ts +3 -2
- package/utils/dom.js +12 -7
- package/utils/exclusion.d.ts +1 -0
- package/utils/exclusion.js +22 -10
- package/utils/extract.d.ts +1 -0
- package/utils/extract.js +26 -7
- package/utils/fathom.d.ts +10 -23
- package/utils/fathom.js +7 -12
- package/utils/field.d.ts +12 -4
- package/utils/field.js +25 -14
- package/utils/flags.d.ts +9 -3
- package/utils/flags.js +27 -9
- package/utils/form.d.ts +16 -5
- package/utils/form.js +35 -14
- package/utils/identity.d.ts +12 -21
- package/utils/identity.js +66 -41
- package/utils/identity.samples.spec.d.ts +1 -0
- package/utils/identity.samples.spec.js +28 -0
- package/utils/iframe.d.ts +2 -0
- package/utils/iframe.js +22 -0
- package/utils/overrides.d.ts +19 -0
- package/utils/overrides.js +40 -0
- package/utils/prepass.js +6 -4
- package/utils/re.d.ts +19 -4
- package/utils/re.js +22 -7
- package/utils/re.spec.d.ts +1 -0
- package/utils/re.spec.js +62 -0
- package/utils/shadow-dom.d.ts +8 -0
- package/utils/shadow-dom.js +53 -0
- package/utils/shadow-dom.spec.d.ts +1 -0
- package/utils/shadow-dom.spec.js +215 -0
- package/utils/visible.d.ts +3 -2
- package/utils/visible.js +42 -22
- package/cli.d.ts +0 -2
- package/cli.js +0 -128
- package/features/abstract.field.d.ts +0 -123
- package/features/abstract.field.js +0 -63
- package/features/abstract.form.d.ts +0 -98
- package/features/abstract.form.js +0 -281
- package/features/field.email.d.ts +0 -18
- package/features/field.email.js +0 -43
- package/features/field.otp.d.ts +0 -36
- package/features/field.otp.js +0 -116
- package/features/field.password.d.ts +0 -35
- package/features/field.password.js +0 -104
- package/features/field.username-hidden.d.ts +0 -15
- package/features/field.username-hidden.js +0 -40
- package/features/field.username.d.ts +0 -16
- package/features/field.username.js +0 -41
- package/features/form.combined.d.ts +0 -1
- package/features/form.combined.js +0 -6
- package/trainees/field.email.d.ts +0 -2
- package/trainees/field.email.js +0 -16
- package/trainees/field.identity.d.ts +0 -2
- package/trainees/field.identity.js +0 -9
- package/trainees/field.otp.d.ts +0 -2
- package/trainees/field.otp.js +0 -16
- package/trainees/field.password.current.d.ts +0 -2
- package/trainees/field.password.current.js +0 -16
- package/trainees/field.password.new.d.ts +0 -2
- package/trainees/field.password.new.js +0 -16
- package/trainees/field.username-hidden.d.ts +0 -2
- package/trainees/field.username-hidden.js +0 -22
- package/trainees/field.username.d.ts +0 -2
- package/trainees/field.username.js +0 -16
- package/trainees/form.login.d.ts +0 -2
- package/trainees/form.login.js +0 -16
- package/trainees/form.noop.d.ts +0 -1
- package/trainees/form.noop.js +0 -7
- package/trainees/form.password-change.d.ts +0 -2
- package/trainees/form.password-change.js +0 -16
- package/trainees/form.recovery.d.ts +0 -2
- package/trainees/form.recovery.js +0 -16
- package/trainees/form.register.d.ts +0 -2
- package/trainees/form.register.js +0 -16
- package/trainees/index.d.ts +0 -9
- package/trainees/index.js +0 -72
- package/trainees/results/result.email.d.ts +0 -2
- package/trainees/results/result.email.js +0 -17
- package/trainees/results/result.login.d.ts +0 -2
- package/trainees/results/result.login.js +0 -110
- package/trainees/results/result.new-password.d.ts +0 -2
- package/trainees/results/result.new-password.js +0 -36
- package/trainees/results/result.otp.d.ts +0 -2
- package/trainees/results/result.otp.js +0 -43
- package/trainees/results/result.password-change.d.ts +0 -2
- package/trainees/results/result.password-change.js +0 -110
- package/trainees/results/result.password.d.ts +0 -2
- package/trainees/results/result.password.js +0 -36
- package/trainees/results/result.recovery.d.ts +0 -2
- package/trainees/results/result.recovery.js +0 -110
- package/trainees/results/result.register.d.ts +0 -2
- package/trainees/results/result.register.js +0 -110
- package/trainees/results/result.username-hidden.d.ts +0 -2
- package/trainees/results/result.username-hidden.js +0 -15
- package/trainees/results/result.username.d.ts +0 -2
- package/trainees/results/result.username.js +0 -16
- package/utils/memoize.d.ts +0 -5
- package/utils/memoize.js +0 -12
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import { formatExpirationDate, getExpirationFormat, getInputExpirationMonthFormat, getSelectExpirationMonthFormat, getSelectExpirationYearFormat } from "./credit-card";
|
|
2
|
+
Object.defineProperty(HTMLElement.prototype, "innerText", {
|
|
3
|
+
get() {
|
|
4
|
+
return this.textContent;
|
|
5
|
+
},
|
|
6
|
+
});
|
|
7
|
+
describe("CC Expiration Detection", () => {
|
|
8
|
+
afterEach(() => {
|
|
9
|
+
document.body.innerHTML = "";
|
|
10
|
+
});
|
|
11
|
+
describe("Pattern Detection", () => {
|
|
12
|
+
test.each([
|
|
13
|
+
{ pattern: "\\d{2}/\\d{2}", expected: { separator: "/", fullYear: false, monthFirst: true } },
|
|
14
|
+
{ pattern: "[0-9]{2}/[0-9]{2}", expected: { separator: "/", fullYear: false, monthFirst: true } },
|
|
15
|
+
{ pattern: "(0[1-9]|1[0-2])\\/[0-9]{2}", expected: { separator: "/", fullYear: false, monthFirst: true } },
|
|
16
|
+
{ pattern: "\\d{2}/\\d{4}", expected: { separator: "/", fullYear: true, monthFirst: true } },
|
|
17
|
+
{ pattern: "[0-9]{2}/[0-9]{4}", expected: { separator: "/", fullYear: true, monthFirst: true } },
|
|
18
|
+
{ pattern: "(0[1-9]|1[0-2])\\/[0-9]{4}", expected: { separator: "/", fullYear: true, monthFirst: true } },
|
|
19
|
+
{ pattern: "\\d{2}-\\d{2}", expected: { separator: "-", fullYear: false, monthFirst: true } },
|
|
20
|
+
{ pattern: "[0-9]{2}-[0-9]{2}", expected: { separator: "-", fullYear: false, monthFirst: true } },
|
|
21
|
+
{ pattern: "\\d{2}-\\d{4}", expected: { separator: "-", fullYear: true, monthFirst: true } },
|
|
22
|
+
{ pattern: "[0-9]{2}-[0-9]{4}", expected: { separator: "-", fullYear: true, monthFirst: true } },
|
|
23
|
+
{ pattern: "\\d{4}", expected: { separator: "", fullYear: false, monthFirst: true } },
|
|
24
|
+
{ pattern: "[0-9]{4}", expected: { separator: "", fullYear: false, monthFirst: true } },
|
|
25
|
+
{ pattern: "\\d{6}", expected: { separator: "", fullYear: true, monthFirst: true } },
|
|
26
|
+
{ pattern: "[0-9]{6}", expected: { separator: "", fullYear: true, monthFirst: true } },
|
|
27
|
+
{ pattern: "\\d{2}/\\d{2}", expected: { separator: "/", fullYear: false, monthFirst: true } },
|
|
28
|
+
])("should detect pattern $pattern", ({ pattern, expected }) => {
|
|
29
|
+
const input = document.createElement("input");
|
|
30
|
+
input.pattern = pattern;
|
|
31
|
+
expect(getExpirationFormat(input)).toEqual(expected);
|
|
32
|
+
});
|
|
33
|
+
test.each([{ pattern: "[invalid regex" }, { pattern: "*+?" }, { pattern: "(" }, { pattern: "[" }])("should handle invalid regex pattern: $pattern", ({ pattern }) => {
|
|
34
|
+
const input = document.createElement("input");
|
|
35
|
+
input.pattern = pattern;
|
|
36
|
+
expect(getExpirationFormat(input)).toEqual({ separator: "/", fullYear: false, monthFirst: true });
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
describe("MaxLength Detection", () => {
|
|
40
|
+
test.each([
|
|
41
|
+
{ maxLength: 4, expected: { separator: "", fullYear: false, monthFirst: true } },
|
|
42
|
+
{ maxLength: 5, expected: { separator: "/", fullYear: false, monthFirst: true } },
|
|
43
|
+
{ maxLength: 6, expected: { separator: "", fullYear: true, monthFirst: true } },
|
|
44
|
+
{ maxLength: 7, expected: { separator: "/", fullYear: true, monthFirst: true } },
|
|
45
|
+
])("should detect maxLength $maxLength", ({ maxLength, expected }) => {
|
|
46
|
+
const input = document.createElement("input");
|
|
47
|
+
input.maxLength = maxLength;
|
|
48
|
+
const result = getExpirationFormat(input);
|
|
49
|
+
expect(result).toEqual(expected);
|
|
50
|
+
});
|
|
51
|
+
test.each([{ maxLength: 1 }, { maxLength: 2 }, { maxLength: 3 }, { maxLength: 8 }, { maxLength: 10 }])("should return default for unmapped maxLength: $maxLength", ({ maxLength }) => {
|
|
52
|
+
const input = document.createElement("input");
|
|
53
|
+
input.maxLength = maxLength;
|
|
54
|
+
expect(getExpirationFormat(input)).toEqual({ separator: "/", fullYear: false, monthFirst: true });
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
describe("Attribute Detection", () => {
|
|
58
|
+
test.each([
|
|
59
|
+
{ attr: "placeholder", value: "MM/YY", expected: { separator: "/", fullYear: false, monthFirst: true } },
|
|
60
|
+
{ attr: "placeholder", value: "MM/YYYY", expected: { separator: "/", fullYear: true, monthFirst: true } },
|
|
61
|
+
{ attr: "placeholder", value: "MM-YY", expected: { separator: "-", fullYear: false, monthFirst: true } },
|
|
62
|
+
{ attr: "placeholder", value: "MM-YYYY", expected: { separator: "-", fullYear: true, monthFirst: true } },
|
|
63
|
+
{ attr: "placeholder", value: "MM YY", expected: { separator: "", fullYear: false, monthFirst: true } },
|
|
64
|
+
{ attr: "placeholder", value: "MM YYYY", expected: { separator: "", fullYear: true, monthFirst: true } },
|
|
65
|
+
{ attr: "placeholder", value: "MMYY", expected: { separator: "", fullYear: false, monthFirst: true } },
|
|
66
|
+
{ attr: "placeholder", value: "MMYYYY", expected: { separator: "", fullYear: true, monthFirst: true } },
|
|
67
|
+
{ attr: "placeholder", value: "YY/MM", expected: { separator: "/", fullYear: false, monthFirst: false } },
|
|
68
|
+
{ attr: "placeholder", value: "YYYY/MM", expected: { separator: "/", fullYear: true, monthFirst: false } },
|
|
69
|
+
{ attr: "placeholder", value: "YY-MM", expected: { separator: "-", fullYear: false, monthFirst: false } },
|
|
70
|
+
{ attr: "placeholder", value: "YYYY-MM", expected: { separator: "-", fullYear: true, monthFirst: false } },
|
|
71
|
+
{ attr: "placeholder", value: "YY MM", expected: { separator: "", fullYear: false, monthFirst: false } },
|
|
72
|
+
{ attr: "placeholder", value: "YYYY MM", expected: { separator: "", fullYear: true, monthFirst: false } },
|
|
73
|
+
{ attr: "placeholder", value: "YYMM", expected: { separator: "", fullYear: false, monthFirst: false } },
|
|
74
|
+
{ attr: "placeholder", value: "YYYYMM", expected: { separator: "", fullYear: true, monthFirst: false } },
|
|
75
|
+
{ attr: "placeholder", value: "MM/AA", expected: { separator: "/", fullYear: false, monthFirst: true } },
|
|
76
|
+
{ attr: "placeholder", value: "MM/AAAA", expected: { separator: "/", fullYear: true, monthFirst: true } },
|
|
77
|
+
{ attr: "placeholder", value: "AA/MM", expected: { separator: "/", fullYear: false, monthFirst: false } },
|
|
78
|
+
{ attr: "placeholder", value: "AAAA/MM", expected: { separator: "/", fullYear: true, monthFirst: false } },
|
|
79
|
+
{ attr: "name", value: "exp-mm-yyyy", expected: { separator: "-", fullYear: true, monthFirst: true } },
|
|
80
|
+
{ attr: "id", value: "cardExpMmYy", expected: { separator: "", fullYear: false, monthFirst: true } },
|
|
81
|
+
{ attr: "className", value: "exp-field mm/yyyy-format", expected: { separator: "/", fullYear: true, monthFirst: true } },
|
|
82
|
+
])('should detect $attr="$value"', ({ attr, value, expected }) => {
|
|
83
|
+
const input = document.createElement("input");
|
|
84
|
+
if (attr === "className")
|
|
85
|
+
input.className = value;
|
|
86
|
+
else
|
|
87
|
+
input.setAttribute(attr, value);
|
|
88
|
+
expect(getExpirationFormat(input)).toEqual(expected);
|
|
89
|
+
});
|
|
90
|
+
test.each([{ value: "Enter expiration MM/YY date" }, { value: "Card expires MM/YYYY" }, { value: "exp_field_mm_yyyy_format" }, { value: "EXPIRY DATE (MM/YY)" }, { value: "Please enter MM-YYYY" }])('should detect format in mixed text: "$value"', ({ value }) => {
|
|
91
|
+
const input = document.createElement("input");
|
|
92
|
+
input.placeholder = value;
|
|
93
|
+
const result = getExpirationFormat(input);
|
|
94
|
+
expect(result === null || result === void 0 ? void 0 : result.separator).toBeDefined();
|
|
95
|
+
expect(result === null || result === void 0 ? void 0 : result.fullYear).toBeDefined();
|
|
96
|
+
expect(result === null || result === void 0 ? void 0 : result.monthFirst).toBeDefined();
|
|
97
|
+
});
|
|
98
|
+
test.each([{ value: "email@example.com" }, { value: "Enter your name" }, { value: "Phone number" }, { value: "Address line 1" }, { value: "" }, { value: "XYZZYX" }])('should return default for non-expiry text: "$value"', ({ value }) => {
|
|
99
|
+
const input = document.createElement("input");
|
|
100
|
+
input.placeholder = value;
|
|
101
|
+
const result = getExpirationFormat(input);
|
|
102
|
+
expect(result).toEqual({ separator: "/", fullYear: false, monthFirst: true });
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
describe("getExpirationFormat - Priority Order", () => {
|
|
106
|
+
test("should prioritize attributes over pattern", () => {
|
|
107
|
+
const input = document.createElement("input");
|
|
108
|
+
input.placeholder = "MM-YYYY";
|
|
109
|
+
input.pattern = "\\d{2}/\\d{2}";
|
|
110
|
+
const result = getExpirationFormat(input);
|
|
111
|
+
expect(result).toEqual({ separator: "-", fullYear: true, monthFirst: true });
|
|
112
|
+
});
|
|
113
|
+
test("should prioritize pattern over maxLength", () => {
|
|
114
|
+
const input = document.createElement("input");
|
|
115
|
+
input.pattern = "\\d{2}-\\d{4}";
|
|
116
|
+
input.maxLength = 5;
|
|
117
|
+
const result = getExpirationFormat(input);
|
|
118
|
+
expect(result).toEqual({ separator: "-", fullYear: true, monthFirst: true });
|
|
119
|
+
});
|
|
120
|
+
test("should use maxLength when no attributes or pattern", () => {
|
|
121
|
+
const input = document.createElement("input");
|
|
122
|
+
input.maxLength = 7;
|
|
123
|
+
const result = getExpirationFormat(input);
|
|
124
|
+
expect(result).toEqual({ separator: "/", fullYear: true, monthFirst: true });
|
|
125
|
+
});
|
|
126
|
+
test("should use default when no indicators present", () => {
|
|
127
|
+
const input = document.createElement("input");
|
|
128
|
+
const result = getExpirationFormat(input);
|
|
129
|
+
expect(result).toEqual({ separator: "/", fullYear: false, monthFirst: true });
|
|
130
|
+
});
|
|
131
|
+
test("should handle outliers", () => {
|
|
132
|
+
const input = document.createElement("input");
|
|
133
|
+
input.pattern = "[-+]?[0-9]*[.,]?[0-9]+";
|
|
134
|
+
input.maxLength = 2;
|
|
135
|
+
input.autocomplete = "cc-exp-month";
|
|
136
|
+
input.ariaLabel = "month";
|
|
137
|
+
const result = getExpirationFormat(input, false);
|
|
138
|
+
expect(result).toEqual(undefined);
|
|
139
|
+
});
|
|
140
|
+
test("coursera.org > cc-exp format", () => {
|
|
141
|
+
document.body.innerHTML = `<div data-field="expiry" class="p-Field"><label class="p-FieldLabel Label Label--empty" for="Field-expiryInput">Expiration date<span class="FadeWrapper"></span><span class="u-visually-hidden"> MM / YY</span></label><div><div><div class="p-Input"><input dir="ltr" type="text" inputmode="numeric" name="expiry" id="Field-expiryInput" placeholder="MM / YY" autocomplete="cc-exp" aria-invalid="false" aria-describedby="Field-expiryError" aria-required="true" class="p-Input-input p-Fieldset-input Input Input--empty p-Input-input--textRight" value=""></div></div></div><div class="AnimateSinglePresence"></div></div>`;
|
|
142
|
+
const field = document.querySelector("select, input");
|
|
143
|
+
expect(getExpirationFormat(field, true)).toEqual({ fullYear: false, monthFirst: true, separator: "/" });
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
describe("getInputExpirationMonthFormat", () => {
|
|
147
|
+
test("tesco.com > padded cc-exp-month", () => {
|
|
148
|
+
document.body.innerHTML = `<fieldset class="Form__Fieldset-sc-1rpc86b-1 fKhXDo"><legend class="Label-sc-1wdkkfu-0 cEywXb ddl-label has-error">Expiry date</legend><label for="card-expiry-month" hidden="" class="Label-sc-1wdkkfu-0 mZWly sf-hidden">month</label><input type="tel" data-auto="card-expiry-month" name="expiryMonth" id="card-expiry-month" placeholder="MM" maxlength="2" aria-required="true" data-rule="expiryMonthRule" class="Input-sc-gj7hrn-1 expiry-date__ExpiryInput-sc-m6yl5f-0 bSmREw eDVddD has-error" data-errored="true" value="1"><span aria-hidden="true" class="expiry-date__ExpirySeparator-sc-m6yl5f-1 huzqtm">/</span></div></fieldset>`;
|
|
149
|
+
const field = document.querySelector("select, input");
|
|
150
|
+
expect(getInputExpirationMonthFormat(field)).toEqual({ padding: true });
|
|
151
|
+
});
|
|
152
|
+
test("orange.md > padded cc-exp-month", () => {
|
|
153
|
+
document.body.innerHTML = `<input name="validMONTH" id="expmonth" class="exp-input numberOnly" maxlength="2" tabindex="1" type="tel" placeholder="LL" required="" style="margin-right:10px" value="">`;
|
|
154
|
+
const field = document.querySelector("select, input");
|
|
155
|
+
expect(getInputExpirationMonthFormat(field)).toEqual({ padding: true });
|
|
156
|
+
});
|
|
157
|
+
test("iticket.md > padded cc-exp-month", () => {
|
|
158
|
+
document.body.innerHTML = `<div class="col-xs-6"><input name="validMONTH" id="expmonth" type="text" size="7" maxlength="2" autocomplete="off" pattern="\d*" value="1" placeholder="LL"><div class="image-help" style="display:none"></div></div>`;
|
|
159
|
+
const field = document.querySelector("select, input");
|
|
160
|
+
expect(getInputExpirationMonthFormat(field)).toEqual({ padding: true });
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
describe("formatExpirationDate", () => {
|
|
164
|
+
test.each([
|
|
165
|
+
{ month: "01", year: "2025", format: { separator: "/", fullYear: false, monthFirst: true }, expected: "01/25" },
|
|
166
|
+
{ month: "12", year: "2025", format: { separator: "-", fullYear: false, monthFirst: true }, expected: "12-25" },
|
|
167
|
+
{ month: "03", year: "2025", format: { separator: "", fullYear: false, monthFirst: true }, expected: "0325" },
|
|
168
|
+
{ month: "01", year: "2025", format: { separator: "/", fullYear: true, monthFirst: true }, expected: "01/2025" },
|
|
169
|
+
{ month: "12", year: "2025", format: { separator: "-", fullYear: true, monthFirst: true }, expected: "12-2025" },
|
|
170
|
+
{ month: "03", year: "2025", format: { separator: "", fullYear: true, monthFirst: true }, expected: "032025" },
|
|
171
|
+
{ month: "01", year: "2025", format: { separator: "/", fullYear: false, monthFirst: false }, expected: "25/01" },
|
|
172
|
+
{ month: "12", year: "2025", format: { separator: "-", fullYear: false, monthFirst: false }, expected: "25-12" },
|
|
173
|
+
{ month: "03", year: "2025", format: { separator: "", fullYear: false, monthFirst: false }, expected: "2503" },
|
|
174
|
+
{ month: "01", year: "2025", format: { separator: "/", fullYear: true, monthFirst: false }, expected: "2025/01" },
|
|
175
|
+
{ month: "12", year: "2025", format: { separator: "-", fullYear: true, monthFirst: false }, expected: "2025-12" },
|
|
176
|
+
{ month: "03", year: "2025", format: { separator: "", fullYear: true, monthFirst: false }, expected: "202503" },
|
|
177
|
+
])('should format month="$month" year="$year" with format=$format.separator$format.yearDigits$format.monthFirst to "$expected"', ({ month, year, format, expected }) => {
|
|
178
|
+
const result = formatExpirationDate(month, year, format);
|
|
179
|
+
expect(result).toBe(expected);
|
|
180
|
+
});
|
|
181
|
+
test.each([
|
|
182
|
+
{ month: "1", year: "2025", format: { separator: "/", fullYear: false, monthFirst: true }, expected: "01/25" },
|
|
183
|
+
{ month: "2", year: "2025", format: { separator: "/", fullYear: false, monthFirst: true }, expected: "02/25" },
|
|
184
|
+
{ month: "9", year: "2025", format: { separator: "/", fullYear: false, monthFirst: true }, expected: "09/25" },
|
|
185
|
+
{ month: "10", year: "2025", format: { separator: "/", fullYear: false, monthFirst: true }, expected: "10/25" },
|
|
186
|
+
{ month: "11", year: "2025", format: { separator: "/", fullYear: false, monthFirst: true }, expected: "11/25" },
|
|
187
|
+
{ month: "12", year: "2025", format: { separator: "/", fullYear: false, monthFirst: true }, expected: "12/25" },
|
|
188
|
+
])('should pad single digit month "$month" to two digits', ({ month, year, format, expected }) => {
|
|
189
|
+
const result = formatExpirationDate(month, year, format);
|
|
190
|
+
expect(result).toBe(expected);
|
|
191
|
+
});
|
|
192
|
+
test.each([
|
|
193
|
+
{ year: "2020", expected: "20" },
|
|
194
|
+
{ year: "2025", expected: "25" },
|
|
195
|
+
{ year: "2030", expected: "30" },
|
|
196
|
+
{ year: "2099", expected: "99" },
|
|
197
|
+
{ year: "2000", expected: "00" },
|
|
198
|
+
{ year: "2001", expected: "01" },
|
|
199
|
+
])('should extract last 2 digits from 4-digit year "$year"', ({ year, expected }) => {
|
|
200
|
+
const format = { separator: "/", fullYear: false, monthFirst: true };
|
|
201
|
+
const result = formatExpirationDate("01", year, format);
|
|
202
|
+
expect(result).toBe(`01/${expected}`);
|
|
203
|
+
});
|
|
204
|
+
test.each([
|
|
205
|
+
{ year: "2020", expected: "2020" },
|
|
206
|
+
{ year: "2025", expected: "2025" },
|
|
207
|
+
{ year: "2030", expected: "2030" },
|
|
208
|
+
{ year: "2099", expected: "2099" },
|
|
209
|
+
])('should keep all 4 digits for 4-digit year format "$year"', ({ year, expected }) => {
|
|
210
|
+
const format = { separator: "/", fullYear: true, monthFirst: true };
|
|
211
|
+
const result = formatExpirationDate("01", year, format);
|
|
212
|
+
expect(result).toBe(`01/${expected}`);
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
describe("Edge Cases and Integration", () => {
|
|
216
|
+
test("should handle case-insensitive attribute detection", () => {
|
|
217
|
+
const input = document.createElement("input");
|
|
218
|
+
input.placeholder = "mm/yyyy";
|
|
219
|
+
const result = getExpirationFormat(input);
|
|
220
|
+
expect(result).toEqual({ separator: "/", fullYear: true, monthFirst: true });
|
|
221
|
+
});
|
|
222
|
+
test("should combine multiple attributes", () => {
|
|
223
|
+
const input = document.createElement("input");
|
|
224
|
+
input.name = "expiry";
|
|
225
|
+
input.className = "mm-yyyy-format";
|
|
226
|
+
input.placeholder = "Enter MM-YYYY";
|
|
227
|
+
const result = getExpirationFormat(input);
|
|
228
|
+
expect(result === null || result === void 0 ? void 0 : result.separator).toBe("-");
|
|
229
|
+
expect(result === null || result === void 0 ? void 0 : result.fullYear).toBe(true);
|
|
230
|
+
expect(result === null || result === void 0 ? void 0 : result.monthFirst).toBe(true);
|
|
231
|
+
});
|
|
232
|
+
test("should handle whitespace in attributes", () => {
|
|
233
|
+
const input = document.createElement("input");
|
|
234
|
+
input.placeholder = " MM / YYYY ";
|
|
235
|
+
const result = getExpirationFormat(input);
|
|
236
|
+
expect(result === null || result === void 0 ? void 0 : result.separator).toBe("/");
|
|
237
|
+
});
|
|
238
|
+
test("should prefer first match when multiple formats present", () => {
|
|
239
|
+
const input = document.createElement("input");
|
|
240
|
+
input.placeholder = "MM/YY or MM-YYYY";
|
|
241
|
+
const result = getExpirationFormat(input);
|
|
242
|
+
expect(result === null || result === void 0 ? void 0 : result.separator).toBe("/");
|
|
243
|
+
expect(result === null || result === void 0 ? void 0 : result.fullYear).toBe(false);
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
describe("getSelectExpirationYearFormat", () => {
|
|
247
|
+
test.each([
|
|
248
|
+
{ options: ["2024", "2025", "2026"], expected: { fullYear: true } },
|
|
249
|
+
{ options: ["2023", "2024", "2025", "2026", "2027"], expected: { fullYear: true } },
|
|
250
|
+
{ options: ["24", "25", "26"], expected: { fullYear: false } },
|
|
251
|
+
{ options: ["23", "24", "25", "26", "27"], expected: { fullYear: false } },
|
|
252
|
+
])("should detect format from options $options", ({ options, expected }) => {
|
|
253
|
+
const select = document.createElement("select");
|
|
254
|
+
options.forEach((value) => {
|
|
255
|
+
const option = document.createElement("option");
|
|
256
|
+
option.value = value;
|
|
257
|
+
select.appendChild(option);
|
|
258
|
+
});
|
|
259
|
+
expect(getSelectExpirationYearFormat(select)).toEqual(expected);
|
|
260
|
+
});
|
|
261
|
+
test.each([{ options: [] }, { options: ["", ""] }, { options: ["Select year", "Choose..."] }, { options: ["abc", "def"] }])("should noop for invalid options $options", ({ options }) => {
|
|
262
|
+
const select = document.createElement("select");
|
|
263
|
+
options.forEach((value) => {
|
|
264
|
+
const option = document.createElement("option");
|
|
265
|
+
option.value = value;
|
|
266
|
+
select.appendChild(option);
|
|
267
|
+
});
|
|
268
|
+
expect(getSelectExpirationYearFormat(select)).toBeUndefined();
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
describe("getSelectExpirationMonthFormat", () => {
|
|
272
|
+
test.each([
|
|
273
|
+
{ options: ["01", "02", "03", "04", "05", "06"], expected: { padding: true } },
|
|
274
|
+
{ options: ["01", "09", "10", "11", "12"], expected: { padding: true } },
|
|
275
|
+
{ options: ["1", "2", "3", "4", "5", "6"], expected: { padding: false } },
|
|
276
|
+
{ options: ["1", "9", "10", "11", "12"], expected: { padding: false } },
|
|
277
|
+
])("should detect format from options $options", ({ options, expected }) => {
|
|
278
|
+
const select = document.createElement("select");
|
|
279
|
+
options.forEach((value) => {
|
|
280
|
+
const option = document.createElement("option");
|
|
281
|
+
option.value = value;
|
|
282
|
+
select.appendChild(option);
|
|
283
|
+
});
|
|
284
|
+
expect(getSelectExpirationMonthFormat(select)).toEqual(expected);
|
|
285
|
+
});
|
|
286
|
+
test.each([{ options: [] }, { options: ["", ""] }, { options: ["Select month", "Choose..."] }, { options: ["abc", "def"] }])("should return noop for invalid options $options", ({ options }) => {
|
|
287
|
+
const select = document.createElement("select");
|
|
288
|
+
options.forEach((value) => {
|
|
289
|
+
const option = document.createElement("option");
|
|
290
|
+
option.value = value;
|
|
291
|
+
select.appendChild(option);
|
|
292
|
+
});
|
|
293
|
+
expect(getSelectExpirationMonthFormat(select)).toBeUndefined();
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
});
|
package/utils/dom.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export declare const closestParent: (start: HTMLElement, match: (el: HTMLElement) => boolean) => HTMLElement | null;
|
|
2
|
-
export declare const
|
|
2
|
+
export declare const parentQuery: (start: HTMLElement, match: (parent: HTMLElement) => HTMLElement | null, maxIterations?: number) => HTMLElement | null;
|
|
3
3
|
export declare const walkUpWhile: (start: HTMLElement, maxIterations: number) => (check: (parent: HTMLElement, candidate: HTMLElement) => boolean) => HTMLElement;
|
|
4
4
|
export declare const getNthParent: (el: HTMLElement) => (n: number) => HTMLElement;
|
|
5
5
|
export declare const uniqueNodes: (...nodes: HTMLElement[][]) => HTMLElement[];
|
|
@@ -10,7 +10,8 @@ export declare const getNodeRect: (el: HTMLElement) => {
|
|
|
10
10
|
bottom: number;
|
|
11
11
|
area: number;
|
|
12
12
|
};
|
|
13
|
-
export declare const
|
|
13
|
+
export declare const matchPreviousSibling: (el: Element, match: (el: Element) => boolean) => Element | null;
|
|
14
|
+
export declare const matchPreviousNonEmptySibling: (el: Element) => Element | null;
|
|
14
15
|
export declare const getLabelFor: (el: HTMLElement) => HTMLElement | null;
|
|
15
16
|
export declare const getRectCenter: (rect: DOMRect) => {
|
|
16
17
|
x: number;
|
package/utils/dom.js
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
|
+
import { kFieldLabelSelector } from "@protontech/autofill/constants/selectors";
|
|
1
2
|
export const closestParent = (start, match) => {
|
|
2
3
|
const parent = start.parentElement;
|
|
3
4
|
if (!parent)
|
|
4
5
|
return null;
|
|
5
6
|
return match(parent) ? parent : closestParent(parent, match);
|
|
6
7
|
};
|
|
7
|
-
export const
|
|
8
|
+
export const parentQuery = (start, match, maxIterations = 1) => {
|
|
8
9
|
const parent = start === null || start === void 0 ? void 0 : start.parentElement;
|
|
9
10
|
if (!parent)
|
|
10
11
|
return null;
|
|
11
12
|
const result = match(parent);
|
|
12
|
-
return result || maxIterations <= 0 ? result :
|
|
13
|
+
return result || maxIterations <= 0 ? result : parentQuery(parent, match, maxIterations - 1);
|
|
13
14
|
};
|
|
14
15
|
export const walkUpWhile = (start, maxIterations) => (check) => {
|
|
15
16
|
const parent = start.parentElement;
|
|
@@ -27,14 +28,15 @@ export const getNodeRect = (el) => {
|
|
|
27
28
|
const area = height * width;
|
|
28
29
|
return { height, width, top, bottom, area };
|
|
29
30
|
};
|
|
30
|
-
export const
|
|
31
|
+
export const matchPreviousSibling = (el, match) => {
|
|
31
32
|
const prevEl = el.previousElementSibling;
|
|
32
33
|
if (prevEl === null)
|
|
33
34
|
return null;
|
|
34
35
|
if (match(prevEl))
|
|
35
36
|
return prevEl;
|
|
36
|
-
return
|
|
37
|
+
return matchPreviousSibling(prevEl, match);
|
|
37
38
|
};
|
|
39
|
+
export const matchPreviousNonEmptySibling = (el) => matchPreviousSibling(el, (el) => el instanceof HTMLElement && el.innerText.trim().length > 0);
|
|
38
40
|
export const getLabelFor = (el) => {
|
|
39
41
|
var _a;
|
|
40
42
|
const forId = (_a = el.getAttribute("id")) !== null && _a !== void 0 ? _a : el.getAttribute("name");
|
|
@@ -44,10 +46,13 @@ export const getLabelFor = (el) => {
|
|
|
44
46
|
const parentLabel = el.closest("label");
|
|
45
47
|
if (parentLabel)
|
|
46
48
|
return parentLabel;
|
|
47
|
-
const closestLabel =
|
|
49
|
+
const closestLabel = parentQuery(el, (parent) => parent.querySelector("label:not([for])"), 1);
|
|
48
50
|
if (closestLabel)
|
|
49
51
|
return closestLabel;
|
|
50
|
-
const
|
|
52
|
+
const labelLike = parentQuery(el, (parent) => parent.querySelector(kFieldLabelSelector), 1);
|
|
53
|
+
if (labelLike)
|
|
54
|
+
return labelLike;
|
|
55
|
+
const textNodeAbove = matchPreviousNonEmptySibling(el);
|
|
51
56
|
if (textNodeAbove)
|
|
52
57
|
return textNodeAbove;
|
|
53
58
|
return null;
|
|
@@ -100,5 +105,5 @@ const findStackedParent = (el, cache = [], maxIterations) => {
|
|
|
100
105
|
};
|
|
101
106
|
export const findStackedParents = (els, maxIterations) => {
|
|
102
107
|
const cache = [];
|
|
103
|
-
return els.map((input) => findStackedParent(input, cache, maxIterations)).filter((el) => Boolean(el));
|
|
108
|
+
return els.map((input) => findStackedParent(input, cache, maxIterations)).filter((el) => Boolean(el && el !== document.body));
|
|
104
109
|
};
|
package/utils/exclusion.d.ts
CHANGED
package/utils/exclusion.js
CHANGED
|
@@ -1,9 +1,18 @@
|
|
|
1
|
-
import { HIDDEN_FIELD_IGNORE_VALUES, MAX_FIELDS_PER_FORM, MAX_HIDDEN_FIELD_VALUE_LENGTH, MAX_INPUTS_PER_FORM, VALID_INPUT_TYPES
|
|
2
|
-
import { inputCandidateSelector, kEditorSelector, kFieldSelector, kHiddenUsernameSelector } from "
|
|
3
|
-
import { getNodeRect } from "
|
|
4
|
-
import { flagAsIgnored, flagSubtreeAsIgnored, isClassifiable } from "
|
|
1
|
+
import { HIDDEN_FIELD_IGNORE_VALUES, MAX_FIELDS_PER_FORM, MAX_HIDDEN_FIELD_VALUE_LENGTH, MAX_INPUTS_PER_FORM, VALID_INPUT_TYPES } from "@protontech/autofill/constants/heuristics";
|
|
2
|
+
import { inputCandidateSelector, kEditorSelector, kFieldSelector, kHiddenUsernameSelector } from "@protontech/autofill/constants/selectors";
|
|
3
|
+
import { getNodeRect } from "./dom";
|
|
4
|
+
import { flagAsIgnored, flagSubtreeAsIgnored, isClassifiable } from "./flags";
|
|
5
5
|
const TABLE_MAX_COLS = 3;
|
|
6
6
|
const TABLE_MAX_AREA = 150000;
|
|
7
|
+
const PAGE_FORM_RATIO = 0.7;
|
|
8
|
+
export const isTopFrame = () => {
|
|
9
|
+
try {
|
|
10
|
+
return window.self === window.top;
|
|
11
|
+
}
|
|
12
|
+
catch (_a) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
};
|
|
7
16
|
const nodeOfInterest = (el) => isClassifiable(el) && el.querySelector("input") !== null;
|
|
8
17
|
export const excludeForms = (doc = document) => {
|
|
9
18
|
const bodyElCount = document.body.querySelectorAll("*").length;
|
|
@@ -11,14 +20,17 @@ export const excludeForms = (doc = document) => {
|
|
|
11
20
|
if (nodeOfInterest(form)) {
|
|
12
21
|
const fieldCount = form.querySelectorAll(kFieldSelector).length;
|
|
13
22
|
const inputCount = form.querySelectorAll(inputCandidateSelector).length;
|
|
14
|
-
const
|
|
15
|
-
const
|
|
23
|
+
const iframeCount = form.querySelectorAll("iframe").length;
|
|
24
|
+
const invalidFieldCount = inputCount + iframeCount === 0 || inputCount > MAX_INPUTS_PER_FORM || fieldCount > MAX_FIELDS_PER_FORM;
|
|
25
|
+
const topFrame = isTopFrame();
|
|
16
26
|
const formElCount = form.querySelectorAll("*").length;
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
|
|
27
|
+
const pageFormMatch = topFrame && form.matches("body > form");
|
|
28
|
+
const pageFormSignal = topFrame && invalidFieldCount;
|
|
29
|
+
const pageForm = (pageFormMatch || pageFormSignal) && formElCount / bodyElCount >= PAGE_FORM_RATIO;
|
|
30
|
+
const invalidSignal = invalidFieldCount || pageForm;
|
|
31
|
+
if (invalidSignal && !pageForm)
|
|
20
32
|
return flagSubtreeAsIgnored(form);
|
|
21
|
-
if (
|
|
33
|
+
if (invalidSignal && pageForm)
|
|
22
34
|
return flagAsIgnored(form);
|
|
23
35
|
if (form.matches("table form") && form.closest("table").querySelectorAll("form").length > 2)
|
|
24
36
|
return flagAsIgnored(form);
|
package/utils/extract.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export declare const getAutocompletes: (field: HTMLElement | HTMLSelectElement) => string[];
|
|
1
2
|
export declare const getPageDescriptionText: (doc: Document) => string;
|
|
2
3
|
export declare const getNodeText: (node: HTMLElement) => string;
|
|
3
4
|
export declare const getNodeAttributes: (node: HTMLElement) => string;
|
package/utils/extract.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { MAX_FORM_HEADING_WALK_UP, MAX_HEADING_HORIZONTAL_DIST, MAX_HEADING_VERTICAL_DIST } from "@protontech/autofill/constants/heuristics";
|
|
2
|
+
import { kDomGroupSelector, kHeadingSelector } from "@protontech/autofill/constants/selectors";
|
|
1
3
|
import { getBaseAttributes, getFieldAttributes, getTextAttributes } from "./attributes";
|
|
2
|
-
import {
|
|
3
|
-
import { kDomGroupSelector, kHeadingSelector } from "../constants/selectors";
|
|
4
|
-
import { getLabelFor, getRectMinDistance, getSiblingWith, walkUpWhile } from "./dom";
|
|
4
|
+
import { getLabelFor, getRectMinDistance, matchPreviousNonEmptySibling, walkUpWhile } from "./dom";
|
|
5
5
|
import { sanitizeString, sanitizeStringWithSpaces } from "./text";
|
|
6
|
+
export const getAutocompletes = (field) => { var _a; return ((_a = field.getAttribute("autocomplete")) !== null && _a !== void 0 ? _a : "").split(/\s+/); };
|
|
6
7
|
export const getPageDescriptionText = (doc) => {
|
|
7
8
|
var _a;
|
|
8
9
|
const pageTitle = doc.title;
|
|
@@ -35,10 +36,19 @@ export const getFieldHaystacks = (field) => {
|
|
|
35
36
|
};
|
|
36
37
|
export const getAllFieldHaystacks = (field) => {
|
|
37
38
|
const { fieldAttrs, fieldText, labelText } = getFieldHaystacks(field);
|
|
38
|
-
|
|
39
|
+
if (field instanceof HTMLInputElement && ["checkbox", "submit", "button", "image"].includes(field.type)) {
|
|
40
|
+
if (field.value)
|
|
41
|
+
fieldAttrs.push(sanitizeString(field.value));
|
|
42
|
+
if (field.type === "image" && field.alt)
|
|
43
|
+
fieldAttrs.push(sanitizeString(field.alt));
|
|
44
|
+
}
|
|
45
|
+
const haystacks = [fieldText, labelText, ...fieldAttrs];
|
|
46
|
+
const autocomplete = field.getAttribute("autocomplete");
|
|
47
|
+
if (autocomplete)
|
|
48
|
+
haystacks.push(sanitizeString(autocomplete));
|
|
49
|
+
return haystacks;
|
|
39
50
|
};
|
|
40
51
|
export const getNearestHeadingsText = (el) => {
|
|
41
|
-
var _a, _b;
|
|
42
52
|
const originRect = el.getBoundingClientRect();
|
|
43
53
|
const parent = walkUpWhile(el, MAX_FORM_HEADING_WALK_UP)((parentEl, candidate) => {
|
|
44
54
|
if (parentEl === document.body)
|
|
@@ -54,6 +64,15 @@ export const getNearestHeadingsText = (el) => {
|
|
|
54
64
|
const { dx, dy } = getRectMinDistance(originRect, headingRect);
|
|
55
65
|
return dx < MAX_HEADING_HORIZONTAL_DIST && dy < MAX_HEADING_VERTICAL_DIST;
|
|
56
66
|
});
|
|
57
|
-
const textAbove = (
|
|
58
|
-
|
|
67
|
+
const textAbove = (() => {
|
|
68
|
+
var _a, _b, _c;
|
|
69
|
+
if (headings.length > 0)
|
|
70
|
+
return headings.map((el) => el.innerText).join();
|
|
71
|
+
const prevSibling = matchPreviousNonEmptySibling(el);
|
|
72
|
+
const parentFirstChild = (_a = el.parentElement) === null || _a === void 0 ? void 0 : _a.firstElementChild;
|
|
73
|
+
const prevSiblingText = (_b = prevSibling === null || prevSibling === void 0 ? void 0 : prevSibling.innerText) !== null && _b !== void 0 ? _b : "";
|
|
74
|
+
const parentFirstChildText = (_c = parentFirstChild === null || parentFirstChild === void 0 ? void 0 : parentFirstChild.innerText) !== null && _c !== void 0 ? _c : "";
|
|
75
|
+
return prevSibling === parentFirstChild ? prevSiblingText : prevSiblingText + parentFirstChildText;
|
|
76
|
+
})();
|
|
77
|
+
return sanitizeString(textAbove);
|
|
59
78
|
};
|
package/utils/fathom.d.ts
CHANGED
|
@@ -1,38 +1,25 @@
|
|
|
1
|
-
import { Fnode } from "@protontech/fathom";
|
|
2
|
-
import {
|
|
3
|
-
import { EmailFieldFeatures } from "../features/field.email";
|
|
4
|
-
import { OTPFieldFeatures } from "../features/field.otp";
|
|
5
|
-
import { PasswordFieldFeatures } from "../features/field.password";
|
|
6
|
-
import { UsernameFieldFeatures } from "../features/field.username";
|
|
7
|
-
import { HiddenUserFieldFeatures } from "../features/field.username-hidden";
|
|
8
|
-
type FeatureType = "form" | "password-field" | "username-field" | "username-hidden-field" | "email-field" | "otp-field";
|
|
9
|
-
type FeatureKey<T extends FeatureType> = {
|
|
10
|
-
form: FormFeatures;
|
|
11
|
-
"password-field": PasswordFieldFeatures;
|
|
12
|
-
"username-field": UsernameFieldFeatures;
|
|
13
|
-
"username-hidden-field": HiddenUserFieldFeatures;
|
|
14
|
-
"email-field": EmailFieldFeatures;
|
|
15
|
-
"otp-field": OTPFieldFeatures;
|
|
16
|
-
}[T];
|
|
1
|
+
import type { Fnode } from "@protontech/fathom";
|
|
2
|
+
import type { AbstractFeature } from "@protontech/autofill/types";
|
|
17
3
|
export declare const TOLERANCE_LEVEL = 0.5;
|
|
18
4
|
export declare const boolInt: (val: boolean) => number;
|
|
19
5
|
export declare const safeInt: (val: number, fallback?: number) => number;
|
|
20
6
|
export declare const typeEffect: (type: string) => (fnode: Fnode) => Fnode;
|
|
21
7
|
export declare const processFormEffect: (fnode: Fnode) => Fnode;
|
|
22
8
|
export declare const processFieldEffect: (fnode: Fnode) => Fnode;
|
|
23
|
-
|
|
9
|
+
type ClassNote = "form" | "new-password-field" | "password-field" | "username-field" | "username-hidden-field" | "email-field" | "otp-field";
|
|
10
|
+
export declare const featureScore: <F extends AbstractFeature>(noteFor: ClassNote, feat: F) => import("@protontech/fathom").Side;
|
|
24
11
|
export declare const getParentFormFnode: (fieldFnode: Fnode) => Fnode | null;
|
|
25
12
|
export declare const typeScoreToleranceTest: (type: string) => (fnode: Fnode) => boolean;
|
|
26
|
-
export declare const getTypeScore: (node: Fnode | null, type: string) =>
|
|
27
|
-
export declare const outRuleWithCache: (candidateType: string, predictionType: string, typeScoreTest?: (predictionType: string) => (fnode: Fnode) => boolean) =>
|
|
28
|
-
export declare const
|
|
29
|
-
export
|
|
30
|
-
export declare const getFormClassification: (formFnode: Fnode | null) => {
|
|
13
|
+
export declare const getTypeScore: (node: Fnode | null, type: string) => number;
|
|
14
|
+
export declare const outRuleWithCache: (candidateType: string, predictionType: string, typeScoreTest?: (predictionType: string) => (fnode: Fnode) => boolean) => (import("@protontech/fathom").OutwardRule | import("@protontech/fathom").InwardRule)[];
|
|
15
|
+
export declare const withFnodeEl: (fn: (el: HTMLElement) => boolean) => (fnode: Fnode) => boolean;
|
|
16
|
+
export type FormClassification = {
|
|
31
17
|
login: boolean;
|
|
32
18
|
register: boolean;
|
|
33
|
-
|
|
19
|
+
passwordChange: boolean;
|
|
34
20
|
recovery: boolean;
|
|
35
21
|
noop: boolean;
|
|
36
22
|
};
|
|
23
|
+
export declare const getFormClassification: (formFnode: Fnode | null) => FormClassification;
|
|
37
24
|
export declare const isNoopForm: (formFnode: Fnode) => boolean;
|
|
38
25
|
export {};
|
package/utils/fathom.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { out, rule, score, type } from "@protontech/fathom";
|
|
2
|
-
import { FormType } from "
|
|
2
|
+
import { FormType } from "@protontech/autofill/types";
|
|
3
3
|
import { flagAsProcessed, getCachedPredictionScore, getParentFormPrediction, isPredictedType, setCachedPredictionScore } from "./flags";
|
|
4
4
|
export const TOLERANCE_LEVEL = 0.5;
|
|
5
5
|
export const boolInt = (val) => Number(val);
|
|
@@ -20,11 +20,9 @@ export const processFieldEffect = throughEffect((fnode) => {
|
|
|
20
20
|
if (visible || type === "hidden")
|
|
21
21
|
flagAsProcessed(fnode.element);
|
|
22
22
|
});
|
|
23
|
-
export const featureScore = (noteFor,
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
return key.map((k) => features[k]).reduce((a, b) => a * b);
|
|
27
|
-
return features[key];
|
|
23
|
+
export const featureScore = (noteFor, feat) => score((fnode) => {
|
|
24
|
+
const note = fnode.noteFor(noteFor);
|
|
25
|
+
return Number(note[feat.name]);
|
|
28
26
|
});
|
|
29
27
|
export const getParentFormFnode = (fieldFnode) => {
|
|
30
28
|
const field = fieldFnode.element;
|
|
@@ -52,17 +50,14 @@ export const outRuleWithCache = (candidateType, predictionType, typeScoreTest =
|
|
|
52
50
|
rule(type(predictionType).when(typeScoreTest(predictionType)), type(`${predictionType}-prediction`), {}),
|
|
53
51
|
rule(type(`${predictionType}-prediction`), out(predictionType).through(typeEffect(predictionType)), {}),
|
|
54
52
|
];
|
|
55
|
-
export const combineFeatures = (arr1, arr2) => {
|
|
56
|
-
return arr1.flatMap((item1) => arr2.map((item2) => [item1, item2]));
|
|
57
|
-
};
|
|
58
53
|
export const withFnodeEl = (fn) => (fnode) => fn(fnode.element);
|
|
59
54
|
export const getFormClassification = (formFnode) => {
|
|
60
55
|
const login = getTypeScore(formFnode, FormType.LOGIN) > 0.5;
|
|
61
56
|
const register = getTypeScore(formFnode, FormType.REGISTER) > 0.5;
|
|
62
|
-
const
|
|
57
|
+
const passwordChange = getTypeScore(formFnode, FormType.PASSWORD_CHANGE) > 0.5;
|
|
63
58
|
const recovery = getTypeScore(formFnode, FormType.RECOVERY) > 0.5;
|
|
64
|
-
const detectionResults = [login, register,
|
|
59
|
+
const detectionResults = [login, register, passwordChange, recovery];
|
|
65
60
|
const noop = detectionResults.every((detected) => !detected);
|
|
66
|
-
return { login, register,
|
|
61
|
+
return { login, register, passwordChange, recovery, noop };
|
|
67
62
|
};
|
|
68
63
|
export const isNoopForm = (formFnode) => getFormClassification(formFnode).noop;
|
package/utils/field.d.ts
CHANGED
|
@@ -1,14 +1,22 @@
|
|
|
1
|
-
import { Fnode } from "@protontech/fathom";
|
|
1
|
+
import type { Fnode } from "@protontech/fathom";
|
|
2
|
+
import type { FieldType } from "@protontech/autofill/types";
|
|
3
|
+
export type HTMLFieldElement = HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement;
|
|
4
|
+
export declare const isInput: (el: HTMLElement) => el is HTMLInputElement;
|
|
5
|
+
export declare const isInputField: (el: HTMLElement) => el is HTMLInputElement;
|
|
6
|
+
export declare const isSelect: (el: HTMLElement) => el is HTMLSelectElement;
|
|
7
|
+
export declare const isTextarea: (el: HTMLElement) => el is HTMLTextAreaElement;
|
|
2
8
|
export declare const splitFieldsByVisibility: (els: HTMLElement[]) => [HTMLElement[], HTMLElement[]];
|
|
9
|
+
export declare const fType: (type: FieldType) => (fnode: Fnode) => boolean;
|
|
3
10
|
export declare const maybeEmail: (value: Fnode) => boolean;
|
|
4
11
|
export declare const maybePassword: (value: Fnode) => boolean;
|
|
5
12
|
export declare const maybeOTP: (value: Fnode) => boolean;
|
|
6
13
|
export declare const maybeUsername: (value: Fnode) => boolean;
|
|
7
14
|
export declare const maybeHiddenUsername: (value: Fnode) => boolean;
|
|
8
|
-
export declare const isUsernameCandidate: (el:
|
|
9
|
-
export declare const isEmailCandidate: (el:
|
|
15
|
+
export declare const isUsernameCandidate: (el: HTMLInputElement) => boolean;
|
|
16
|
+
export declare const isEmailCandidate: (el: HTMLInputElement) => boolean;
|
|
10
17
|
export declare const isOAuthCandidate: (el: HTMLElement) => boolean;
|
|
18
|
+
export declare const isMFACandidate: (el: HTMLFieldElement) => boolean;
|
|
11
19
|
export declare const isBtnCandidate: (btn: HTMLElement) => boolean;
|
|
12
|
-
export declare const isProcessableField: (input:
|
|
20
|
+
export declare const isProcessableField: (input: HTMLElement) => boolean;
|
|
13
21
|
export declare const isClassifiableField: (fnode: Fnode) => boolean;
|
|
14
22
|
export declare const selectInputCandidates: (target?: Document | HTMLElement) => HTMLInputElement[];
|