WServHapiClient.mjs

import get from 'lodash-es/get.js'
import isestr from 'wsemi/src/isestr.mjs'
import isfun from 'wsemi/src/isfun.mjs'
import isbol from 'wsemi/src/isbol.mjs'
import isp0int from 'wsemi/src/isp0int.mjs'
import ispint from 'wsemi/src/ispint.mjs'
import ispm from 'wsemi/src/ispm.mjs'
import isWindow from 'wsemi/src/isWindow.mjs'
import waitFun from 'wsemi/src/waitFun.mjs'
import WConverhpClient from 'w-converhp/src/WConverhpClient.mjs'
import WServWebdataClient from 'w-serv-webdata/src/WServWebdataClient.mjs'


/**
 * Hapi瀏覽器端之資料控制與同步器
 *
 * @class
 * @param {Object} [opt={}] 輸入設定物件,預設{}
 * @param {Function} [opt.FormData=()=>''] 輸入指定FormData函數,當於nodejs執行時需提供FormData才能使用,可安裝'form-data'並引用取得FormData,預設()=>''
 * @param {String} [opt.url='http://localhost:8080'] 輸入伺服器接口網址,前端常用window.location.origin+window.location.pathname,預設為'http://localhost:8080'
 * @param {String} [opt.apiName='api'] 輸入API名稱字串,預設'api'
 * @param {Boolean} [opt.useWaitToken=false] 輸入是否等待有token才啟動,供驗證使用者已成功登入之用,預設false
 * @param {Function} [opt.getToken=()=>''] 輸入取得使用者token的回調函數,預設()=>''
 * @param {String} [opt.tokenType='Bearer'] 輸入token類型字串,預設'Bearer'
 * @param {Integer} [opt.sizeSlice=1024*1024] 輸入切片上傳檔案之切片檔案大小整數,單位為Byte,預設為1024*1024
 * @param {Integer} [opt.timeout=5*60*1000] 輸入最長等待時間整數,單位ms,預設為5*60*1000、為5分鐘
 * @param {Integer} [opt.retryMain=3] 輸入主要控制器傳輸失敗重試次數整數,預設為3
 * @param {Integer} [opt.retryUpload=10] 輸入切片上傳檔案傳輸失敗重試次數整數,預設為10
 * @param {Integer} [opt.retryDownload=2] 輸入下載檔案傳輸失敗重試次數整數,預設為2
 * @param {Function} [opt.getServerMethods=()=>{}] 輸入提供操作物件的回調函數,前後端通訊先取得可呼叫函數清單,映射完之後,後端函數都將放入物件當中,key為函數名而值為函數,並通過回調函數提供該物件,預設()=>{}
 * @param {Function} [opt.recvData=()=>{}] 輸入取得變更表資料的回調函數,預設()=>{}
 * @param {Boolean} [opt.showLog=true] 輸入是否使用console.log顯示基本資訊布林值,預設true
 * @returns {Object} 回傳物件,wcc為w-converhp的瀏覽器事件物件,wsdc為w-serv-webdata的瀏覽器事件物件,可監聽error事件
 * @example
 *
 * // import fs from 'fs'
 * import FormData from 'form-data'
 * import WServHapiClient from './src/WServHapiClient.mjs'
 *
 * async function client() {
 *
 *     //WServHapiClient
 *     let instWServHapiClient = new WServHapiClient({
 *         FormData,
 *         url: 'http://localhost:8080',
 *         useWaitToken: false,
 *         getToken: () => {
 *             return 'token-for-test' //Vue.prototype.$store.state.userToken
 *         },
 *         getServerMethods: (r) => {
 *             console.log('getServerMethods', r)
 *             //Vue.prototype.$fapi = r
 *
 *             //select tabA
 *             r.tabA.select(({ prog, p, m }) => {
 *                 console.log('select tabA', prog, p, m)
 *             })
 *                 .then((res) => {
 *                     console.log('r.tabA.select then', res)
 *                 })
 *                 .catch((err) => {
 *                     console.log('r.tabA.select catch', err)
 *                 })
 *
 *             //select tabB
 *             r.tabB.select(({ prog, p, m }) => {
 *                 console.log('select tabB', prog, p, m)
 *             })
 *                 .then((res) => {
 *                     console.log('r.tabB.select then', res)
 *                 })
 *                 .catch((err) => {
 *                     console.log('r.tabB.select catch', err)
 *                 })
 *
 *             //uploadFile
 *             r.uploadFile({
 *                 name: 'zdata.b1',
 *                 u8a: new Uint8Array([66, 97, 115]),
 *             }, ({ prog, p, m }) => {
 *                 console.log('uploadFile', prog, p, m)
 *             })
 *
 *             //add
 *             r.add({
 *                 pa: 1,
 *                 pb: 2.5,
 *             }, ({ prog, p, m }) => {
 *                 console.log('add', prog, p, m)
 *             })
 *                 .then((res) => {
 *                     console.log('add then', res)
 *                 })
 *                 .catch((err) => {
 *                     console.log('add catch', err)
 *                 })
 *
 *         },
 *         recvData: (r) => {
 *             console.log('recvData', r)
 *             //Vue.prototype.$store.commit(Vue.prototype.$store.types.UpdateTableData, r)
 *         },
 *         getRefreshState: (r) => {
 *             console.log('getRefreshState', 'needToRefresh', r.needToRefresh)
 *         },
 *         getRefreshTable: (r) => {
 *             console.log('getRefreshTable', 'tableName', r.tableName, 'timeTag', r.timeTag)
 *         },
 *         getBeforeUpdateTableTags: (r) => {
 *             console.log('getBeforeUpdateTableTags', 'needToRefresh', JSON.stringify(r.oldTableTags) !== JSON.stringify(r.newTableTags))
 *         },
 *         getAfterUpdateTableTags: (r) => {
 *             console.log('getAfterUpdateTableTags', 'needToRefresh', JSON.stringify(r.oldTableTags) !== JSON.stringify(r.newTableTags))
 *         },
 *     })
 *
 *     instWServHapiClient.on('error', (err) => {
 *         console.log(err)
 *     })
 *
 * }
 * client()
 *
 * // getServerMethods {
 * //   tabA: {
 * //     select: [AsyncFunction: f],
 * //     insert: [AsyncFunction: f],
 * //     save: [AsyncFunction: f],
 * //     del: [AsyncFunction: f]
 * //   },
 * //   tabB: {
 * //     select: [AsyncFunction: f],
 * //     insert: [AsyncFunction: f],
 * //     save: [AsyncFunction: f],
 * //     del: [AsyncFunction: f]
 * //   },
 * //   uploadFile: [AsyncFunction: f],
 * //   add: [AsyncFunction: f]
 * // }
 * // select tabA 100 98 upload
 * // select tabB 100 98 upload
 * // add 100 107 upload
 * // getBeforeUpdateTableTags needToRefresh true
 * // getRefreshState needToRefresh true
 * // getRefreshTable tableName tabA timeTag tag-1
 * // select tabA 100 246 download
 * // r.tabA.select then [
 * //   { id: 'id-tabA-peter', name: 'peter', value: 123 },
 * //   { id: 'id-tabA-rosemary', name: 'rosemary', value: 123.456 },
 * //   { id: 'id-tabA-kettle', name: 'kettle', value: 456 }
 * // ]
 * // add 100 93 download
 * // add then result: pa+pb=3.5
 * // recvData {
 * //   tableName: 'tabA',
 * //   timeTag: 'tag-1',
 * //   data: [
 * //     { id: 'id-tabA-peter', name: 'peter', value: 123 },
 * //     { id: 'id-tabA-rosemary', name: 'rosemary', value: 123.456 },
 * //     { id: 'id-tabA-kettle', name: 'kettle', value: 456 }
 * //   ]
 * // }
 * // getAfterUpdateTableTags needToRefresh false
 * // select tabB 100 194 download
 * // r.tabB.select then [
 * //   { id: 'id-tabB-peter', name: 'peter', value: 123 },
 * //   { id: 'id-tabB-rosemary', name: 'rosemary', value: 123.456 }
 * // ]
 * // getBeforeUpdateTableTags needToRefresh true
 * // getRefreshState needToRefresh true
 * // getRefreshTable tableName tabA timeTag tag-2
 * // recvData {
 * //   tableName: 'tabA',
 * //   timeTag: 'tag-2',
 * //   data: [
 * //     { id: 'id-tabA-peter', name: 'peter', value: 'value-1' },
 * //     { id: 'id-tabA-rosemary', name: 'rosemary', value: 123.456 },
 * //     { id: 'id-tabA-kettle', name: 'kettle', value: 456 }
 * //   ]
 * // }
 * // getAfterUpdateTableTags needToRefresh false
 * // getBeforeUpdateTableTags needToRefresh true
 * // getRefreshState needToRefresh true
 * // getRefreshTable tableName tabA timeTag tag-3
 * // recvData {
 * //   tableName: 'tabA',
 * //   timeTag: 'tag-3',
 * //   data: [
 * //     { id: 'id-tabA-peter', name: 'peter', value: 'value-2' },
 * //     { id: 'id-tabA-rosemary', name: 'rosemary', value: 123.456 },
 * //     { id: 'id-tabA-kettle', name: 'kettle', value: 456 }
 * //   ]
 * // }
 * // getAfterUpdateTableTags needToRefresh false
 * // getBeforeUpdateTableTags needToRefresh true
 * // getRefreshState needToRefresh true
 * // getRefreshTable tableName tabA timeTag tag-4
 * // recvData {
 * //   tableName: 'tabA',
 * //   timeTag: 'tag-4',
 * //   data: [
 * //     { id: 'id-tabA-peter', name: 'peter', value: 'value-3' },
 * //     { id: 'id-tabA-rosemary', name: 'rosemary', value: 123.456 },
 * //     { id: 'id-tabA-kettle', name: 'kettle', value: 456 }
 * //   ]
 * // }
 * // getAfterUpdateTableTags needToRefresh false
 * // getBeforeUpdateTableTags needToRefresh true
 * // getRefreshState needToRefresh true
 * // getRefreshTable tableName tabA timeTag tag-5
 * // recvData {
 * //   tableName: 'tabA',
 * //   timeTag: 'tag-5',
 * //   data: [
 * //     { id: 'id-tabA-peter', name: 'peter', value: 'value-4' },
 * //     { id: 'id-tabA-rosemary', name: 'rosemary', value: 123.456 },
 * //     { id: 'id-tabA-kettle', name: 'kettle', value: 456 }
 * //   ]
 * // }
 * // getAfterUpdateTableTags needToRefresh false
 * // getBeforeUpdateTableTags needToRefresh true
 * // getRefreshState needToRefresh true
 * // getRefreshTable tableName tabA timeTag tag-6
 * // recvData {
 * //   tableName: 'tabA',
 * //   timeTag: 'tag-6',
 * //   data: [
 * //     { id: 'id-tabA-peter', name: 'peter', value: 'value-5' },
 * //     { id: 'id-tabA-rosemary', name: 'rosemary', value: 123.456 },
 * //     { id: 'id-tabA-kettle', name: 'kettle', value: 456 }
 * //   ]
 * // }
 * // getAfterUpdateTableTags needToRefresh false
 *
 */
