mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-03-19 07:07:59 -04:00
Improve autofill matching (#801)
This commit is contained in:
committed by
Leendert de Borst
parent
22d2e09982
commit
1d77d05e7c
@@ -1,63 +1,103 @@
|
||||
import { CombinedStopWords } from "@/utils/formDetector/FieldPatterns";
|
||||
import { Credential } from "../../utils/types/Credential";
|
||||
|
||||
interface CredentialWithPriority extends Credential {
|
||||
priority: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter credentials based on current URL and page context to determine which credentials to show
|
||||
* in the autofill popup.
|
||||
* in the autofill popup. Credentials are sorted by priority:
|
||||
* 1. Exact URL match (highest priority)
|
||||
* 2. Base URL match AND page title word match
|
||||
* 3. Base URL match only
|
||||
* 4. Page title word match only (lowest priority)
|
||||
*/
|
||||
export function filterCredentials(credentials: Credential[], currentUrl: string, pageTitle: string): Credential[] {
|
||||
const urlObject = new URL(currentUrl);
|
||||
const baseUrl = `${urlObject.protocol}//${urlObject.hostname}`;
|
||||
const filtered: CredentialWithPriority[] = [];
|
||||
|
||||
// 1. Exact URL match
|
||||
let filtered = credentials.filter(cred =>
|
||||
cred.ServiceUrl?.toLowerCase() === currentUrl.toLowerCase()
|
||||
);
|
||||
const sanitizedCurrentUrl = currentUrl.toLowerCase().replace('www.', '');
|
||||
|
||||
// 2. Base URL match with fuzzy domain comparison if no exact matches
|
||||
filtered = filtered.concat(credentials.filter(cred => {
|
||||
if (!cred.ServiceUrl) {
|
||||
return false;
|
||||
// 1. Exact URL match (priority 1)
|
||||
credentials.forEach(cred => {
|
||||
if (!cred.ServiceUrl || cred.ServiceUrl.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sanitizedCredUrl = cred.ServiceUrl.toLowerCase().replace('www.', '');
|
||||
|
||||
if (sanitizedCurrentUrl.startsWith(sanitizedCredUrl)) {
|
||||
filtered.push({ ...cred, priority: 1 });
|
||||
}
|
||||
});
|
||||
|
||||
// If we have one or more exact matches, do not continue to other matches
|
||||
if (filtered.length > 0) {
|
||||
return filtered;
|
||||
}
|
||||
|
||||
// Prepare page title words for matching
|
||||
const titleWords = pageTitle.length > 0
|
||||
? pageTitle.toLowerCase()
|
||||
.split(/\s+/)
|
||||
.filter(word =>
|
||||
word.length > 3 &&
|
||||
!CombinedStopWords.has(word.toLowerCase())
|
||||
)
|
||||
: [];
|
||||
|
||||
// Check for base URL matches and page title matches
|
||||
credentials.forEach(cred => {
|
||||
if (!cred.ServiceUrl || filtered.some(f => f.Id === cred.Id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let hasBaseUrlMatch = false;
|
||||
let hasTitleMatch = false;
|
||||
|
||||
// Check base URL match
|
||||
try {
|
||||
const credUrlObject = new URL(cred.ServiceUrl);
|
||||
const currentUrlObject = new URL(baseUrl);
|
||||
|
||||
// Extract root domains by splitting on dots and taking last two parts
|
||||
const credDomainParts = credUrlObject.hostname.toLowerCase().split('.');
|
||||
const currentDomainParts = currentUrlObject.hostname.toLowerCase().split('.');
|
||||
|
||||
// Get root domain (last two parts, e.g., 'aliasvault.net')
|
||||
const credRootDomain = credDomainParts.slice(-2).join('.');
|
||||
const currentRootDomain = currentDomainParts.slice(-2).join('.');
|
||||
|
||||
// Compare protocols and root domains
|
||||
return credUrlObject.protocol === currentUrlObject.protocol &&
|
||||
credRootDomain === currentRootDomain;
|
||||
if (credUrlObject.protocol === currentUrlObject.protocol &&
|
||||
credRootDomain === currentRootDomain) {
|
||||
hasBaseUrlMatch = true;
|
||||
}
|
||||
} catch {
|
||||
return false;
|
||||
// Invalid URL, skip
|
||||
}
|
||||
}));
|
||||
|
||||
// 3. Page title word match if still no matches
|
||||
if (filtered.length === 0 && pageTitle.length > 0) {
|
||||
const titleWords = pageTitle.toLowerCase()
|
||||
.split(/\s+/)
|
||||
.filter(word =>
|
||||
word.length > 3 && // Filter out words shorter than 4 characters
|
||||
!CombinedStopWords.has(word.toLowerCase()) // Filter out generic words
|
||||
);
|
||||
// Check page title match
|
||||
if (titleWords.length > 0) {
|
||||
hasTitleMatch = titleWords.some(word => cred.ServiceName.toLowerCase().includes(word));
|
||||
}
|
||||
|
||||
filtered = credentials.filter(cred =>
|
||||
titleWords.some(word =>
|
||||
cred.ServiceName.toLowerCase().includes(word)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Ensure we have unique credentials
|
||||
const uniqueCredentials = Array.from(new Map(filtered.map(cred => [cred.Id, cred])).values());
|
||||
// Assign priority based on matches
|
||||
if (hasBaseUrlMatch && hasTitleMatch) {
|
||||
filtered.push({ ...cred, priority: 2 });
|
||||
} else if (hasBaseUrlMatch) {
|
||||
filtered.push({ ...cred, priority: 3 });
|
||||
} else if (hasTitleMatch) {
|
||||
filtered.push({ ...cred, priority: 4 });
|
||||
}
|
||||
});
|
||||
|
||||
// Sort by priority and then take unique credentials
|
||||
const uniqueCredentials = Array.from(
|
||||
new Map(filtered
|
||||
.sort((a, b) => a.priority - b.priority)
|
||||
.map(cred => [cred.Id, cred]))
|
||||
.values()
|
||||
);
|
||||
// Show max 3 results
|
||||
return uniqueCredentials.slice(0, 3);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import { PasswordSettingsResponse } from '@/utils/types/messaging/PasswordSettin
|
||||
import SqliteClient from '../../utils/SqliteClient';
|
||||
import { BaseIdentityGenerator } from '@/utils/generators/Identity/implementations/base/BaseIdentityGenerator';
|
||||
import { StringResponse } from '@/utils/types/messaging/StringResponse';
|
||||
import { Credential } from '@/utils/types/Credential';
|
||||
|
||||
// TODO: store generic setting constants somewhere else.
|
||||
export const DISABLED_SITES_KEY = 'local:aliasvault_disabled_sites';
|
||||
|
||||
Reference in New Issue
Block a user