@protontech/autofill 0.0.35059265 → 0.0.36026908
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/features/feature.d.ts +13 -2
- package/features/feature.js +37 -15
- package/features/v1/abstract.field.d.ts +8406 -435
- package/features/v1/abstract.field.js +24 -24
- package/features/v1/abstract.form.d.ts +3428 -14
- package/features/v1/abstract.form.js +197 -197
- package/features/v1/field-predicates.d.ts +8 -0
- package/features/v1/field-predicates.js +42 -0
- package/features/v1/field.email.d.ts +3445 -24
- package/features/v1/field.email.js +7 -7
- package/features/v1/field.otp.d.ts +71234 -56
- package/features/v1/field.otp.js +42 -42
- package/features/v1/field.password.d.ts +99230 -64
- package/features/v1/field.password.js +42 -42
- package/features/v1/field.username-hidden.d.ts +932 -24
- package/features/v1/field.username-hidden.js +7 -7
- package/features/v1/field.username.d.ts +42410 -28
- package/features/v1/field.username.js +11 -11
- package/features/v1/fields.sorted.gen.d.ts +1441 -0
- package/features/v1/fields.sorted.gen.js +344 -0
- package/features/v1/forms.sorted.gen.d.ts +734 -0
- package/features/v1/forms.sorted.gen.js +334 -0
- package/features/v1/index.d.ts +5305 -12
- package/features/v1/index.js +7 -11
- package/models/perceptron/params/email-model.json +4 -4
- package/models/perceptron/params/login-model.json +97 -97
- package/models/perceptron/params/new-password-model.json +16 -16
- package/models/perceptron/params/otp-model.json +29 -29
- package/models/perceptron/params/password-change-model.json +69 -69
- package/models/perceptron/params/password-model.json +18 -18
- package/models/perceptron/params/recovery-model.json +92 -92
- package/models/perceptron/params/register-model.json +90 -90
- package/models/perceptron/params/username-hidden-model.json +7 -7
- package/models/perceptron/params/username-model.json +3 -3
- package/models/random_forest/params/otp-model.json +306 -366
- package/package.json +3 -3
- package/rules/v1/index.js +5 -5
- package/types/index.d.ts +2 -1
- package/utils/credit-card.d.ts +0 -1
- package/utils/credit-card.js +2 -9
- package/utils/fathom.d.ts +1 -2
- package/utils/fathom.js +2 -2
- package/utils/field.d.ts +4 -5
- package/utils/field.js +6 -17
- package/utils/identity.d.ts +0 -2
- package/utils/identity.js +2 -23
- package/dictionary/generate.d.ts +0 -1
- package/dictionary/generate.js +0 -51
- package/features/feature.spec.d.ts +0 -1
- package/features/feature.spec.js +0 -59
- package/features/registry.d.ts +0 -3
- package/features/registry.js +0 -9
- package/features/sorted.gen.d.ts +0 -14
- package/features/sorted.gen.js +0 -22
- package/features/v1/index.spec.d.ts +0 -1
- package/features/v1/index.spec.js +0 -30
- package/scripts/gen-sorted-features.d.ts +0 -1
- package/scripts/gen-sorted-features.js +0 -32
- package/utils/credit-card.samples.spec.d.ts +0 -1
- package/utils/credit-card.samples.spec.js +0 -452
- package/utils/credit-card.spec.d.ts +0 -1
- package/utils/credit-card.spec.js +0 -296
- package/utils/identity.samples.spec.d.ts +0 -1
- package/utils/identity.samples.spec.js +0 -28
- package/utils/re.spec.d.ts +0 -1
- package/utils/re.spec.js +0 -62
- package/utils/shadow-dom.spec.d.ts +0 -1
- package/utils/shadow-dom.spec.js +0 -215
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@protontech/autofill",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.36026908",
|
|
4
4
|
"private": false,
|
|
5
5
|
"scripts": {
|
|
6
6
|
"typecheck": "tsc --noEmit"
|
|
7
7
|
},
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"@protontech/fathom": "0.0.
|
|
10
|
-
"@protontech/ml-inference": "0.0.
|
|
9
|
+
"@protontech/fathom": "0.0.36026908",
|
|
10
|
+
"@protontech/ml-inference": "0.0.36026908"
|
|
11
11
|
},
|
|
12
12
|
"devDependencies": {
|
|
13
13
|
"@types/jest": "^29.5.14",
|
package/rules/v1/index.js
CHANGED
|
@@ -2,22 +2,22 @@ import { FEATURE_SEPARATOR } from "@protontech/autofill/constants/features";
|
|
|
2
2
|
import { formCandidateSelector } from "@protontech/autofill/constants/selectors";
|
|
3
3
|
import { computeFeatures } from "@protontech/autofill/features/feature";
|
|
4
4
|
import { FieldType, FormType } from "@protontech/autofill/types";
|
|
5
|
-
import { isCCInputField,
|
|
5
|
+
import { isCCInputField, isIdentity, maybeEmail, maybeHiddenUsername, maybeOTP, maybePassword, maybeUsername } from "@protontech/autofill/features/v1/field-predicates";
|
|
6
|
+
import { isCCSelectField } from "@protontech/autofill/utils/credit-card";
|
|
6
7
|
import { featureScore, isNoopForm, outRuleWithCache, processFieldEffect, processFormEffect, withFnodeEl } from "@protontech/autofill/utils/fathom";
|
|
7
|
-
import { isClassifiableField
|
|
8
|
+
import { isClassifiableField } from "@protontech/autofill/utils/field";
|
|
8
9
|
import { isClassifiable } from "@protontech/autofill/utils/flags";
|
|
9
|
-
import { isIdentity } from "@protontech/autofill/utils/identity";
|
|
10
10
|
import { overrides } from "@protontech/autofill/utils/overrides";
|
|
11
11
|
import { isVisibleForm } from "@protontech/autofill/utils/visible";
|
|
12
12
|
import { dom, out, rule, type } from "@protontech/fathom";
|
|
13
13
|
const getRulesForFieldClass = (klass, featureComputer) => [
|
|
14
14
|
rule(type(`${klass}-field`), type(klass), {}),
|
|
15
|
-
...featureComputer.features.map((
|
|
15
|
+
...featureComputer.features.map((featName) => rule(type(klass), featureScore(`${klass}-field`, featName), { name: `${klass}${FEATURE_SEPARATOR}${featName}` })),
|
|
16
16
|
...outRuleWithCache("field-candidate", klass),
|
|
17
17
|
];
|
|
18
18
|
const getRulesForFormClass = (klass, featureComputer) => [
|
|
19
19
|
rule(type("form"), type(klass), {}),
|
|
20
|
-
...featureComputer.features.map((
|
|
20
|
+
...featureComputer.features.map((featName) => rule(type(klass), featureScore("form", featName), { name: `${klass}${FEATURE_SEPARATOR}${featName}` })),
|
|
21
21
|
...outRuleWithCache("form-candidate", klass),
|
|
22
22
|
];
|
|
23
23
|
const noopRules = [
|
package/types/index.d.ts
CHANGED
|
@@ -71,13 +71,14 @@ export type Feature<N extends string, R, P extends AbstractFeatures> = {
|
|
|
71
71
|
name: N;
|
|
72
72
|
parents: P;
|
|
73
73
|
compute(parents: InferParentComputeType<P>, fnode: Fnode): R;
|
|
74
|
+
private?: true;
|
|
74
75
|
};
|
|
75
76
|
export type ComputedFeatures<F extends AbstractFeatures> = {
|
|
76
77
|
[K in keyof F as F[K]["name"]]: ReturnType<F[K]["compute"]>;
|
|
77
78
|
};
|
|
78
79
|
export interface FeatureComputer<F extends AbstractFeatures = AbstractFeatures> {
|
|
79
80
|
compute(fnode: Fnode): ComputedFeatures<F>;
|
|
80
|
-
features:
|
|
81
|
+
features: string[];
|
|
81
82
|
}
|
|
82
83
|
export type ModelProviderRegistry = Record<ModelName, ModelProvider>;
|
|
83
84
|
export type ModelName = string;
|
package/utils/credit-card.d.ts
CHANGED
|
@@ -27,6 +27,5 @@ export declare const getCCHaystack: (field: HTMLElement) => string;
|
|
|
27
27
|
export declare const getCachedCCSubtype: (el: HTMLElement) => CCFieldType | undefined;
|
|
28
28
|
export declare const getCCFieldType: (field: CCFieldElement) => CCFieldType | undefined;
|
|
29
29
|
export declare const matchCCFieldCandidate: (input: HTMLInputElement, { visible }: CCFieldMatchParams) => boolean;
|
|
30
|
-
export declare const isCCInputField: (fnode: Fnode) => boolean;
|
|
31
30
|
export declare const isCCSelectField: (fnode: Fnode) => boolean;
|
|
32
31
|
export {};
|
package/utils/credit-card.js
CHANGED
|
@@ -7,7 +7,7 @@ import { matchCCExp, matchCCExpMonth, matchCCExpYear, matchCCFirstName, matchCCL
|
|
|
7
7
|
import { sanitizeString, sanitizeStringWithSpaces } from "./text";
|
|
8
8
|
import { isVisible } from "./visible";
|
|
9
9
|
export const CC_ATTRIBUTES = ["autocomplete", "name", "id", "class", "form", "aria-label", "aria-labelledby", "placeholder", "data-testid", "data-stripe", "data-recurly", "data-encrypted-name"];
|
|
10
|
-
export const CC_INPUT_TYPES = ["tel", "
|
|
10
|
+
export const CC_INPUT_TYPES = ["tel", "text", "number", "password"];
|
|
11
11
|
const CC_EXP_YEAR_FORMAT = ["YYYY", "AAAA", "YY", "AA"];
|
|
12
12
|
const CC_EXP_MONTH_FORMAT = ["MM", "LL"];
|
|
13
13
|
const CC_EXP_FULL_RE = /(mmyy|mmaa|yymm|aamm)/;
|
|
@@ -212,8 +212,7 @@ export const getCCFieldType = (field) => {
|
|
|
212
212
|
const cachedSubType = getCachedCCSubtype(field);
|
|
213
213
|
if (cachedSubType)
|
|
214
214
|
return cachedSubType;
|
|
215
|
-
|
|
216
|
-
if (field.tagName === "INPUT" && type && !CC_INPUT_TYPES.includes(type))
|
|
215
|
+
if (field.tagName === "INPUT" && !CC_INPUT_TYPES.includes(field.type))
|
|
217
216
|
return;
|
|
218
217
|
const haystack = getCCHaystack(field);
|
|
219
218
|
const autocompletes = getAutocompletes(field);
|
|
@@ -234,12 +233,6 @@ export const matchCCFieldCandidate = (input, { visible }) => {
|
|
|
234
233
|
setCachedSubType(input, ccType);
|
|
235
234
|
return ccType !== undefined;
|
|
236
235
|
};
|
|
237
|
-
export const isCCInputField = (fnode) => {
|
|
238
|
-
const { isCC, visible } = fnode.noteFor("field");
|
|
239
|
-
if (!visible)
|
|
240
|
-
return false;
|
|
241
|
-
return isCC;
|
|
242
|
-
};
|
|
243
236
|
export const isCCSelectField = (fnode) => {
|
|
244
237
|
const select = fnode.element;
|
|
245
238
|
if (!(select instanceof HTMLSelectElement))
|
package/utils/fathom.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { Fnode } from "@protontech/fathom";
|
|
2
|
-
import type { AbstractFeature } from "@protontech/autofill/types";
|
|
3
2
|
export declare const TOLERANCE_LEVEL = 0.5;
|
|
4
3
|
export declare const boolInt: (val: boolean) => number;
|
|
5
4
|
export declare const safeInt: (val: number, fallback?: number) => number;
|
|
@@ -7,7 +6,7 @@ export declare const typeEffect: (type: string) => (fnode: Fnode) => Fnode;
|
|
|
7
6
|
export declare const processFormEffect: (fnode: Fnode) => Fnode;
|
|
8
7
|
export declare const processFieldEffect: (fnode: Fnode) => Fnode;
|
|
9
8
|
type ClassNote = "form" | "new-password-field" | "password-field" | "username-field" | "username-hidden-field" | "email-field" | "otp-field";
|
|
10
|
-
export declare const featureScore:
|
|
9
|
+
export declare const featureScore: (noteFor: ClassNote, featName: string) => import("@protontech/fathom").Side;
|
|
11
10
|
export declare const getParentFormFnode: (fieldFnode: Fnode) => Fnode | null;
|
|
12
11
|
export declare const typeScoreToleranceTest: (type: string) => (fnode: Fnode) => boolean;
|
|
13
12
|
export declare const getTypeScore: (node: Fnode | null, type: string) => number;
|
package/utils/fathom.js
CHANGED
|
@@ -20,9 +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,
|
|
23
|
+
export const featureScore = (noteFor, featName) => score((fnode) => {
|
|
24
24
|
const note = fnode.noteFor(noteFor);
|
|
25
|
-
return Number(note[
|
|
25
|
+
return Number(note[featName]);
|
|
26
26
|
});
|
|
27
27
|
export const getParentFormFnode = (fieldFnode) => {
|
|
28
28
|
const field = fieldFnode.element;
|
package/utils/field.d.ts
CHANGED
|
@@ -7,11 +7,10 @@ export declare const isSelect: (el: HTMLElement) => el is HTMLSelectElement;
|
|
|
7
7
|
export declare const isTextarea: (el: HTMLElement) => el is HTMLTextAreaElement;
|
|
8
8
|
export declare const splitFieldsByVisibility: (els: HTMLElement[]) => [HTMLElement[], HTMLElement[]];
|
|
9
9
|
export declare const fType: (type: FieldType) => (fnode: Fnode) => boolean;
|
|
10
|
-
export declare const
|
|
11
|
-
export declare const
|
|
12
|
-
export declare const
|
|
13
|
-
export declare const
|
|
14
|
-
export declare const maybeHiddenUsername: (value: Fnode) => boolean;
|
|
10
|
+
export declare const fInput: (types: string[]) => (fnode: Fnode) => boolean;
|
|
11
|
+
export declare const fMatch: (selector: string) => (fnode: Fnode) => boolean;
|
|
12
|
+
export declare const fMode: (mode: string) => (fnode: Fnode) => boolean;
|
|
13
|
+
export declare const fList: (fnode: Fnode) => boolean;
|
|
15
14
|
export declare const isUsernameCandidate: (el: HTMLInputElement) => boolean;
|
|
16
15
|
export declare const isEmailCandidate: (el: HTMLInputElement) => boolean;
|
|
17
16
|
export declare const isOAuthCandidate: (el: HTMLElement) => boolean;
|
package/utils/field.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { MIN_AREA_SUBMIT_BTN } from "@protontech/autofill/constants/heuristics";
|
|
2
|
-
import { inputCandidateSelector
|
|
3
|
-
import {
|
|
2
|
+
import { inputCandidateSelector } from "@protontech/autofill/constants/selectors";
|
|
3
|
+
import { any } from "./combinators";
|
|
4
4
|
import { getAllFieldHaystacks } from "./extract";
|
|
5
5
|
import { getParentFormFnode } from "./fathom";
|
|
6
6
|
import { isClassifiable, isHidden, isProcessed } from "./flags";
|
|
@@ -14,11 +14,6 @@ export const isInputField = (el) => {
|
|
|
14
14
|
};
|
|
15
15
|
export const isSelect = (el) => el.tagName === "SELECT";
|
|
16
16
|
export const isTextarea = (el) => el.tagName === "TEXTAREA";
|
|
17
|
-
const isActiveFieldFNode = (fnode) => {
|
|
18
|
-
const el = fnode.element;
|
|
19
|
-
const { visible } = fnode.noteFor("field");
|
|
20
|
-
return visible && !el.readOnly && !el.disabled;
|
|
21
|
-
};
|
|
22
17
|
export const splitFieldsByVisibility = (els) => {
|
|
23
18
|
return els.reduce((acc, el) => {
|
|
24
19
|
if (isVisibleField(el))
|
|
@@ -29,16 +24,10 @@ export const splitFieldsByVisibility = (els) => {
|
|
|
29
24
|
}, [[], []]);
|
|
30
25
|
};
|
|
31
26
|
export const fType = (type) => (fnode) => fnode.hasType(type);
|
|
32
|
-
const fInput = (types) => (fnode) => types.includes(fnode.element.type);
|
|
33
|
-
const fMatch = (selector) => (fnode) => fnode.element.matches(selector);
|
|
34
|
-
const fMode = (mode) => (fnode) => fnode.element.inputMode === mode;
|
|
35
|
-
const
|
|
36
|
-
const fList = (fnode) => fnode.element.getAttribute("aria-autocomplete") === "list" || fnode.element.role === "combobox";
|
|
37
|
-
export const maybeEmail = and(not(fList), or(fInput(["email", "text"]), fMode("email")), fActive);
|
|
38
|
-
export const maybePassword = and(not(fList), fMatch(kPasswordSelector), fActive);
|
|
39
|
-
export const maybeOTP = and(fInput(["text", "number", "tel"]), fActive, not(fList));
|
|
40
|
-
export const maybeUsername = and(not(fList), or(and(not(fMode("email")), fInput(["text", "tel"])), fMatch(kUsernameSelector)), fActive);
|
|
41
|
-
export const maybeHiddenUsername = and(not(fList), fInput(["email", "text", "hidden"]), not(fActive));
|
|
27
|
+
export const fInput = (types) => (fnode) => types.includes(fnode.element.type);
|
|
28
|
+
export const fMatch = (selector) => (fnode) => fnode.element.matches(selector);
|
|
29
|
+
export const fMode = (mode) => (fnode) => fnode.element.inputMode === mode;
|
|
30
|
+
export const fList = (fnode) => fnode.element.getAttribute("aria-autocomplete") === "list" || fnode.element.role === "combobox";
|
|
42
31
|
export const isUsernameCandidate = (el) => el.type === "text" || (el.type === "tel" && any(matchUsername)(getAllFieldHaystacks(el)));
|
|
43
32
|
export const isEmailCandidate = (el) => el.type === "email" || (el.type === "text" && any(matchEmail)(getAllFieldHaystacks(el)));
|
|
44
33
|
export const isOAuthCandidate = (el) => any(matchOAuth)(getAllFieldHaystacks(el));
|
package/utils/identity.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { Fnode } from "@protontech/fathom";
|
|
2
1
|
import { IdentityFieldType } from "@protontech/autofill/types";
|
|
3
2
|
import type { FormClassification } from "./fathom";
|
|
4
3
|
type IdentityFieldMatchParams = {
|
|
@@ -11,5 +10,4 @@ export declare const getCachedIdentitySubType: (el: HTMLElement) => IdentityFiel
|
|
|
11
10
|
export declare const getIdentityHaystack: (input: HTMLInputElement) => string;
|
|
12
11
|
export declare const getIdentityFieldType: (input: HTMLInputElement) => IdentityFieldType | undefined;
|
|
13
12
|
export declare const matchIdentityField: (input: HTMLInputElement, { visible }: IdentityFieldMatchParams) => boolean;
|
|
14
|
-
export declare const isIdentity: (fnode: Fnode) => boolean;
|
|
15
13
|
export {};
|
package/utils/identity.js
CHANGED
|
@@ -24,7 +24,7 @@ const IDENTITY_MATCHERS = [
|
|
|
24
24
|
[IdentityFieldType.ADDRESS, guard({ autocompletes: ["street-address", "address-line1"] }, matchAddress)],
|
|
25
25
|
];
|
|
26
26
|
const IDENTITY_ATTRIBUTES = ["autocomplete", "name", "id", "data-bhw"];
|
|
27
|
-
const IDENTITY_INPUT_TYPES = ["tel", "
|
|
27
|
+
const IDENTITY_INPUT_TYPES = ["tel", "text", "number"];
|
|
28
28
|
export const getCachedIdentitySubType = (el) => {
|
|
29
29
|
const subType = getCachedSubType(el);
|
|
30
30
|
if (subType && identityFields.has(subType))
|
|
@@ -41,8 +41,7 @@ export const getIdentityFieldType = (input) => {
|
|
|
41
41
|
return cachedSubType;
|
|
42
42
|
if (getCachedSubType(input) !== undefined)
|
|
43
43
|
return;
|
|
44
|
-
|
|
45
|
-
if (type && !IDENTITY_INPUT_TYPES.includes(type))
|
|
44
|
+
if (!IDENTITY_INPUT_TYPES.includes(input.type))
|
|
46
45
|
return;
|
|
47
46
|
if ((_a = input.getAttribute("autocomplete")) === null || _a === void 0 ? void 0 : _a.includes("email"))
|
|
48
47
|
return;
|
|
@@ -55,7 +54,6 @@ export const getIdentityFieldType = (input) => {
|
|
|
55
54
|
return identityType;
|
|
56
55
|
}
|
|
57
56
|
};
|
|
58
|
-
const isAutocompleteListInput = (el) => el.getAttribute("aria-autocomplete") === "list" || el.role === "combobox";
|
|
59
57
|
export const matchIdentityField = (input, { visible }) => {
|
|
60
58
|
if (!visible)
|
|
61
59
|
return false;
|
|
@@ -67,22 +65,3 @@ export const matchIdentityField = (input, { visible }) => {
|
|
|
67
65
|
setCachedSubType(input, identityType);
|
|
68
66
|
return true;
|
|
69
67
|
};
|
|
70
|
-
export const isIdentity = (fnode) => {
|
|
71
|
-
const input = fnode.element;
|
|
72
|
-
const { isIdentity, isCC, searchField, isFormLogin, isFormRecovery, visible } = fnode.noteFor("field");
|
|
73
|
-
if (!visible)
|
|
74
|
-
return false;
|
|
75
|
-
if (isCC || !isIdentity)
|
|
76
|
-
return false;
|
|
77
|
-
const identityType = getIdentityFieldType(input);
|
|
78
|
-
if (!identityType)
|
|
79
|
-
return false;
|
|
80
|
-
const outlierForm = isFormLogin || isFormRecovery;
|
|
81
|
-
if (outlierForm)
|
|
82
|
-
return false;
|
|
83
|
-
if (isAutocompleteListInput(input))
|
|
84
|
-
return [IdentityFieldType.ADDRESS, IdentityFieldType.ZIPCODE].includes(identityType);
|
|
85
|
-
if (searchField)
|
|
86
|
-
return [IdentityFieldType.ADDRESS, IdentityFieldType.ZIPCODE, IdentityFieldType.CITY].includes(identityType);
|
|
87
|
-
return true;
|
|
88
|
-
};
|
package/dictionary/generate.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dictionary/generate.js
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { sanitizeString, sanitizeStringWithSpaces } from "@protontech/autofill/utils/text";
|
|
2
|
-
import fs from "node:fs";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import regexgen from "regexgen";
|
|
5
|
-
import dictionary from "./source/dictionary";
|
|
6
|
-
const generateRegexes = () => {
|
|
7
|
-
let output = "";
|
|
8
|
-
output += "/* Regular expressions auto-generated using `source/dictionary.ts`\n";
|
|
9
|
-
output += " * Simplified & optimised using `regexgen` for performance. \n";
|
|
10
|
-
output += " * `TYPE_RE`: i18n regexp for text matches\n";
|
|
11
|
-
output += " * `TYPE_ATTR_RE`: regexp for node attributes\n";
|
|
12
|
-
output += " * `TYPE_ATTR_END`: attr ending constraint regexp */\n\n";
|
|
13
|
-
Object.entries(dictionary).forEach(([name, definition]) => {
|
|
14
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
15
|
-
const i18n = Object.values((_a = definition.i18n) !== null && _a !== void 0 ? _a : {})
|
|
16
|
-
.flat()
|
|
17
|
-
.map(sanitizeString);
|
|
18
|
-
const attrs = ((_c = (_b = definition === null || definition === void 0 ? void 0 : definition.attr) === null || _b === void 0 ? void 0 : _b.values) !== null && _c !== void 0 ? _c : []).map(sanitizeString);
|
|
19
|
-
const prefixes = ((_e = (_d = definition === null || definition === void 0 ? void 0 : definition.attr) === null || _d === void 0 ? void 0 : _d.prefixes) !== null && _e !== void 0 ? _e : []).map(sanitizeString);
|
|
20
|
-
const attrsExact = ((_g = (_f = definition === null || definition === void 0 ? void 0 : definition.attr) === null || _f === void 0 ? void 0 : _f.exact) !== null && _g !== void 0 ? _g : []).map(sanitizeStringWithSpaces);
|
|
21
|
-
if (i18n.length > 0) {
|
|
22
|
-
output += `export const ${name}_RE = ${regexgen(i18n, "")}i;\n`;
|
|
23
|
-
}
|
|
24
|
-
if (attrs.length > 0 || attrsExact.length > 0) {
|
|
25
|
-
const regAttrs = attrs.length > 0 ? regexgen(attrs, "").source : null;
|
|
26
|
-
const regExact = attrsExact.length > 0 ? `\\b(${regexgen(attrsExact, "").source})\\b` : null;
|
|
27
|
-
const regPrefixes = prefixes.length > 0 ? regexgen(prefixes, "").source : null;
|
|
28
|
-
const withPrefixes = (re) => (regPrefixes ? `(?:${regPrefixes})${re}` : re);
|
|
29
|
-
const re = (() => {
|
|
30
|
-
if (regAttrs && !regExact)
|
|
31
|
-
return withPrefixes(regAttrs);
|
|
32
|
-
else if (!regAttrs && regExact)
|
|
33
|
-
return regExact;
|
|
34
|
-
else if (regAttrs && regExact)
|
|
35
|
-
return `${withPrefixes(regAttrs)}|${regExact}`;
|
|
36
|
-
return "";
|
|
37
|
-
})();
|
|
38
|
-
output += `export const ${name}_ATTR_RE = /${re}/i;\n`;
|
|
39
|
-
}
|
|
40
|
-
if (definition === null || definition === void 0 ? void 0 : definition.startsWith) {
|
|
41
|
-
const startWithRe = regexgen(definition.startsWith.map(sanitizeString), "");
|
|
42
|
-
output += `export const ${name}_ATTR_START_RE = /\\b(?:${startWithRe.source})\\S+/i;\n`;
|
|
43
|
-
}
|
|
44
|
-
if (definition === null || definition === void 0 ? void 0 : definition.endsWith) {
|
|
45
|
-
const endWordsRe = regexgen(definition.endsWith.map(sanitizeString), "");
|
|
46
|
-
output += `export const ${name}_ATTR_END_RE= /\\b\\S*(?:${endWordsRe.source})\\b/i;\n`;
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
fs.writeFileSync(path.resolve(__dirname, "./generated/dictionary.ts"), output);
|
|
50
|
-
};
|
|
51
|
-
generateRegexes();
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/features/feature.spec.js
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { feature, featuresProduct, getComputerForFeatures, topologicalSort } from "./feature";
|
|
2
|
-
function doNothing() {
|
|
3
|
-
}
|
|
4
|
-
describe("topological sort", () => {
|
|
5
|
-
test("it works", () => {
|
|
6
|
-
const A = feature("A", {}, doNothing);
|
|
7
|
-
const B = feature("B", { A }, doNothing);
|
|
8
|
-
const C = feature("C", { A }, doNothing);
|
|
9
|
-
const D = feature("D", { B, C }, doNothing);
|
|
10
|
-
const E = feature("E", { B }, doNothing);
|
|
11
|
-
const F = feature("F", {}, doNothing);
|
|
12
|
-
const G = feature("G", { E, F }, doNothing);
|
|
13
|
-
const H = feature("H", {}, doNothing);
|
|
14
|
-
const features = { D, G, H };
|
|
15
|
-
const sorted = topologicalSort(features);
|
|
16
|
-
const nodeNames = sorted.map((feat) => feat.name);
|
|
17
|
-
expect(nodeNames.slice(0, 3).sort()).toEqual(["A", "F", "H"]);
|
|
18
|
-
expect(nodeNames.slice(3, 5).sort()).toEqual(["B", "C"]);
|
|
19
|
-
expect(nodeNames[5]).toEqual("E");
|
|
20
|
-
expect(nodeNames.slice(6, 8).sort()).toEqual(["D", "G"]);
|
|
21
|
-
});
|
|
22
|
-
test("it fails if there is a loop", () => {
|
|
23
|
-
const A = feature("A", {}, doNothing);
|
|
24
|
-
const B = feature("B", { A }, doNothing);
|
|
25
|
-
const C = feature("C", { A }, doNothing);
|
|
26
|
-
const D = feature("D", { B, C }, doNothing);
|
|
27
|
-
const E = feature("E", { B }, doNothing);
|
|
28
|
-
const F = feature("F", {}, doNothing);
|
|
29
|
-
const G = feature("G", { E, F }, doNothing);
|
|
30
|
-
const H = feature("H", {}, doNothing);
|
|
31
|
-
A.parents = { D };
|
|
32
|
-
const features = { A, B, C, D, E, F, G, H };
|
|
33
|
-
expect(() => topologicalSort(features)).toThrow(`loop detected with elements:
|
|
34
|
-
A -> {D}
|
|
35
|
-
B -> {A}
|
|
36
|
-
C -> {A}
|
|
37
|
-
D -> {B, C}
|
|
38
|
-
E -> {B}
|
|
39
|
-
G -> {E}`);
|
|
40
|
-
});
|
|
41
|
-
});
|
|
42
|
-
describe("Feature engineering", () => {
|
|
43
|
-
test("featureProduct", () => {
|
|
44
|
-
const A = feature("A", {}, () => 2);
|
|
45
|
-
const B = feature("B", { A }, () => 4);
|
|
46
|
-
const C = featuresProduct(A, B);
|
|
47
|
-
const features = { A, B, C };
|
|
48
|
-
const outputNames = Object.values(features).map((f) => f.name);
|
|
49
|
-
expect(getComputerForFeatures(topologicalSort(features), outputNames).compute({})).toStrictEqual({ A: 2, B: 4, "A,B": 8 });
|
|
50
|
-
});
|
|
51
|
-
test("feature name re-mapping", () => {
|
|
52
|
-
const A = feature("A", {}, () => 2);
|
|
53
|
-
const B = feature("_B", { A }, () => 4);
|
|
54
|
-
const C = feature("C", { A, B }, (p) => p.A + p._B);
|
|
55
|
-
const features = { A, B, C };
|
|
56
|
-
const outputNames = Object.values(features).map((f) => f.name);
|
|
57
|
-
expect(getComputerForFeatures(topologicalSort(features), outputNames).compute({})).toStrictEqual({ A: 2, _B: 4, C: 6 });
|
|
58
|
-
});
|
|
59
|
-
});
|
package/features/registry.d.ts
DELETED
package/features/registry.js
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
let formFeaturesComputer;
|
|
2
|
-
export const setFormFeaturesComputer = (c) => {
|
|
3
|
-
formFeaturesComputer = c;
|
|
4
|
-
};
|
|
5
|
-
export const getFormFeaturesComputer = () => {
|
|
6
|
-
if (!formFeaturesComputer)
|
|
7
|
-
throw new Error("formFeaturesComputer accessed before registration; features/v1 must be initialised first");
|
|
8
|
-
return formFeaturesComputer;
|
|
9
|
-
};
|
package/features/sorted.gen.d.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
export declare const sortedFieldFeatures: import("..").AbstractFeature<unknown>[];
|
|
2
|
-
export declare const fieldFeatureNames: ("text" | "label" | "autocomplete" | "visible" | "attrs" | "isCC" | "isFormLogin" | "parentFormFnode" | "isFormNoop" | "isFormPasswordChange" | "isFormRecovery" | "isFormRegister" | "isIdentity" | "searchField" | "nextField" | "parentFormFeatures" | "prevField" | "prevInput")[];
|
|
3
|
-
export declare const sortedFormFeatures: import("..").AbstractFeature<unknown>[];
|
|
4
|
-
export declare const formFeatureNames: ("__formInputMFACandidates" | "__formMFA" | "formTextMFA" | "formAttrsMFA" | "__formOTPOutlier" | "__formTextAuthenticator" | "inputsMFA" | "__headingsOTPOutlier" | "__inputIterator" | "__linkOTPOutlier" | "visibleFieldsCountScaled" | "visibleInputsCountScaled" | "fieldsetsCountScaled" | "textsCountScaled" | "textareasCountScaled" | "selectsCountScaled" | "disabledCountScaled" | "radiosCountScaled" | "readOnlyCountScaled" | "formComplexityScaled" | "visibleIdentifiersCountScaled" | "hiddenIdentifiersCountScaled" | "usernamesCountScaled" | "emailsCountScaled" | "hiddenCountScaled" | "hiddenPasswordsCountScaled" | "submitsCountScaled" | "identitiesCountScaled" | "ccsCountScaled" | "hasTels" | "hasOAuth" | "hasCaptchas" | "hasFiles" | "hasDate" | "hasNumber" | "oneVisibleField" | "twoVisibleFields" | "threeOrMoreVisibleFields" | "noPasswords" | "onePassword" | "twoPasswords" | "threeOrMorePasswords" | "noIdentifiers" | "oneIdentifier" | "twoIdentifiers" | "threeOrMoreIdentifiers" | "autofocusedIsIdentifier" | "autofocusedIsPassword" | "visibleRatio" | "inputRatio" | "hiddenRatio" | "identifierRatio" | "emailRatio" | "usernameRatio" | "passwordRatio" | "disabledRatio" | "requiredRatio" | "checkboxRatio" | "hiddenIdentifierRatio" | "hiddenPasswordRatio" | "pageLogin" | "formTextLogin" | "formAttrsLogin" | "headingsLogin" | "layoutLogin" | "rememberMeCheckbox" | "troubleLink" | "submitLogin" | "pageRegister" | "formTextRegister" | "formAttrsRegister" | "headingsRegister" | "layoutRegister" | "pwNewRegister" | "pwConfirmRegister" | "submitRegister" | "TOSRef" | "pagePwReset" | "formTextPwReset" | "formAttrsPwReset" | "headingsPwReset" | "layoutPwReset" | "pageRecovery" | "formTextRecovery" | "formAttrsRecovery" | "headingsRecovery" | "layoutRecovery" | "identifierRecovery" | "submitRecovery" | "newsletterForm" | "searchForm" | "multistepForm" | "multiAuthForm" | "multistepForm,multiAuthForm" | "visibleRatio,visibleFieldsCountScaled" | "visibleRatio,visibleIdentifiersCountScaled" | "visibleRatio,visiblePasswordsCountScaled" | "visibleRatio,hiddenIdentifiersCountScaled" | "visibleRatio,hiddenPasswordsCountScaled" | "visibleRatio,multiAuthForm" | "visibleRatio,multistepForm" | "identifierRatio,visibleFieldsCountScaled" | "identifierRatio,visibleIdentifiersCountScaled" | "identifierRatio,visiblePasswordsCountScaled" | "identifierRatio,hiddenIdentifiersCountScaled" | "identifierRatio,hiddenPasswordsCountScaled" | "identifierRatio,multiAuthForm" | "identifierRatio,multistepForm" | "passwordRatio,visibleFieldsCountScaled" | "passwordRatio,visibleIdentifiersCountScaled" | "passwordRatio,visiblePasswordsCountScaled" | "passwordRatio,hiddenIdentifiersCountScaled" | "passwordRatio,hiddenPasswordsCountScaled" | "passwordRatio,multiAuthForm" | "passwordRatio,multistepForm" | "requiredRatio,visibleFieldsCountScaled" | "requiredRatio,visibleIdentifiersCountScaled" | "requiredRatio,visiblePasswordsCountScaled" | "requiredRatio,hiddenIdentifiersCountScaled" | "requiredRatio,hiddenPasswordsCountScaled" | "requiredRatio,multiAuthForm" | "requiredRatio,multistepForm")[];
|
|
5
|
-
export declare const sortedEmailFeatures: import("..").AbstractFeature<unknown>[];
|
|
6
|
-
export declare const emailFeatureNames: ("fieldCC" | "fieldIdentity" | "isSearchField" | "attrEmail" | "autocompleteEmail" | "autocompleteOff" | "typeEmail" | "exactAttrEmail" | "textEmail" | "labelEmail" | "placeholderEmail" | "mfaOutlier")[];
|
|
7
|
-
export declare const sortedOtpFeatures: import("..").AbstractFeature<unknown>[];
|
|
8
|
-
export declare const otpFeatureNames: ("formComplexity" | "fieldCC" | "fieldIdentity" | "formMFA" | "formAuthenticator" | "outlierForm" | "outlierLink" | "outlierHeadings" | "numericMode" | "patternOTP" | "nameMatch" | "idMatch" | "autocompleteOTC" | "singleInput" | "siblingOfInterest" | "attrOTP" | "attrMFA" | "textMFA" | "textAuthenticator" | "labelOTP" | "labelMFA" | "labelAuthenticator" | "outlierMaxLength" | "outlierInputCount" | "outlierEmailMatchScaled" | "outlierField" | "outlierSearch" | "outlierAutocomplete")[];
|
|
9
|
-
export declare const sortedPasswordFeatures: import("..").AbstractFeature<unknown>[];
|
|
10
|
-
export declare const passwordFeatureNames: ("fieldCC" | "fieldIdentity" | "autocompleteOff" | "fieldLogin" | "fieldRegister" | "fieldExotic" | "passwordOutlier" | "autocompleteNew" | "autocompleteCurrent" | "autocompleteOTP" | "attrCurrent" | "textCurrent" | "labelCurrent" | "attrCreate" | "textCreate" | "labelCreate" | "attrConfirm" | "textConfirm" | "labelConfirm" | "attrReset" | "prevPwField" | "prevPwNew" | "prevPwCurrent" | "nextPwField" | "nextPwNew" | "nextPwConfirm" | "nextPwCurrent" | "fieldLogin,autocompleteNew" | "fieldLogin,maybeNew" | "fieldRegister,autocompleteCurrent" | "fieldRegister,maybeCurrent" | "prevPwCurrent,nextPwNew")[];
|
|
11
|
-
export declare const sortedUsernameFeatures: import("..").AbstractFeature<unknown>[];
|
|
12
|
-
export declare const usernameFeatureNames: ("fieldCC" | "fieldIdentity" | "isSearchField" | "autocompleteUsername" | "attrUsername" | "autocompleteEmail" | "autocompleteOff" | "textUsername" | "labelUsername" | "outlierAttrs" | "outlierText" | "outlierLabel" | "outlierEmail" | "firstLoginFormField")[];
|
|
13
|
-
export declare const sortedUsernameHiddenFeatures: import("..").AbstractFeature<unknown>[];
|
|
14
|
-
export declare const usernameHiddenFeatureNames: ("fieldCC" | "fieldIdentity" | "autocompleteUsername" | "attrEmail" | "attrUsername" | "autocompleteEmail" | "fieldExotic" | "visibleReadonly" | "attrMatch" | "valueEmail" | "valueTel" | "valueUsername")[];
|
package/features/sorted.gen.js
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { topologicalSort } from "@protontech/autofill/features/feature";
|
|
2
|
-
import { fieldFeatures } from "./v1/abstract.field";
|
|
3
|
-
import { formFeatures } from "./v1/abstract.form";
|
|
4
|
-
import { emailFeatures } from "./v1/field.email";
|
|
5
|
-
import { otpFeatures } from "./v1/field.otp";
|
|
6
|
-
import { passwordFeatures } from "./v1/field.password";
|
|
7
|
-
import { usernameFeatures } from "./v1/field.username";
|
|
8
|
-
import { usernameHiddenFeatures } from "./v1/field.username-hidden";
|
|
9
|
-
export const sortedFieldFeatures = topologicalSort(fieldFeatures);
|
|
10
|
-
export const fieldFeatureNames = Object.values(fieldFeatures).map((f) => f.name);
|
|
11
|
-
export const sortedFormFeatures = topologicalSort(formFeatures);
|
|
12
|
-
export const formFeatureNames = Object.values(formFeatures).map((f) => f.name);
|
|
13
|
-
export const sortedEmailFeatures = topologicalSort(emailFeatures);
|
|
14
|
-
export const emailFeatureNames = Object.values(emailFeatures).map((f) => f.name);
|
|
15
|
-
export const sortedOtpFeatures = topologicalSort(otpFeatures);
|
|
16
|
-
export const otpFeatureNames = Object.values(otpFeatures).map((f) => f.name);
|
|
17
|
-
export const sortedPasswordFeatures = topologicalSort(passwordFeatures);
|
|
18
|
-
export const passwordFeatureNames = Object.values(passwordFeatures).map((f) => f.name);
|
|
19
|
-
export const sortedUsernameFeatures = topologicalSort(usernameFeatures);
|
|
20
|
-
export const usernameFeatureNames = Object.values(usernameFeatures).map((f) => f.name);
|
|
21
|
-
export const sortedUsernameHiddenFeatures = topologicalSort(usernameHiddenFeatures);
|
|
22
|
-
export const usernameHiddenFeatureNames = Object.values(usernameHiddenFeatures).map((f) => f.name);
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { topologicalSort } from "@protontech/autofill/features/feature";
|
|
2
|
-
import { fieldFeatures } from "./abstract.field";
|
|
3
|
-
import { formFeatures } from "./abstract.form";
|
|
4
|
-
import { emailFeatures } from "./field.email";
|
|
5
|
-
import { otpFeatures } from "./field.otp";
|
|
6
|
-
import { passwordFeatures } from "./field.password";
|
|
7
|
-
import { usernameFeatures } from "./field.username";
|
|
8
|
-
import { usernameHiddenFeatures } from "./field.username-hidden";
|
|
9
|
-
describe("features can be topologically sorted", () => {
|
|
10
|
-
const featureSets = {
|
|
11
|
-
fieldFeatures,
|
|
12
|
-
formFeatures,
|
|
13
|
-
emailFeatures,
|
|
14
|
-
otpFeatures,
|
|
15
|
-
passwordFeatures,
|
|
16
|
-
usernameHiddenFeatures,
|
|
17
|
-
usernameFeatures,
|
|
18
|
-
};
|
|
19
|
-
Object.entries(featureSets).forEach(([featureSetName, features]) => test(`${featureSetName} can be topologically sorted`, () => {
|
|
20
|
-
try {
|
|
21
|
-
topologicalSort(features);
|
|
22
|
-
}
|
|
23
|
-
catch (error) {
|
|
24
|
-
console.log(Object.values(features)
|
|
25
|
-
.map((feature) => `${feature.name} -> {${Object.keys(feature.parents).join(", ")}}`)
|
|
26
|
-
.join("\n"));
|
|
27
|
-
throw error;
|
|
28
|
-
}
|
|
29
|
-
}));
|
|
30
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { dirname } from "node:path";
|
|
3
|
-
import { topologicalSort } from "../features/feature";
|
|
4
|
-
import { fieldFeatures } from "../features/v1/abstract.field";
|
|
5
|
-
import { formFeatures } from "../features/v1/abstract.form";
|
|
6
|
-
import { emailFeatures } from "../features/v1/field.email";
|
|
7
|
-
import { otpFeatures } from "../features/v1/field.otp";
|
|
8
|
-
import { passwordFeatures } from "../features/v1/field.password";
|
|
9
|
-
import { usernameFeatures } from "../features/v1/field.username";
|
|
10
|
-
import { usernameHiddenFeatures } from "../features/v1/field.username-hidden";
|
|
11
|
-
const sets = [
|
|
12
|
-
{ name: "fieldFeatures", sourceModule: "./v1/abstract.field", features: fieldFeatures },
|
|
13
|
-
{ name: "formFeatures", sourceModule: "./v1/abstract.form", features: formFeatures },
|
|
14
|
-
{ name: "emailFeatures", sourceModule: "./v1/field.email", features: emailFeatures },
|
|
15
|
-
{ name: "otpFeatures", sourceModule: "./v1/field.otp", features: otpFeatures },
|
|
16
|
-
{ name: "passwordFeatures", sourceModule: "./v1/field.password", features: passwordFeatures },
|
|
17
|
-
{ name: "usernameFeatures", sourceModule: "./v1/field.username", features: usernameFeatures },
|
|
18
|
-
{ name: "usernameHiddenFeatures", sourceModule: "./v1/field.username-hidden", features: usernameHiddenFeatures },
|
|
19
|
-
];
|
|
20
|
-
for (const { features } of sets)
|
|
21
|
-
topologicalSort(features);
|
|
22
|
-
const capitalize = (s) => s.charAt(0).toUpperCase() + s.slice(1);
|
|
23
|
-
const namesExport = (setName) => setName.replace(/Features$/, "FeatureNames");
|
|
24
|
-
const sortedExport = (setName) => `sorted${capitalize(setName)}`;
|
|
25
|
-
const banner = "// Generated by `bazel run //pass_ml/js/autofill:write_sorted_features` — do not edit.\n";
|
|
26
|
-
const imports = [`import { topologicalSort } from "@protontech/autofill/features/feature";`, ...sets.map(({ name, sourceModule }) => `import { ${name} } from "${sourceModule}";`)].join("\n");
|
|
27
|
-
const exports = sets.flatMap(({ name }) => [`export const ${sortedExport(name)} = topologicalSort(${name});`, `export const ${namesExport(name)} = Object.values(${name}).map((f) => f.name);`]).join("\n");
|
|
28
|
-
const outPath = process.argv[2];
|
|
29
|
-
if (!outPath)
|
|
30
|
-
throw new Error("usage: gen-sorted-features <out.ts>");
|
|
31
|
-
mkdirSync(dirname(outPath), { recursive: true });
|
|
32
|
-
writeFileSync(outPath, `${banner}\n${imports}\n\n${exports}\n`);
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|