function WServHapiClient(opt = {}) {
    let instWConverClient = null

    //env
    let env = isWindow() ? 'browser' : 'nodejs'
    // console.log('env', env)

    async function core() {

        //FormData
        let FormData = get(opt, 'FormData')
        // if (!isfun(FormData)) {
        //     throw new Error(`invalid opt.FormData in nodejs`)
        // }

        //check, 於瀏覽器端自動停用外部引入之FormData
        if (env === 'browser') {
            FormData = undefined
        }

        //url
        let url = get(opt, 'url')
        if (!isestr(url)) {
            url = 'http://localhost:8080'
        }

        //apiName
        let apiName = get(opt, 'apiName')
        if (!isestr(apiName)) {
            apiName = 'api'
        }

        //useWaitToken
        let useWaitToken = get(opt, 'useWaitToken', null)
        if (!isbol(useWaitToken)) {
            useWaitToken = false
        }

        //getToken
        let getToken = get(opt, 'getToken')
        if (!isfun(getToken)) {
            if (useWaitToken) {
                throw new Error('invalid opt.getToken when opt.useWaitToken=true')
            }
            getToken = () => {
                return ''
            }
        }

        //tokenType
        let tokenType = get(opt, 'tokenType')
        if (!isestr(tokenType)) {
            tokenType = 'Bearer'
        }

        //sizeSlice
        let sizeSlice = get(opt, 'sizeSlice')
        if (!ispint(sizeSlice)) {
            sizeSlice = 1024 * 1024 //1m
        }

        //timeout
        let timeout = get(opt, 'timeout')
        if (!isp0int(timeout)) {
            timeout = 5 * 60 * 1000 //5min
        }

        //retryMain
        let retryMain = get(opt, 'retryMain')
        if (!isp0int(retryMain)) {
            retryMain = 3
        }

        //retryUpload
        let retryUpload = get(opt, 'retryUpload')
        if (!isp0int(retryUpload)) {
            retryUpload = 10
        }

        //retryDownload
        let retryDownload = get(opt, 'retryDownload')
        if (!isp0int(retryDownload)) {
            retryDownload = 2
        }

        //getServerMethods
        let getServerMethods = get(opt, 'getServerMethods')
        if (!isfun(getServerMethods)) {
            getServerMethods = () => { }
        }

        //recvData
        let recvData = get(opt, 'recvData')
        if (!isfun(recvData)) {
            recvData = () => { }
        }

        //getRefreshState, getRefreshTable, getBeforeUpdateTableTags, getAfterUpdateTableTags
        let getRefreshState = get(opt, 'getRefreshState')
        let getRefreshTable = get(opt, 'getRefreshTable')
        let getBeforeUpdateTableTags = get(opt, 'getBeforeUpdateTableTags')
        let getAfterUpdateTableTags = get(opt, 'getAfterUpdateTableTags')

        //showLog
        let showLog = get(opt, 'showLog')
        if (!isbol(showLog)) {
            showLog = true
        }

        //useWaitToken, 等待token
        if (useWaitToken) {
            if (showLog) {
                console.log('waiting token...')
            }

            //waitFun
            await waitFun(async () => {

                //getToken
                let token = getToken()
                if (ispm(token)) {
                    token = await token
                }

                return isestr(token)
            }, { timeInterval: 100 })

            if (showLog) {
                console.log('get token')
            }
        }

        //WConverhpClient
        instWConverClient = new WConverhpClient({
            FormData, //w-converhp的WConverhpClient, 於nodejs使用FormData需安裝套件並提供, 於browser就使用內建FormData故可不用給予
            url,
            apiName,
            getToken, //token會放於headers內, 供execute、upload與download使用, 於伺服器verifyConn驗證用
            tokenType,
            sizeSlice,
            timeout,
            retryMain,
            retryUpload,
            retryDownload,
        })

        //WServWebdataClient
        instWConverClient = new WServWebdataClient(
            instWConverClient,
            {

                // getToken: () => {
                //     return Vue.prototype.$store.state.userToken
                // },
                getToken, //token會放於數據內, 於伺服器再通過getUserIdByToken取得使用者id用

                // getServerMethods: (r) => {
                //     console.log('$fapi', r)
                //     Vue.prototype.$fapi = r
                //     Vue.prototype.$store.commit(Vue.prototype.$store.types.UpdateDriveable, true)
                // },
                getServerMethods,

                // recvData: (r) => {
                //     console.log('recvData', r.tableName, r.data)
                //     Vue.prototype.$store.commit(Vue.prototype.$store.types.UpdateTableData, r)
                // },
                recvData,

                getRefreshState,
                getRefreshTable,
                getBeforeUpdateTableTags,
                getAfterUpdateTableTags,
            })

    }

    //core
    core()
        .catch((err) => {
            try {
                instWConverClient.emit('error', err)
            }
            catch (err) {
                throw new Error(err)
            }
        })

    return instWConverClient
}


export default WServHapiClient