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 iseobj from 'wsemi/src/iseobj.mjs'
import isfun from 'wsemi/src/isfun.mjs'
import ispm from 'wsemi/src/ispm.mjs'
import WSyncWebdataClient from 'w-sync-webdata/src/WSyncWebdataClient.mjs'
/**
* 瀏覽器端之資料控制與同步器
*
* @class
* @param {Object} [opt={}] 輸入設定物件,預設{}
* @param {Object} opt.instWConverClient 輸入通訊服務實體物件,可使用例如WConverhpClient等建立
* @param {Function} [opt.cbGetToken=()=>''] 輸入取得使用者token的回調函數,預設()=>''
* @param {Function} opt.cbGetServerMethods 輸入提供操作物件的回調函數,前後端通訊先取得可呼叫函數清單,映射完之後,後端函數都將放入物件當中,key為函數名而值為函數,並通過回調函數提供該物件
* @param {Function} opt.cbRecvData 輸入取得變更表資料的回調函數
* @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'
*
*
* //wcc
* let wcc = WConverhpClient({
* FormData, //w-converhp的WConverhpClient, 於nodejs使用FormData需安裝套件並提供, 於browser就使用內建FormData故可不用給予
* //url: window.location.origin + window.location.pathname,
* url: 'http://localhost:9000',
* })
*
* //wsdc
* let wsdc = WServWebdataClient({
* instWConverClient: wcc,
* cbGetToken: () => {
* return '' //Vue.prototype.$store.state.userToken
* },
* cbGetServerMethods: (r) => {
* console.log('cbGetServerMethods', 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]),
* // u8a: new Uint8Array(fs.readFileSync('../_data/500mb.7z')), //最多500mb, 因測試使用w-converhp, 其依賴新版@hapi/pez無法處理1g檔案, 會出現: Invalid string length
* }, ({ prog, p, m }) => {
* console.log('uploadFile', prog, p, m)
* })
*
* },
* cbRecvData: (r) => {
* console.log('cbRecvData', r)
* //Vue.prototype.$store.commit(Vue.prototype.$store.types.UpdateTableData, r)
* },
* cbGetRefreshState: (r) => {
* console.log('cbGetRefreshState', 'needToRefresh', r.needToRefresh)
* },
* cbGetRefreshTable: (r) => {
* console.log('cbGetRefreshTable', 'tableName', r.tableName, 'timeTag', r.timeTag)
* },
* cbBeforeUpdateTableTags: (r) => {
* console.log('cbBeforeUpdateTableTags', 'needToRefresh', JSON.stringify(r.oldTableTags) !== JSON.stringify(r.newTableTags))
* },
* cbAfterUpdateTableTags: (r) => {
* console.log('cbAfterUpdateTableTags', 'needToRefresh', JSON.stringify(r.oldTableTags) !== JSON.stringify(r.newTableTags))
* },
* cbBeforePollingTableTags: () => {
* console.log('cbBeforePollingTableTags')
* },
* cbAfterPollingTableTags: () => {
* console.log('cbAfterPollingTableTags')
* },
* })
*
* //error
* wsdc.on('error', (err) => {
* console.log('error', err)
* })
*
* // cbGetServerMethods {
* // 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]
* // }
* // r.tabB.select then [
* // { id: 'id-tabB-peter', name: 'peter', value: 123 },
* // { id: 'id-tabB-rosemary', name: 'rosemary', value: 123.456 }
* // ]
* // r.tabA.select then [
* // { id: 'id-tabB-peter', name: 'peter', value: 0.6735191308795969 },
* // { 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 }
* // ]
* // cbBeforeUpdateTableTags needToRefresh true
* // cbGetRefreshState needToRefresh true
* // cbGetRefreshTable tableName tabA timeTag xzZGGa
* // cbRecvData {
* // tableName: 'tabA',
* // timeTag: 'xzZGGa',
* // data: [
* // { id: 'id-tabB-peter', name: 'peter', value: 0.6735191308795969 },
* // { 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 }
* // ]
* // }
* // cbAfterUpdateTableTags needToRefresh false
* // cbBeforeUpdateTableTags needToRefresh true
* // cbGetRefreshState needToRefresh true
* // cbGetRefreshTable tableName tabA timeTag 2022-03-02T16:40:46+08:00|MneMQH
* // cbRecvData {
* // tableName: 'tabA',
* // timeTag: '2022-03-02T16:40:46+08:00|MneMQH',
* // data: [
* // { id: 'id-tabB-peter', name: 'peter', value: 0.6735191308795969 },
* // { id: 'id-tabA-peter', name: 'peter', value: 0.5847204423720489 },
* // { id: 'id-tabA-rosemary', name: 'rosemary', value: 123.456 }, { id: 'id-tabA-kettle', name: 'kettle', value: 456 }
* // ]
* // }
* // cbAfterUpdateTableTags needToRefresh false
* // cbBeforeUpdateTableTags needToRefresh true
* // cbGetRefreshState needToRefresh true
* // cbGetRefreshTable tableName tabA timeTag 2022-03-02T16:40:49+08:00|qzQJQ4
* // cbRecvData {
* // tableName: 'tabA',
* // timeTag: '2022-03-02T16:40:49+08:00|qzQJQ4',
* // data: [
* // { id: 'id-tabB-peter', name: 'peter', value: 0.6735191308795969 },
* // { id: 'id-tabA-peter', name: 'peter', value: 0.9801109028960009 },
* // { id: 'id-tabA-rosemary', name: 'rosemary', value: 123.456 }, { id: 'id-tabA-kettle', name: 'kettle', value: 456 }
* // ]
* // }
* // cbAfterUpdateTableTags needToRefresh false
* // cbBeforeUpdateTableTags needToRefresh true
* // cbGetRefreshState needToRefresh true
* // cbGetRefreshTable tableName tabA timeTag 2022-03-02T16:40:52+08:00|Cnk33i
* // cbRecvData {
* // tableName: 'tabA',
* // timeTag: '2022-03-02T16:40:52+08:00|Cnk33i',
* // data: [
* // { id: 'id-tabB-peter', name: 'peter', value: 0.6735191308795969 },
* // { id: 'id-tabA-peter', name: 'peter', value: 0.9667464984165397 },
* // { id: 'id-tabA-rosemary', name: 'rosemary', value: 123.456 }, { id: 'id-tabA-kettle', name: 'kettle', value: 456 }
* // ]
* // }
* // cbAfterUpdateTableTags needToRefresh false
* // cbBeforeUpdateTableTags needToRefresh true
* // cbGetRefreshState needToRefresh true
* // cbGetRefreshTable tableName tabA timeTag 2022-03-02T16:40:55+08:00|wyFygc
* // cbRecvData {
* // tableName: 'tabA',
* // timeTag: '2022-03-02T16:40:55+08:00|wyFygc',
* // data: [
* // { id: 'id-tabB-peter', name: 'peter', value: 0.6735191308795969 },
* // { id: 'id-tabA-peter', name: 'peter', value: 0.311292348917773 },
* // { id: 'id-tabA-rosemary', name: 'rosemary', value: 123.456 }, { id: 'id-tabA-kettle', name: 'kettle', value: 456 }
* // ]
* // }
* // cbAfterUpdateTableTags needToRefresh false
* // cbBeforeUpdateTableTags needToRefresh true
* // cbGetRefreshState needToRefresh true
* // cbGetRefreshTable tableName tabA timeTag 2022-03-02T16:40:58+08:00|Bd82vG
* // cbRecvData {
* // tableName: 'tabA',
* // timeTag: '2022-03-02T16:40:58+08:00|Bd82vG',
* // data: [
* // { id: 'id-tabB-peter', name: 'peter', value: 0.6735191308795969 },
* // { id: 'id-tabA-peter', name: 'peter', value: 0.6912250899420782 },
* // { id: 'id-tabA-rosemary', name: 'rosemary', value: 123.456 }, { id: 'id-tabA-kettle', name: 'kettle', value: 456 }
* // ]
* // }
* // cbAfterUpdateTableTags needToRefresh false
*
*/
function WServWebdataClient(opt = {}) {
let execs = {}
//ev
let ev = evem()
//instWConverClient
let instWConverClient = get(opt, 'instWConverClient', null)
if (instWConverClient === null) {
ev.emit('error', 'invalid opt.instWConverClient')
return ev
}
//cbGetToken
let cbGetToken = get(opt, 'cbGetToken', null)
if (!isfun(cbGetToken)) {
cbGetToken = () => {
return ''
}
}
//cbGetServerMethods
let cbGetServerMethods = get(opt, 'cbGetServerMethods', null)
if (!isfun(cbGetServerMethods)) {
ev.emit('error', 'invalid opt.cbGetServerMethods')
return ev
}
//cbRecvData
let cbRecvData = get(opt, 'cbRecvData', null)
if (!isfun(cbRecvData)) {
ev.emit('error', 'invalid opt.cbRecvData')
return ev
}
//cbGetRefreshState
let cbGetRefreshState = get(opt, 'cbGetRefreshState', null)
let cbGetRefreshTable = get(opt, 'cbGetRefreshTable', null)
let cbBeforeUpdateTableTags = get(opt, 'cbBeforeUpdateTableTags', null)
let cbAfterUpdateTableTags = get(opt, 'cbAfterUpdateTableTags', null)
let cbBeforePollingTableTags = get(opt, 'cbBeforePollingTableTags', null)
let cbAfterPollingTableTags = get(opt, 'cbAfterPollingTableTags', null)
//wsdc
let wsdc = new WSyncWebdataClient()
function executeShell(func) {
//通過instWConverClient.execute調用後端函數
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 = cbGetToken()
if (ispm(token)) {
token = await token
}
//check, 若允許undefined會導致傳輸input時欄位__sysToken__消失, 故強制取代為空字串
if (token === undefined) {
token = ''
}
//input
let input = { __sysInputArgs__: args, __sysToken__: token }
//execute
await instWConverClient.execute(func, input,
function (prog, p, m) {
fprog({ prog, p, m })
})
.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) {
//將函數清單自動綁定至物件execs
//execs
execs = {}
each(funcs, (func) => {
set(execs, func, executeShell(func))
})
//cbGetServerMethods
cbGetServerMethods(execs)
}
instWConverClient.on('openOnce', function() {
// console.log('instWConverClient: openOnce')
//getFuncList, 取得可用函數清單
executeShell('getFuncList')()
.then((res) => {
bindFuncs(res)
})
.catch((err) => {
ev.emit('error', err)
})
})
function updateSyncTable(data) {
//當收到後端broadcast與deliver時
//check, 當mode='syncTable'代表為資料同步器發送訊息(各資料表時間戳), 調用wsdc.updateTableTags(傳入時間戳), 會觸發wsdc.refreshTable事件, 於內呼叫API來更新資料
if (get(data, 'mode') === 'syncTable') {
//updateTableTags
wsdc.updateTableTags(get(data, 'data'))
}
}
instWConverClient.on('broadcast', function(data) {
// console.log('instWConverClient: broadcast', data)
//updateSyncTable
updateSyncTable(data)
})
instWConverClient.on('deliver', function(data) { //使用者第1次瀏覽時會用deliver推播時間戳
// console.log('instWConverClient: deliver', data)
//updateSyncTable
updateSyncTable(data)
})
// //setTableTags, 前端先不暫存時間戳於localStorage
// wsdc.setTableTags(tableTagsCl)
//refreshState
wsdc.on('refreshState', (msg) => {
// console.log('refreshState needToRefresh', msg.needToRefresh)
if (isfun(cbGetRefreshState)) {
cbGetRefreshState(msg)
}
})
//refreshTable, 收到資料表時間戳有變更通知
wsdc.on('refreshTable', (input) => {
// console.log('refreshTable', input)
//check
if (!iseobj(execs[input.tableName])) {
ev.emit('error', `無法存取${input.tableName}資料表`)
return
}
//cbGetRefreshTable
if (isfun(cbGetRefreshTable)) {
cbGetRefreshTable(input)
}
//select, 通過$fapi來取資料
// console.log('getAPIData before: ', input.tableName)
execs[input.tableName].select()
.then((data) => {
// console.log('getAPIData after: ', input.tableName, data)
input.pm.resolve(data)
})
.catch((err) => {
console.log(`${input.tableName}.select: catch`, err)
})
})
//getData
wsdc.on('getData', (data) => {
// console.log('getData', data)
//cbRecvData
cbRecvData(data)
})
//beforeUpdateTableTags, afterUpdateTableTags, beforePollingTableTags, afterPollingTableTags
wsdc.on('beforeUpdateTableTags', (msg) => {
// console.log('client: beforeUpdateTableTags', msg)
if (isfun(cbBeforeUpdateTableTags)) {
cbBeforeUpdateTableTags(msg)
}
})
wsdc.on('afterUpdateTableTags', (msg) => {
// console.log('client: afterUpdateTableTags', msg)
if (isfun(cbAfterUpdateTableTags)) {
cbAfterUpdateTableTags(msg)
}
})
wsdc.on('beforePollingTableTags', () => {
// console.log('client: beforePollingTableTags')
if (isfun(cbBeforePollingTableTags)) {
cbBeforePollingTableTags()
}
})
wsdc.on('afterPollingTableTags', () => {
// console.log('client: afterPollingTableTags')
if (isfun(cbAfterPollingTableTags)) {
cbAfterPollingTableTags()
}
})
//error
wsdc.on('error', (err) => {
ev.emit('error', err)
})
return ev
}
export default WServWebdataClient