javascriptdns

I need help trying to capture net::ERR_NAME_NOT_RESOLVED in Javascript


I’m trying to allow users to drop a list of endpoints into a web page to test them for validity. I thought I could use AJAX but this proved problematic.

I understand I can do this on the server side, but I expect I’ll be getting hundreds of requests and I would prefer to let each user’s browser take care of this. At first I thought it would be possible since both Edge and Chrome showed net::ERR_NAME_NOT_RESOLVED in the console. I think this particular error may be too low level in the browser and is not accessible at the Javascript layer.

Below is my first attempt

var req = new XMLHttpRequest();  
req.open("get", "http://xxgdvgshd.com"); // non existent endpoint  
req.onerror = (e) => {
    // gets invoked but no indication of the actual error
    console.log(e);
    console.log(req.readyState,e.readyState);
    console.log(req.status,e.status,req.statusText,e.statusText);
}
req.onreadystatechange = (e) => {
    console.log(e);
    console.log(req.readyState,e.readyState);
    console.log(req.status,e.status,req.statusText,e.statusText);
}
req.onload = (e) => { // never gets invoked
    console.log(e);
    console.log(req.readyState,e.readyState);
    console.log(req.status,e.status,req.statusText,e.statusText);
}
try {
    req.send();
} catch(e) {
    // Won't be executed during asychronous call.
    console.log(e);
    console.log(req.readyState,e.readyState);
    console.log(req.status,e.status,req.statusText,e.statusText);
}

I get nothing indicating a DNS error. Specifically, Firefox gave me a CORs error. I can’t use that method since it doesn’t bother to see if the endpoint has a DNS associated with it.

Next, I thought loading an image won't cause a CORs error, so I tried this:

var img = new Image(10, 10);  
img.src = "http://baddomain.ddd";  
img.onerror = (e) => {
    // Edge & Chrome writes 'net::ERR_NAME_NOT_RESOLVED' to the console, but is not accessible in any error handlers
    // Firefox essentially gives no indication there's any problem other than invoking this handler.
    console.log(e);
}

Same result: I see net::ERR_NAME_NOT_RESOLVED in the console, but not in any error handler accessible variables.

Would Webassembly offer a way to do this, perhaps?


Solution

  • I've created a browser-based endpoint validity checker using Fetch API with timeout mechanism to test endpoint validity across browsers. Try it and see if it works:

        class EndpointValidator {
            /**
             * Check if an endpoint is valid (resolvable and responds)
             * @param {string} url - The URL to test
             * @param {number} timeout - Timeout in milliseconds (default 5000)
             * @returns {Promise<object>} Validation result
             */
            static async checkEndpoint(url, timeout = 5000) {
                try {
                    // Ensure URL has a protocol
                    const fullUrl = url.startsWith('http://') || url.startsWith('https://') 
                        ? url 
                        : `http://${url}`;
        
                    // Create an AbortController to manage timeout
                    const controller = new AbortController();
                    const timeoutId = setTimeout(() => controller.abort(), timeout);
        
                    try {
                        const response = await fetch(fullUrl, {
                            method: 'HEAD',
                            mode: 'no-cors',
                            signal: controller.signal
                        });
        
                        clearTimeout(timeoutId);
                        return {
                            valid: true,
                            url: fullUrl,
                            status: response.status
                        };
                    } catch (error) {
                        clearTimeout(timeoutId);
        
                        // Detailed error checking
                        if (error.name === 'AbortError') {
                            return {
                                valid: false,
                                url: fullUrl,
                                error: 'Timeout',
                                details: 'Request took too long to respond'
                            };
                        }
        
                        // Attempt to diagnose specific issues
                        return this.diagnoseEndpointError(fullUrl, error);
                    }
                } catch (generalError) {
                    return {
                        valid: false,
                        url,
                        error: 'Unhandled Error',
                        details: generalError.message
                    };
                }
            }
        
            /**
             * Attempt to provide more detailed error diagnosis
             * @param {string} url - The URL being tested
             * @param {Error} error - The caught error
             * @returns {object} Detailed error information
             */
            static diagnoseEndpointError(url, error) {
                // Try to provide more context about the error
                const errorLowerCase = error.message.toLowerCase();
                
                const errorTypes = [
                    { 
                        pattern: /failed to fetch|network error/i, 
                        type: 'Network Error', 
                        details: 'Unable to resolve domain or connect to server' 
                    },
                    { 
                        pattern: /cors|cross-origin/i, 
                        type: 'CORS Error', 
                        details: 'Cross-Origin Resource Sharing restriction' 
                    },
                    { 
                        pattern: /dns/i, 
                        type: 'DNS Error', 
                        details: 'Domain name could not be resolved' 
                    }
                ];
        
                const matchedError = errorTypes.find(err => err.pattern.test(errorLowerCase));
        
                return {
                    valid: false,
                    url,
                    error: matchedError ? matchedError.type : 'Connection Error',
                    details: matchedError ? matchedError.details : error.message
                };
            }
        
            /**
             * Validate multiple endpoints
             * @param {string[]} urls - Array of URLs to test
             * @param {number} concurrency - Maximum concurrent checks
             * @returns {Promise<object[]>} Array of validation results
             */
            static async validateEndpoints(urls, concurrency = 5) {
                // Use Promise.all with limited concurrency
                const results = [];
                for (let i = 0; i < urls.length; i += concurrency) {
                    const batch = urls.slice(i, i + concurrency);
                    const batchResults = await Promise.all(
                        batch.map(url => this.checkEndpoint(url))
                    );
                    results.push(...batchResults);
                }
                return results;
            }
        }
        
        // Example usage
        async function testEndpoints() {
            const endpoints = [
                'google.com', 
                'nonexistentdomain.xyz', 
                'http://example.com'
            ];
        
            const results = await EndpointValidator.validateEndpoints(endpoints);
            console.log(results);
        }
    
    // Expose for global use if needed
    window.EndpointValidator = EndpointValidator;
    

    It uses fetch() with no-cors to bypass CORS restrictions and provides timeout to prevent indefinite waiting. It also attempts to diagnose different types of connection errors. It supports both full urls and domain names, and allows batch processing of endpoints.

    To use it, you can simply call:

    // Check a single endpoint
    EndpointValidator.checkEndpoint('google.com')
        .then(result => console.log(result));
    
    // Validate multiple endpoints
    EndpointValidator.validateEndpoints(['google.com', 'example.com'])
        .then(results => console.log(results));
    

    Regarding your observation about net::ERR_NAME_NOT_RESOLVED, this approach provides a more programmatic way to detect and handle such errors across different browsers.

    I hope this helps.