95 lines
4.1 KiB
TypeScript
95 lines
4.1 KiB
TypeScript
|
|
import { DiscoveryDescriptor } from '../../core/classes.discoverydescriptor.js';
|
||
|
|
import type { IDiscoveryCandidate, IDiscoveryMatch, IDiscoveryMatcher, IDiscoveryValidator } from '../../core/types.js';
|
||
|
|
import type { IAdguardManualEntry, IAdguardSnapshot } from './adguard.types.js';
|
||
|
|
import { adguardDefaultPort } from './adguard.types.js';
|
||
|
|
|
||
|
|
export class AdguardManualMatcher implements IDiscoveryMatcher<IAdguardManualEntry> {
|
||
|
|
public id = 'adguard-manual-match';
|
||
|
|
public source = 'manual' as const;
|
||
|
|
public description = 'Recognize manual AdGuard Home setup entries by host, domain, manufacturer, model, or metadata.';
|
||
|
|
|
||
|
|
public async matches(inputArg: IAdguardManualEntry): Promise<IDiscoveryMatch> {
|
||
|
|
const matched = isAdguardHint(inputArg) || Boolean(inputArg.host);
|
||
|
|
if (!matched) {
|
||
|
|
return {
|
||
|
|
matched: false,
|
||
|
|
confidence: 'low',
|
||
|
|
reason: 'Manual entry does not contain AdGuard Home setup hints.',
|
||
|
|
};
|
||
|
|
}
|
||
|
|
const id = inputArg.id || inputArg.host && `${inputArg.host}:${inputArg.port || adguardDefaultPort}`;
|
||
|
|
return {
|
||
|
|
matched: true,
|
||
|
|
confidence: inputArg.host ? 'high' : 'medium',
|
||
|
|
reason: 'Manual entry can start AdGuard Home setup.',
|
||
|
|
normalizedDeviceId: id,
|
||
|
|
candidate: {
|
||
|
|
source: 'manual',
|
||
|
|
integrationDomain: 'adguard',
|
||
|
|
id,
|
||
|
|
host: inputArg.host,
|
||
|
|
port: inputArg.port || adguardDefaultPort,
|
||
|
|
name: inputArg.name || 'AdGuard Home',
|
||
|
|
manufacturer: 'AdGuard Team',
|
||
|
|
model: inputArg.model || 'AdGuard Home',
|
||
|
|
metadata: {
|
||
|
|
...inputArg.metadata,
|
||
|
|
ssl: inputArg.ssl,
|
||
|
|
verifySsl: inputArg.verifySsl,
|
||
|
|
basePath: inputArg.basePath,
|
||
|
|
snapshot: inputArg.snapshot,
|
||
|
|
},
|
||
|
|
},
|
||
|
|
};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export class AdguardCandidateValidator implements IDiscoveryValidator {
|
||
|
|
public id = 'adguard-candidate-validator';
|
||
|
|
public description = 'Validate AdGuard Home candidates before starting local HTTP setup.';
|
||
|
|
|
||
|
|
public async validate(candidateArg: IDiscoveryCandidate): Promise<IDiscoveryMatch> {
|
||
|
|
const matched = isAdguardHint(candidateArg) || candidateArg.integrationDomain === 'adguard';
|
||
|
|
const snapshot = candidateArg.metadata?.snapshot;
|
||
|
|
const hasSnapshot = isAdguardSnapshot(snapshot);
|
||
|
|
const hasUsableAddress = Boolean(candidateArg.host && isValidPort(candidateArg.port || adguardDefaultPort));
|
||
|
|
return {
|
||
|
|
matched: matched && (hasUsableAddress || hasSnapshot),
|
||
|
|
confidence: matched && candidateArg.id ? 'certain' : matched && hasUsableAddress ? 'high' : matched ? 'medium' : 'low',
|
||
|
|
reason: matched
|
||
|
|
? hasUsableAddress || hasSnapshot ? 'Candidate has AdGuard Home metadata and a usable HTTP address or snapshot.' : 'Candidate has AdGuard Home metadata but no usable HTTP address.'
|
||
|
|
: 'Candidate is not AdGuard Home.',
|
||
|
|
candidate: matched && (hasUsableAddress || hasSnapshot) ? candidateArg : undefined,
|
||
|
|
normalizedDeviceId: candidateArg.id || candidateArg.host && `${candidateArg.host}:${candidateArg.port || adguardDefaultPort}`,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export const createAdguardDiscoveryDescriptor = (): DiscoveryDescriptor => {
|
||
|
|
return new DiscoveryDescriptor({
|
||
|
|
integrationDomain: 'adguard',
|
||
|
|
displayName: 'AdGuard Home',
|
||
|
|
})
|
||
|
|
.addMatcher(new AdguardManualMatcher())
|
||
|
|
.addValidator(new AdguardCandidateValidator());
|
||
|
|
};
|
||
|
|
|
||
|
|
const isAdguardHint = (valueArg: { integrationDomain?: string; manufacturer?: string; model?: string; name?: string; metadata?: Record<string, unknown> }): boolean => {
|
||
|
|
const manufacturer = valueArg.manufacturer?.toLowerCase() || '';
|
||
|
|
const model = valueArg.model?.toLowerCase() || '';
|
||
|
|
const name = valueArg.name?.toLowerCase() || '';
|
||
|
|
return valueArg.integrationDomain === 'adguard'
|
||
|
|
|| manufacturer.includes('adguard')
|
||
|
|
|| model.includes('adguard')
|
||
|
|
|| name.includes('adguard')
|
||
|
|
|| Boolean(valueArg.metadata?.adguard);
|
||
|
|
};
|
||
|
|
|
||
|
|
const isValidPort = (valueArg: number): boolean => {
|
||
|
|
return Number.isInteger(valueArg) && valueArg > 0 && valueArg <= 65535;
|
||
|
|
};
|
||
|
|
|
||
|
|
const isAdguardSnapshot = (valueArg: unknown): valueArg is IAdguardSnapshot => {
|
||
|
|
return typeof valueArg === 'object' && valueArg !== null && 'status' in valueArg && 'online' in valueArg;
|
||
|
|
};
|