useOfflineBookingCachePolling.js 7.61 KB
import { ref, onMounted, onUnmounted } from 'vue'
import Taro from '@tarojs/taro'
import { refresh_offline_booking_cache } from '@/composables/useOfflineBookingCache'
import { get_network_type, is_usable_network } from '@/utils/network'

/**
 * @description: 轮询状态
 * @property {Number} timer_id 轮询定时器id
 * @property {Boolean} running 是否正在轮询
 * @property {Boolean} in_flight 是否正在刷新
 * @property {Number} ref_count 引用计数
 * @property {Boolean} app_enabled 是否启用应用
 * @property {Object} last_options 最后一次选项
 * @property {Boolean} network_usable 网络可用性
 * @property {Boolean} has_network_listener 是否已注册网络监听器
 * @property {Function} network_listener 网络监听器
 * @property {Promise} network_listener_promise 网络监听器Promise
 */

const polling_state = {
    timer_id: null, // 轮询定时器id
    running: false, // 是否正在轮询
    in_flight: false, // 是否正在刷新
    ref_count: 0, // 引用计数
    app_enabled: false, // 是否启用应用
    last_options: null, // 最后一次选项
    network_usable: null, // 网络可用性
    has_network_listener: false, // 是否已注册网络监听器
    network_listener: null, // 网络监听器
    network_listener_promise: null, // 网络监听器Promise
}

/**
 * @description: 归一化选项
 * @param {Object} options 选项
 * @return {Object} 归一化后的选项
 */

const normalize_options = (options) => {
    polling_state.last_options = options || polling_state.last_options || {}
    return polling_state.last_options
}

/**
 * @description: 刷新离线预约缓存一次
 * @param {Object} options 选项
 * @param {Boolean} options.force 是否强制刷新
 */

const run_refresh_once = async (options) => {
    if (polling_state.in_flight) return
    polling_state.in_flight = true
    try {
        await refresh_offline_booking_cache({ force: !!options?.force })
    } finally {
        polling_state.in_flight = false
    }
}

/**
 * @description: 更新网络可用性
 */

const update_network_usable = async () => {
    const type = await get_network_type()
    polling_state.network_usable = is_usable_network(type)
}

/**
 * @description: 判断是否需要运行轮询
 * @return {Boolean} 是否需要运行轮询
 */

const should_run_polling = () => {
    if (polling_state.ref_count <= 0) return false
    if (polling_state.network_usable === false) return false
    if (polling_state.network_usable === null) return false
    return true
}

/**
 * @description: 确保轮询已启动
 * @param {Object} options 选项
 */

const ensure_polling_started = (options) => {
    start_offline_booking_cache_polling(options)
}

/**
 * @description: 确保网络监听器已注册
 */

const ensure_network_listener = async () => {
    if (polling_state.has_network_listener) {
        await update_network_usable()
        return
    }
    if (polling_state.network_listener_promise) {
        await polling_state.network_listener_promise
        return
    }

    polling_state.network_listener_promise = (async () => {
        polling_state.has_network_listener = true
        await update_network_usable()

        polling_state.network_listener = (res) => {
            const is_connected = res?.isConnected !== false
            const type = res?.networkType || 'unknown'
            polling_state.network_usable = is_connected && is_usable_network(type)

            if (!polling_state.network_usable) {
                stop_offline_booking_cache_polling()
                return
            }

            if (should_run_polling()) {
                ensure_polling_started(polling_state.last_options || {})
            }
        }

        try {
            Taro.onNetworkStatusChange(polling_state.network_listener)
        } catch (e) {
            polling_state.has_network_listener = false
            polling_state.network_listener = null
            polling_state.network_usable = null
            console.error('注册网络监听失败:', e)
        }
    })()

    try {
        await polling_state.network_listener_promise
    } finally {
        polling_state.network_listener_promise = null
    }
}

/**
 * @description: 注销网络监听器
 */

const teardown_network_listener = () => {
    if (!polling_state.has_network_listener) return
    if (polling_state.ref_count > 0) return
    polling_state.has_network_listener = false
    if (polling_state.network_listener && typeof Taro.offNetworkStatusChange === 'function') {
        try {
            Taro.offNetworkStatusChange(polling_state.network_listener)
        } catch (e) {
            polling_state.network_listener = null
        }
    }
    polling_state.network_listener = null
    polling_state.network_usable = null
}

/**
 * @description: 启动离线预约缓存轮询
 * @param {Object} options 选项
 * @param {Number} options.interval_ms 轮询间隔,单位毫秒
 * @param {Boolean} options.immediate 是否立即刷新一次
 */

export const start_offline_booking_cache_polling = (options) => {
    normalize_options(options)
    if (!should_run_polling()) return
    const interval_ms = Number(options?.interval_ms || 60000)
    if (polling_state.running) return

    polling_state.running = true

    if (options?.immediate !== false) {
        run_refresh_once(options)
    }

    polling_state.timer_id = setInterval(() => {
        run_refresh_once(options)
    }, interval_ms)
}

/**
 * @description: 停止离线预约缓存轮询
 */

export const stop_offline_booking_cache_polling = () => {
    if (polling_state.timer_id) {
        clearInterval(polling_state.timer_id)
        polling_state.timer_id = null
    }
    polling_state.running = false
}

export const enable_offline_booking_cache_polling = (options) => {
    normalize_options(options)
    if (polling_state.app_enabled) {
        ensure_network_listener().then(() => ensure_polling_started(polling_state.last_options || {}))
        return
    }
    polling_state.app_enabled = true
    polling_state.ref_count += 1
    ensure_network_listener().then(() => ensure_polling_started(polling_state.last_options || {}))
}

export const disable_offline_booking_cache_polling = () => {
    if (!polling_state.app_enabled) return
    polling_state.app_enabled = false
    polling_state.ref_count = Math.max(0, polling_state.ref_count - 1)
    if (polling_state.ref_count === 0) {
        stop_offline_booking_cache_polling()
        teardown_network_listener()
    }
}

/**
 * @description: 用于管理离线预约缓存轮询的组合式函数
 * @param {Object} options 选项
 * @param {Boolean} options.enabled 是否启用轮询
 * @param {Boolean} options.auto 是否自动启动轮询
 * @param {Number} options.interval_ms 轮询间隔,单位毫秒
 * @param {Boolean} options.immediate 是否立即刷新一次
 */

export const use_offline_booking_cache_polling = (options) => {
    const is_running = ref(false)
    const enabled = options?.enabled !== false

    const start = () => {
        if (!enabled) return
        polling_state.ref_count += 1
        ensure_network_listener().then(() => {
            ensure_polling_started(options)
        })
        is_running.value = true
    }

    const stop = () => {
        if (!is_running.value) return
        polling_state.ref_count = Math.max(0, polling_state.ref_count - 1)
        if (polling_state.ref_count === 0) {
            stop_offline_booking_cache_polling()
            teardown_network_listener()
        }
        is_running.value = false
    }

    onMounted(() => {
        if (options?.auto !== false) start()
    })

    onUnmounted(() => {
        stop()
    })

    return {
        is_running,
        start,
        stop,
    }
}