is-online npm包源码解释

112 阅读3分钟

is_online

检查互联网连接是否正常

适用于 Node.js 和浏览器 (带有捆绑器)

在浏览器中,已经有了navigator.onLine,但它没用,因为它只告诉您是否有本地连接,而不是告诉您是否可以访问互联网。

安装

npm install is-online

用法

import isOnline from 'is-online';

console.log(await isOnline());
//=> true

API

是否在线(选项?)

选项

类型:object

暂停

类型:number
默认:5000

等待服务器响应的毫秒数。

ip版本

类型:number
值:4 | 6
默认值:4

要使用的Internet 协议版本

这是一个高级选项,通常不需要设置,但它对于专门声明 IPv6 连接很有用。

工作原理

以下检查同时运行:

  • 通过 HTTPS检索icanhazip.com(或ipify.org作为后备)。
  • 查询myip.opendns.como-o.myaddr.l.google.comDNS 条目。 (仅限 Node.js)
  • 检索 Apple 的 Captive Portal 测试页面(这是 iOS 所做的)。 (仅限 Node.js)

当任何检查成功时,返回的 Promise 将被解析为true

代理支持

为了使其通过代理工作,您需要设置global-agent

源码解释

import got, {CancelError} from 'got'; // 从 'got' 模块导入 got 函数和 CancelError 类
import {publicIpv4, publicIpv6} from 'public-ip'; // 从 'public-ip' 模块中导入 publicIpv4 和 publicIpv6 函数
import pAny from 'p-any'; // 从 'p-any' 模块导入 pAny 函数
import pTimeout from 'p-timeout'; // 从 'p-timeout' 模块导入 pTimeout 函数

// appleCheck 函数:检测当前网络是否能访问 Apple 的测试网址
const appleCheck = options => {
    // 发起请求到 'https://captive.apple.com/hotspot-detect.html',并传入超时时间和 IP 版本
    const gotPromise = got('https://captive.apple.com/hotspot-detect.html', {
        timeout: {
            request: options.timeout,
        },
        dnsLookupIpVersion: options.ipVersion,
        headers: {
            'user-agent': 'CaptiveNetworkSupport/1.0 wispr', // 模拟 Apple 设备的 User-Agent
        },
    });

    // 定义一个异步函数来处理请求的响应
    const promise = (async () => {
        try {
            const {body} = await gotPromise;
            if (!body?.includes('Success')) { // 如果响应内容不包含 'Success' 字符串,则抛出错误
                throw new Error('Apple check failed');
            }
        } catch (error) {
            if (!(error instanceof CancelError)) { // 如果错误不是 CancelError 类型,则抛出错误
                throw error;
            }
        }
    })();

    // 将 gotPromise 的 cancel 方法赋值给 promise 对象的 cancel 方法
    promise.cancel = gotPromise.cancel;

    return promise;
};

// isOnline 函数:检测是否联网
// 不能是 `async` 函数,因为这样会失去 `.cancel()` 方法。
export default function isOnline(options) {
    // 设置默认选项,并合并用户传入的 options
    options = {
        timeout: 5000,
        ipVersion: 4,
        ...options,
    };

    // 如果所有的网络接口都是内部的,则直接返回 false
    if (Object.values(os.networkInterfaces()).flat().every(({internal}) => internal)) {
        return false;
    }

    // 如果 IP 版本不是 4 或 6,则抛出错误
    if (![4, 6].includes(options.ipVersion)) {
        throw new TypeError('`ipVersion` must be 4 or 6');
    }

    // 根据 IP 版本选择合适的 publicIpFunction 函数
    const publicIpFunction = options.ipVersion === 4 ? publicIpv4 : publicIpv6;
    const queries = []; // 保存所有查询的 Promise

    // 使用 pAny 执行多个异步操作,返回第一个成功的结果
    const promise = pAny([
        (async () => {
            const query = publicIpFunction(options);
            queries.push(query); // 将查询加入 queries 数组
            await query; // 等待查询完成
            return true; // 如果查询成功,返回 true
        })(),
        (async () => {
            const query = publicIpFunction({...options, onlyHttps: true});
            queries.push(query);
            await query;
            return true;
        })(),
        (async () => {
            const query = appleCheck(options);
            queries.push(query);
            await query;
            return true;
        })(),
    ]);

    // 使用 pTimeout 设置超时,如果超时或出现错误,则取消所有查询并返回 false
    return pTimeout(promise, {milliseconds: options.timeout}).catch(() => { // eslint-disable-line promise/prefer-await-to-then
        for (const query of queries) {
            query.cancel(); // 取消每个查询
        }

        return false; // 如果没有成功的查询,返回 false
    });

    // TODO: 在支持 AbortController 时使用这个替代方法。
    // try {
    // 	return await pTimeout(promise, options.timeout);
    // } catch {
    // 	for (const query of queries) {
    // 		query.cancel();
    // 	}
    // 	return false;
    // }
}