WServBroadcastServer.mjs

import get from 'lodash-es/get.js'
import each from 'lodash-es/each.js'
import map from 'lodash-es/map.js'
import cloneDeep from 'lodash-es/cloneDeep.js'
import evem from 'wsemi/src/evem.mjs'
import haskey from 'wsemi/src/haskey.mjs'
import iseobj from 'wsemi/src/iseobj.mjs'
import alive from 'wsemi/src/alive.mjs'


/**
 * 伺服器端之資料控制與同步器
 *
 * @class
 * @param {Object} instWConverServer 輸入通訊服務實體物件,可使用例如WConverhpServer等建立
 * @param {Object} [opt={}] 輸入設定物件,預設{}
 * @returns {Object} 回傳事件物件,可監聽error事件
 * @example
 *
 * import WConverhpServer from 'w-converhp/src/WConverhpServer.mjs'
 * import WServBroadcastServer from './src/WServBroadcastServer.mjs'
 *
 * let opt = {
 *     port: 8080,
 *     apiName: 'api',
 *     pathStaticFiles: '.', //要存取專案資料夾下web.html, 故不能給dist
 *     verifyConn: () => {
 *         return true
 *     },
 * }
 *
 * //instWConverServer
 * let instWConverServer = new WConverhpServer(opt)
 *
 * //instWConverServer
 * instWConverServer = new WServBroadcastServer(instWConverServer)
 *
 * let n = 0
 * setInterval(() => {
 *     n++
 *     instWConverServer.broadcast(`n=${n}`)
 * }, 1500)
 *
 * instWConverServer.on('clientEnter', function(data) {
 *     console.log(`Server[port:${opt.port}]: clientEnter`, data)
 * })
 * instWConverServer.on('clientLeave', function(data) {
 *     console.log(`Server[port:${opt.port}]: clientLeave`, data)
 * })
 * instWConverServer.on('clientChange', function(data) {
 *     console.log(`Server[port:${opt.port}]: clientChange`, data)
 * })
 * instWConverServer.on('broadcast', function(data) {
 *     console.log(`Server[port:${opt.port}]: broadcast`, data)
 * })
 * instWConverServer.on('error', function(err) {
 *     console.log(`Server[port:${opt.port}]: error`, err)
 * })
 * instWConverServer.on('handler', function(data) {
 *     // console.log(`Server[port:${opt.port}]: handler`, data)
 * })
 *
 */
function WServBroadcastServer(instWConverServer, opt = {}) {
    let broadcastMessages = {}

    //check
    if (!iseobj(instWConverServer)) {
        console.log('instWConverServer is not an effective object, and set instWConverServer to an EventEmitter')
        instWConverServer = evem()
    }
    if (!haskey(instWConverServer, 'emit')) {
        throw new Error(`instWConverServer is not an EventEmitter`)
    }

    //eeEmit
    let eeEmit = (name, ...args) => {
        setTimeout(() => {
            instWConverServer.emit(name, ...args)
        }, 1)
    }

    //ea
    let ea = alive()

    //ea broadcastMessages
    let t = setInterval(() => {

        //now alive
        let nowAlive = ea.get()
        //console.log('nowAlive', nowAlive)

        //clientIds
        let clientIds = map(nowAlive, 'key')
        //console.log('clientIds', clientIds)

        //pick
        let t = {}
        each(clientIds, (clientId) => {
            t[clientId] = get(broadcastMessages, clientId, [])
        })
        broadcastMessages = t
        // console.log('broadcastMessages', broadcastMessages)

    }, 1000)

    //message
    ea.on('message', function({ eventName, key, data, now }) {
        //console.log({ eventName, key, data, now })
        if (eventName === 'enter') {
            eeEmit('clientEnter', key, data)
        }
        else if (eventName === 'leave') {
            eeEmit('clientLeave', key, data)
        }
        eeEmit('clientChange', now)
    })

    //execute
    instWConverServer.on('execute', function(func, input, pm) {
        if (func === '[sys:polling]') {
            // console.log(`execute: [sys:polling]`, input)
            try {

                //clientId
                let clientId = get(input, 'clientId', '')
                // console.log('clientId', clientId)

                //polling messages
                let pms = get(broadcastMessages, clientId, [])
                pms = cloneDeep(pms)

                //clear messages
                broadcastMessages[clientId] = []

                //trigger
                ea.trigger(clientId, input)

                //resolve
                pm.resolve(pms)

            }
            catch (err) {
                // console.log('[sys:polling] error', err)
                eeEmit('error', err)
                pm.reject('[sys:polling] error')
            }
        }
    })

    //broadcast
    let broadcast = (data) => {

        //check, broadcastMessages受ea偵測頻率1s影響, 伺服器初始化後至少需1s才會有有效對象
        if (!iseobj(broadcastMessages)) {
            return
        }

        //modify broadcast data
        let t = {}
        each(broadcastMessages, (v, k) => {

            //push, 數據為陣列, 加入新廣播數據
            v.push({
                mode: 'broadcast',
                data,
            })

            //save
            t[k] = v

        })
        broadcastMessages = t

    }

    //getMessages
    let getMessages = () => {
        return cloneDeep(broadcastMessages) //使用cloneDeep避免外部修改內部數據
    }

    //clearBroadcast
    let clearBroadcast = () => {
        clearInterval(t)
    }

    //save
    instWConverServer.getClients = ea.get
    instWConverServer.getMessages = getMessages
    instWConverServer.broadcast = broadcast
    instWConverServer.clearBroadcast = clearBroadcast

    return instWConverServer
}


export default WServBroadcastServer