import get from 'lodash-es/get.js'
import set from 'lodash-es/set.js'
import each from 'lodash-es/each.js'
import last from 'lodash-es/last.js'
import dropRight from 'lodash-es/dropRight.js'
import genPm from 'wsemi/src/genPm.mjs'
import evem from 'wsemi/src/evem.mjs'
import haskey from 'wsemi/src/haskey.mjs'
import iseobj from 'wsemi/src/iseobj.mjs'
import isfun from 'wsemi/src/isfun.mjs'
import isestr from 'wsemi/src/isestr.mjs'
import ispm from 'wsemi/src/ispm.mjs'
import WSyncWebdataClient from 'w-sync-webdata/src/WSyncWebdataClient.mjs'
import WServBroadcastClient from 'w-serv-broadcast/src/WServBroadcastClient.mjs'
/**
* 瀏覽器端之資料控制與同步器
*
* @class
* @param {Object} instWConverClient 輸入通訊服務實體物件,可使用例如WConverhpClient等建立
* @param {Object} [opt={}] 輸入設定物件,預設{}
* @param {Function} [opt.getToken=()=>''] 輸入取得使用者token的回調函數,預設()=>''
* @param {Function} opt.getServerMethods 輸入提供操作物件的回調函數,前後端通訊先取得可呼叫函數清單,映射完之後,後端函數都將放入物件當中,key為函數名而值為函數,並通過回調函數提供該物件
* @param {Function} opt.recvData 輸入取得變更表資料的回調函數
* @returns {Object} 回傳事件物件,可監聽error事件
* @example
*
* import FormData from 'form-data'
* import WConverhpClient from 'w-converhp/src/WConverhpClient.mjs' //編譯後axios與form-data都不適合執行於nodejs, 故需引用原程式碼執行
* import WServWebdataClient from './src/WServWebdataClient.mjs'
*
* let ms = []
*
* //instWConverClient
* let instWConverClient = new WConverhpClient({
* FormData, //w-converhp的WConverhpClient, 於nodejs使用FormData需安裝套件並提供, 於browser就使用內建FormData故可不用給予
* //url: window.location.origin + window.location.pathname,
* url: 'http://localhost:9000',
* })
*
* //instWConverClient
* instWConverClient = new WServWebdataClient(
* instWConverClient,
* {
* getToken: () => {
* return '' //Vue.prototype.$store.state.userToken
* },
* getServerMethods: (r) => {
* console.log('getServerMethods', r)
* //Vue.prototype.$fapi = r
*
* //$fapi
* let $fapi = r
*
* let core = async() => {
*
* //select tabA
* await $fapi.tabA.select(({ prog, p, m }) => {
* console.log('select tabA', prog, p, m)
* })
* .then((res) => {
* console.log('tabA.select then', res)
* ms.push({ 'select tabA': JSON.stringify(res) })
* })
* .catch((err) => {
* console.log('tabA.select catch', err)
* })
*
* //select tabB
* await $fapi.tabB.select(({ prog, p, m }) => {
* console.log('select tabB', prog, p, m)
* })
* .then((res) => {
* console.log('tabB.select then', res)
* ms.push({ 'select tabB': JSON.stringify(res) })
* })
* .catch((err) => {
* console.log('tabB.select catch', err)
* })
*
* //add
* ms.push({ 'call add before': '' })
* await $fapi.add({
* pa: 1,
* pb: 2.5,
* }, ({ prog, p, m }) => {
* console.log('add', prog, p, m)
* })
* .then((res) => {
* console.log('add then', res)
* ms.push({ 'call add after': res })
* })
* .catch((err) => {
* console.log('add catch', err)
* })
*
* //uploadFile
* ms.push({ 'call uploadFile before': '' })
* await $fapi.uploadFile({
* name: 'zdata.b1',
* u8a: new Uint8Array([66, 97, 115]),
* }, ({ prog, p, m }) => {
* console.log('uploadFile', prog, p, m)
* })
* .then((res) => {
* console.log('uploadFile then', res)
* ms.push({ 'call uploadFile after': res })
* })
* .catch((err) => {
* console.log('uploadFile catch', err)
* })
*
* }
* core()
* .catch(() => {})
*
* },
* 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))
* },
* })
*
* //error
* instWConverClient.on('error', (err) => {
* console.log('error', err)
* })
*
* //sync會通過broadcast給前端還需要時間處理, 故不能於滿足條件就stop
* setTimeout(() => {
* instWConverClient.clearBroadcast()
* console.log('ms', ms)
* }, 14000)
* // => ms [
* // {
* // 'select tabA': '[{"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}]'
* // },
* // {
* // 'select tabB': '[{"id":"id-tabB-peter","name":"peter","value":123},{"id":"id-tabB-rosemary","name":"rosemary","value":123.456}]'
* // },
* // { 'call add before': '' },
* // { 'call add after': 3.5 },
* // { 'call uploadFile before': '' },
* // { 'call uploadFile after': { name: 'zdata.b1', size: 3 } }
* // ]
*
*/
function WServWebdataClient(instWConverClient, opt = {}) {
let kpExec = {}
//check
if (!iseobj(instWConverClient)) {
console.log('instWConverClient is not an effective object, and set instWConverClient to an EventEmitter')
instWConverClient = evem()
}
if (!haskey(instWConverClient, 'emit')) {
throw new Error(`instWConverClient is not an EventEmitter`)
}
//getToken
let getToken = get(opt, 'getToken', null)
if (!isfun(getToken)) {
getToken = () => {
return ''
}
}
//getServerMethods
let getServerMethods = get(opt, 'getServerMethods', null)
if (!isfun(getServerMethods)) {
instWConverClient.emit('error', 'invalid opt.getServerMethods')
return instWConverClient
}
//recvData
let recvData = get(opt, 'recvData', null)
if (!isfun(recvData)) {
instWConverClient.emit('error', 'invalid opt.recvData')
return instWConverClient
}
//getRefreshState, getRefreshTable, getBeforeUpdateTableTags, getAfterUpdateTableTags
let getRefreshState = get(opt, 'getRefreshState', null)
let getRefreshTable = get(opt, 'getRefreshTable', null)
let getBeforeUpdateTableTags = get(opt, 'getBeforeUpdateTableTags', null)
let getAfterUpdateTableTags = get(opt, 'getAfterUpdateTableTags', null)
//擴充同步資料功能
instWConverClient = new WSyncWebdataClient(instWConverClient)
//擴充廣播功能
instWConverClient = new WServBroadcastClient(instWConverClient)
function executeShell(func) {
//通過instWConverClient.execute調用後端函數func
async function f() {
//pm
let pm = genPm()
//args
let args = [...arguments]
// console.log('args1', args)
//fprog
let fprog = () => {}
let argLast = last(args) //若最後一個參數是函數, 因前端對後端無法使用回調函數, 故一定為監聽上下傳的進度函數
// console.log('argLast', argLast)
if (isfun(argLast)) {
fprog = argLast
args = dropRight(args) //剔除最後的監聽上下傳的進度函數
}
// console.log('args2', args)
//token
let token = getToken()
if (ispm(token)) {
token = await token
}
//check, 若允許undefined會導致傳輸input時欄位__sysToken__消失, 故強制取代為空字串
if (!isestr(token)) {
token = ''
}
//input
let input = { __sysInputArgs__: args, __sysToken__: token }
//execute
await instWConverClient.execute(func, input, fprog)
.then((r) => {
// console.log('instWConverClient.execute then', r)
let res = r.msg
if (r.state === 'success') {
pm.resolve(res)
}
else {
pm.reject(res)
}
})
.catch((err) => {
// console.log('instWConverClient.execute catch', err)
pm.reject(err)
})
return pm
}
return f
}
function bindFuncs(funcs) {
//將函數清單自動綁定至物件kpExec
//kpExec
kpExec = {}
each(funcs, (func) => {
set(kpExec, func, executeShell(func))
})
//getServerMethods
getServerMethods(kpExec)
}
function updateSyncTable(data) {
//當收到後端broadcast
//check, 當mode='syncKpTable'代表為資料同步器發送訊息(各資料表時間戳), 調用instWConverClient.updateTableTags(傳入時間戳), 會觸發instWConverClient.refreshTable事件, 於內呼叫API來更新資料
if (get(data, 'mode') === 'syncKpTable') {
// console.log('syncKpTable', data)
//updateTableTags
instWConverClient.updateTableTags(get(data, 'data'))
}
}
instWConverClient.on('openOnce', function() {
// console.log('instWConverClient: openOnce')
//core
let core = async() => {
//1.openOnce是第1次完成通過execute調用[sys:polling]後才會觸發
//2.於openOnce內再通過execute調用[sys:getFuncList]後, 才能取得伺服器可用函數清單, 有orm可用函數清單才有辦法綁定kpExec, 才有辦法針對後端orm對應函數執行select撈取資料庫數據
//3.updateSyncTable內是通過updateTableTags去觸發[w-sync-webdata的client]的refreshTable, refreshTable再通過kpExec[input.tableName].select()去撈資料庫資料
//4.調用[sys:getFuncList]與[sys:getTableTags]不能保證回傳順序, 得要強制await
//getFuncList, 取得可用函數清單
let res = await executeShell('[sys:getFuncList]')()
// console.log('[sys:getFuncList] res', res)
//bindFuncs
bindFuncs(res)
//getTableTags, 取得同步資料
let data = await executeShell('[sys:getTableTags]')()
// console.log('[sys:getTableTags] data', data)
//updateSyncTable, 啟動並連線成功後取得時間戳
updateSyncTable(data)
}
//core
core()
.catch((err) => {
instWConverClient.emit('error', err)
})
})
instWConverClient.on('broadcast', function(data) {
// console.log('instWConverClient: broadcast', data)
//updateSyncTable
updateSyncTable(data)
})
// //setTableTags, 前端先不暫存時間戳於localStorage
// instWConverClient.setTableTags(tableTagsCl)
//refreshState
instWConverClient.on('refreshState', (msg) => {
// console.log('refreshState needToRefresh', msg.needToRefresh)
if (isfun(getRefreshState)) {
getRefreshState(msg)
}
})
//refreshTable, 收到資料表時間戳有變更通知
instWConverClient.on('refreshTable', (input) => {
// console.log('refreshTable', input)
//check
if (!iseobj(kpExec[input.tableName])) {
instWConverClient.emit('error', `invalid kpExec[${input.tableName}]`)
return
}
//getRefreshTable
if (isfun(getRefreshTable)) {
getRefreshTable(input)
}
//exec
let exec = get(kpExec, input.tableName)
//check
if (!iseobj(exec)) {
console.log(`kpExec[${input.tableName}] is not an effective object`)
return
}
//funSelect
let funSelect = get(exec, 'select')
//check
if (!isfun(funSelect)) {
console.log(`kpExec[${input.tableName}].select is not a function`)
return
}
//select
// console.log('getAPIData before: ', input.tableName)
funSelect() //沒限制{isActive:'y'}, 後端須基於權限給予適合數據
.then((data) => {
// console.log('getAPIData after: ', input.tableName, data)
input.pm.resolve(data)
})
.catch((err) => {
console.log(`${input.tableName}.select: catch`, err)
input.pm.resolve([]) //refreshTable內通過pm回傳, 發生非預期錯誤用resolve回傳避免自動同步中斷
})
})
//getData
instWConverClient.on('getData', (data) => {
// console.log('getData', data)
//recvData
recvData(data)
})
//beforeUpdateTableTags, afterUpdateTableTags, beforePollingTableTags, afterPollingTableTags
instWConverClient.on('beforeUpdateTableTags', (msg) => {
// console.log('client: beforeUpdateTableTags', msg)
if (isfun(getBeforeUpdateTableTags)) {
getBeforeUpdateTableTags(msg)
}
})
instWConverClient.on('afterUpdateTableTags', (msg) => {
// console.log('client: afterUpdateTableTags', msg)
if (isfun(getAfterUpdateTableTags)) {
getAfterUpdateTableTags(msg)
}
})
return instWConverClient
}
export default WServWebdataClient