Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {
XCircle,
} from "lucide-react";
import Link from "next/link";
import { useState } from "react";
import { useCallback, useEffect, useRef, useState } from "react";
import { toast } from "sonner";
import { DialogAction } from "@/components/shared/dialog-action";
import { Badge } from "@/components/ui/badge";
Expand Down Expand Up @@ -84,7 +84,7 @@ export const ShowDomains = ({ id, type }: Props) => {
const { data: permissions } = api.user.getPermissions.useQuery();
const canCreateDomain = permissions?.domain.create ?? false;
const canDeleteDomain = permissions?.domain.delete ?? false;
const { data: application } =
const { data: application, isFetched: isApplicationFetched } =
type === "application"
? api.application.one.useQuery(
{
Expand All @@ -105,6 +105,10 @@ export const ShowDomains = ({ id, type }: Props) => {
const [validationStates, setValidationStates] = useState<ValidationStates>(
{},
);
const autoValidatedHostsRef = useRef<Set<string>>(new Set());
const validationRequestIdRef = useRef(0);
const hostValidationRequestIdsRef = useRef<Map<string, number>>(new Map());
const lastAutoValidatedServerIpRef = useRef<string | undefined>(undefined);
const [viewMode, setViewMode] = useState<"grid" | "table">(() => {
if (typeof window !== "undefined") {
return (
Expand All @@ -118,7 +122,7 @@ export const ShowDomains = ({ id, type }: Props) => {
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({});
const [rowSelection, setRowSelection] = useState({});
const { data: ip } = api.settings.getIp.useQuery();
const { data: ip, isFetched: isIpFetched } = api.settings.getIp.useQuery();

const {
data,
Expand Down Expand Up @@ -157,42 +161,155 @@ export const ShowDomains = ({ id, type }: Props) => {
}
};

const handleValidateDomain = async (host: string) => {
setValidationStates((prev) => ({
...prev,
[host]: { isLoading: true },
}));
const resolveServerIp = useCallback(() => {
const remoteIp = application?.server?.ipAddress?.toString();
if (application?.serverId) {
return remoteIp || undefined;
}

try {
const result = await validateDomain({
domain: host,
serverIp:
application?.server?.ipAddress?.toString() || ip?.toString() || "",
});
return ip?.toString() || undefined;
}, [application?.server?.ipAddress, application?.serverId, ip]);

const handleValidateDomain = useCallback(
async (host: string, serverIpOverride?: string) => {
const serviceRequestId = validationRequestIdRef.current;
const hostRequestId =
(hostValidationRequestIdsRef.current.get(host) ?? 0) + 1;
hostValidationRequestIdsRef.current.set(host, hostRequestId);

const serverIp =
serverIpOverride ??
(application?.server?.ipAddress?.toString() || ip?.toString() || "");

const isCurrentRequest = () =>
validationRequestIdRef.current === serviceRequestId &&
hostValidationRequestIdsRef.current.get(host) === hostRequestId;

if (!isCurrentRequest()) {
return;
}

setValidationStates((prev) => ({
...prev,
[host]: {
isLoading: false,
isValid: result.isValid,
error: result.error,
resolvedIp: result.resolvedIp,
cdnProvider: result.cdnProvider,
message: result.error && result.isValid ? result.error : undefined,
},
}));
} catch (err) {
const error = err as Error;
setValidationStates((prev) => ({
...prev,
[host]: {
isLoading: false,
isValid: false,
error: error.message || "Failed to validate domain",
},
[host]: { isLoading: true },
}));

try {
const result = await validateDomain({
domain: host,
serverIp,
});

if (!isCurrentRequest()) {
return;
}

setValidationStates((prev) => ({
...prev,
[host]: {
isLoading: false,
isValid: result.isValid,
error: result.error,
resolvedIp: result.resolvedIp,
cdnProvider: result.cdnProvider,
message: result.error && result.isValid ? result.error : undefined,
},
}));
Comment on lines +207 to +217

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Stale validation crosses services

If a user navigates between services that share a hostname while the first service's automatic check is still running, that request writes into the current host-keyed state after the ID reset, causing the new service to display a DNS result calculated with the previous service's server IP.

Knowledge Base Used: Git Providers and Domains

} catch (err) {
if (!isCurrentRequest()) {
return;
}

const error = err as Error;
setValidationStates((prev) => ({
...prev,
[host]: {
isLoading: false,
isValid: false,
error: error.message || "Failed to validate domain",
},
}));
}
},
[validateDomain, application?.server?.ipAddress, ip],
);

useEffect(() => {
validationRequestIdRef.current += 1;
autoValidatedHostsRef.current = new Set();
hostValidationRequestIdsRef.current = new Map();
lastAutoValidatedServerIpRef.current = undefined;
setValidationStates({});
}, [id]);

useEffect(() => {
if (!data?.length || !isApplicationFetched || !application) {
return;
}
};

if (application.serverId) {
if (!application.server?.ipAddress) {
return;
}
} else if (!isIpFetched) {
return;
}

const serverIp = resolveServerIp();
if (!serverIp) {
return;
}

if (lastAutoValidatedServerIpRef.current !== serverIp) {
lastAutoValidatedServerIpRef.current = serverIp;
autoValidatedHostsRef.current = new Set();
}

const hostsToValidate = data
.map((item) => item.host)
.filter((host) => {
if (autoValidatedHostsRef.current.has(host)) {
return false;
}

autoValidatedHostsRef.current.add(host);
Comment on lines +271 to +275

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Expected-IP changes stay stale

When the application's server IP or the global server IP changes while this page remains mounted, the effect reruns but autoValidatedHostsRef filters out every previously checked hostname, causing the badge to keep showing the DNS result calculated against the old IP until manual revalidation or a component reset.

Knowledge Base Used: Git Providers and Domains

return true;
});

if (hostsToValidate.length === 0) {
return;
}

const maxConcurrent = 5;
let nextIndex = 0;

const runNext = async () => {
while (nextIndex < hostsToValidate.length) {
const host = hostsToValidate[nextIndex];
nextIndex += 1;
if (!host) {
continue;
}
await handleValidateDomain(host, serverIp);
}
};

void Promise.all(
Array.from(
{ length: Math.min(maxConcurrent, hostsToValidate.length) },
() => runNext(),
),
);
}, [
data,
isIpFetched,
isApplicationFetched,
application,
application?.serverId,
application?.server?.ipAddress,
resolveServerIp,
handleValidateDomain,
]);

const columns = createColumns({
id,
Expand Down