add proxy resolver
This commit is contained in:
1 parent
cd520dacc5
commit
a56bff2305
4 files changed
+126
-1
No files matched your search
@@ -5,6 +5,7 @@ export interface IProxiesRepository {
|
||||
create(proxyData: Partial<Proxy>): Promise<Proxy>;
|
||||
// read
|
||||
findById(proxyId: string, teamId: string): Promise<Proxy>;
|
||||
findByIdOrNull(proxyId: string, teamId?: string): Promise<Proxy | null>;
|
||||
findByTeamId(teamId: string): Promise<Proxy[]>;
|
||||
// update
|
||||
updateById(proxyId: string, teamId: string, patch: Partial<Proxy>, options?: { unsetPassword?: boolean; unsetUsername?: boolean }): Promise<Proxy>;
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Proxy } from "@/domain/proxies/proxy.type.js";
|
||||
import { ProxyDocument, ProxyModel } from "@/domain/proxies/proxy.model.js";
|
||||
import { AppError } from "@/utils/AppError.js";
|
||||
import { toStringId, toDateString } from "@/utils/mongoMappers.js";
|
||||
import { UpdateQuery } from "mongoose";
|
||||
import { Error as MongooseError, UpdateQuery } from "mongoose";
|
||||
|
||||
const SERVICE_NAME = "ProxiesRepository";
|
||||
|
||||
@@ -45,6 +45,18 @@ class MongoProxiesRepository implements IProxiesRepository {
|
||||
return this.toEntity(proxy);
|
||||
}
|
||||
|
||||
async findByIdOrNull(proxyId: string, teamId?: string): Promise<Proxy | null> {
|
||||
try {
|
||||
const proxy = await ProxyModel.findOne(teamId ? { _id: proxyId, teamId } : { _id: proxyId });
|
||||
return proxy ? this.toEntity(proxy) : null;
|
||||
} catch (error) {
|
||||
if (error instanceof MongooseError.CastError) {
|
||||
return null;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async findByTeamId(teamId: string): Promise<Proxy[]> {
|
||||
const proxies = await ProxyModel.find({ teamId });
|
||||
return proxies.map(this.toEntity);
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
import { ISettingsService } from "@/domain/app-settings/app-settings.service.js";
|
||||
import { Monitor } from "@/domain/monitors/monitor.type.js";
|
||||
import { IProxiesRepository } from "@/domain/proxies/proxy.repository.interface.js";
|
||||
import { ILogger } from "@/utils/logger.js";
|
||||
import { Proxy } from "@/domain/proxies/proxy.type.js";
|
||||
|
||||
export interface IProxyResolver {
|
||||
resolve(monitor: Monitor): Promise<string | undefined>;
|
||||
}
|
||||
|
||||
const SERVICE_NAME = "ProxyResolver";
|
||||
|
||||
export const buildProxyUrl = (proxy: Proxy): string => {
|
||||
const { protocol, host, port, username, password } = proxy;
|
||||
let credentials = "";
|
||||
if (username) {
|
||||
credentials = password ? `${encodeURIComponent(username)}:${encodeURIComponent(password)}@` : `${encodeURIComponent(username)}@`;
|
||||
}
|
||||
return `${protocol}://${credentials}${host}:${port}`;
|
||||
};
|
||||
|
||||
export class ProxyResolver implements IProxyResolver {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
constructor(
|
||||
private proxiesRepository: IProxiesRepository,
|
||||
private settingsService: ISettingsService,
|
||||
private logger: ILogger,
|
||||
private ttlMs = 60_000
|
||||
) {}
|
||||
|
||||
private cache = new Map<string, { value: unknown; expiresAt: number }>();
|
||||
|
||||
private async getCached<T>(key: string, fetch: () => Promise<T>): Promise<T> {
|
||||
const hit = this.cache.get(key);
|
||||
if (hit && hit.expiresAt > Date.now()) {
|
||||
return hit.value as T;
|
||||
}
|
||||
const value = await fetch();
|
||||
this.cache.set(key, { value, expiresAt: Date.now() + this.ttlMs });
|
||||
return value;
|
||||
}
|
||||
|
||||
private warn(message: string, monitor: Monitor, error?: unknown) {
|
||||
this.logger.warn({
|
||||
message,
|
||||
service: SERVICE_NAME,
|
||||
method: "resolve",
|
||||
details: {
|
||||
monitorId: monitor.id,
|
||||
proxyMode: monitor.proxyMode,
|
||||
proxyId: monitor.proxyId,
|
||||
...(error instanceof Error ? { error: error.message } : {}),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private resolveCustom = async (monitor: Monitor): Promise<string | undefined> => {
|
||||
const { proxyId, teamId } = monitor;
|
||||
if (!proxyId) {
|
||||
this.warn("Proxy mode is custom, but proxyId is missing, connecting direct", monitor);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const proxy = await this.getCached(`proxy:${proxyId}:${teamId}`, () => this.proxiesRepository.findByIdOrNull(proxyId, teamId));
|
||||
if (!proxy) {
|
||||
this.warn("Proxy not found, connecting direct", monitor);
|
||||
return undefined;
|
||||
}
|
||||
return buildProxyUrl(proxy);
|
||||
};
|
||||
|
||||
private resolveInherit = async (monitor: Monitor): Promise<string | undefined> => {
|
||||
const settings = await this.getCached("settings", () => this.settingsService.getDBSettings());
|
||||
if (!settings.globalProxyEnabled) {
|
||||
return undefined;
|
||||
}
|
||||
const globalProxyId = settings.globalProxyId;
|
||||
if (!globalProxyId) {
|
||||
return undefined;
|
||||
}
|
||||
const proxy = await this.getCached(`proxy:${globalProxyId}`, () => this.proxiesRepository.findByIdOrNull(globalProxyId));
|
||||
if (!proxy) {
|
||||
this.warn("Global proxy references a proxy that no longer exists, connecting direct", monitor);
|
||||
return undefined;
|
||||
}
|
||||
return buildProxyUrl(proxy);
|
||||
};
|
||||
|
||||
async resolve(monitor: Monitor): Promise<string | undefined> {
|
||||
if (monitor.type !== "http") {
|
||||
return undefined;
|
||||
}
|
||||
try {
|
||||
switch (monitor.proxyMode) {
|
||||
case "none":
|
||||
return undefined;
|
||||
case "custom":
|
||||
return await this.resolveCustom(monitor);
|
||||
default:
|
||||
// "inherit" is the default mode
|
||||
return await this.resolveInherit(monitor);
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
this.warn("Proxy resolution failed, connecting direct", monitor, error);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -152,3 +152,7 @@ export type StatusChangeResult = {
|
||||
};
|
||||
|
||||
export type MonitorStatusResponseOverrides<T> = Partial<Omit<MonitorStatusResponse<T>, "monitorId" | "teamId" | "type">>;
|
||||
|
||||
export type CheckContext = {
|
||||
proxyUrl?: string;
|
||||
};
|
||||
Reference in new issue
Block a user