import get from 'lodash-es/get.js'
import each from 'lodash-es/each.js'
import cloneDeep from 'lodash-es/cloneDeep.js'
import genPm from 'wsemi/src/genPm.mjs'
import evem from 'wsemi/src/evem.mjs'
import haskey from 'wsemi/src/haskey.mjs'
import isbol from 'wsemi/src/isbol.mjs'
import ispint from 'wsemi/src/ispint.mjs'
import iseobj from 'wsemi/src/iseobj.mjs'
import cint from 'wsemi/src/cint.mjs'
import delay from 'wsemi/src/delay.mjs'
import isWindow from 'wsemi/src/isWindow.mjs'
/**
* 瀏覽器端之資料同步器
*
* @class
* @param {Object} instWConverClient 輸入通訊服務實體物件,可使用例如WConverhpClient等建立
* @param {Object} [opt={}] 輸入設定物件,預設{}
* @param {Boolean} [opt.autoPollingTableTagsForActive=false] 輸入是否當使用者活躍時自動進行輪詢布林值,預設false
* @param {Integer} [opt.pollingDelayTime=2000] 輸入輪詢強制延遲時間整數,單位ms,預設2000
* @returns {Object} 回傳前端資料同步物件,可監聽事件refreshTags、refreshState、refreshTable、getData、error,可使用函數setTableTags、updateTableTags、pollingTableTags
* @example
*
* //前後端共用範例
* import w from 'wsemi'
* import WSyncWebdataServer from './src/WSyncWebdataServer.mjs'
* import WSyncWebdataClient from './src/WSyncWebdataClient.mjs'
*
* let ms = []
*
* let ntg = 0
* let genTag = () => {
* ntg++
* return `tag-${ntg}`
* }
*
* //-------- 後端 ---------
*
* //EventEmitter, 模擬後端推播至前端
* let ee = w.evem()
*
* //instWConverServer
* let instWConverServer = w.evem()
*
* //wsds
* let wsds = new WSyncWebdataServer(
* instWConverServer,
* {
* // fpTableTags: 'tableTags-sync-webdata.json',
* genTag,
* },
* )
*
* //tableTagsSrv
* let tableTagsSrv = {
* tabA: 'tag-0-a',
* tabB: 'tag-0-b',
* }
*
* //initTableTags
* wsds.initTableTags(tableTagsSrv)
*
* //readTableTags
* console.log('server: nowTableTags', wsds.readTableTags())
* ms.push({ 'server nowTableTags': JSON.stringify(wsds.readTableTags()) })
*
* //updateTableTag
* setTimeout(() => {
* wsds.updateTableTag('tabA')
* ms.push({ 'server updateTableTag': 'tabA' })
* }, 500)
* setTimeout(() => {
* wsds.updateTableTag('tabB')
* ms.push({ 'server updateTableTag': 'tabB' })
* }, 750)
* setTimeout(() => {
* wsds.updateTableTag('tabA')
* ms.push({ 'server updateTableTag': 'tabA' })
* }, 1000)
*
* //changeTableTags
* wsds.on('changeTableTags', (nowTableTags) => {
* console.log('server: changeTableTags', nowTableTags)
*
* //server push
* console.log('server: push')
* ms.push({ 'server changeTableTags': JSON.stringify(nowTableTags) })
* ee.emit('push', nowTableTags)
*
* })
*
* //error
* wsds.on('error', (err) => {
* console.log('server: error', err)
* })
*
* //getAPIData, 模擬後端提供API
* let tabsCount = {
* tabA: 0,
* tabB: 0,
* }
* async function getAPIData(tableName) {
* ms.push({ 'server call getAPIData before': tableName })
* return new Promise((resolve, reject) => {
* setTimeout(() => {
* tabsCount[tableName] += 1
* ms.push({ 'server call getAPIData after': `table[${tableName}] = ${tabsCount[tableName]}` })
* resolve(`table[${tableName}] = ${tabsCount[tableName]}`)
* }, 100)
* })
* }
*
* //-------- 前端 ---------
*
* //instWConverClient
* let instWConverClient = w.evem()
*
* //wsdc
* let wsdc = new WSyncWebdataClient(instWConverClient, {})
*
* //tableTagsCl
* let tableTagsCl = {
* tabA: 'tag-0-a',
* tabB: 'tag-0-b',
* }
*
* //setTableTags, 模擬前端將tableTags預先或有變更即儲存至localStorage, 啟動時有既有tableTags
* wsdc.setTableTags(tableTagsCl)
* ms.push({ 'clinet setTableTags': JSON.stringify(tableTagsCl) })
*
* //push, 模擬前端接收後端推播有變更tableTags
* ee.on('push', (nowTableTags) => {
*
* //updateTableTags
* wsdc.updateTableTags(nowTableTags)
* ms.push({ 'clinet updateTableTags': JSON.stringify(nowTableTags) })
*
* })
*
* //refreshState
* wsdc.on('refreshState', (msg) => {
* console.log('client: refreshState needToRefresh', msg.needToRefresh)
* })
*
* //refreshTable
* wsdc.on('refreshTable', (input) => {
* // console.log('client: refreshTable', input)
*
* //收到有變更tableTags, 模擬呼叫後端API
* console.log('client: getAPIData before: ' + input.tableName)
* ms.push({ 'clinet call getAPIData before': input.tableName })
* getAPIData(input.tableName)
* .then((data) => {
* console.log('client: getAPIData after: ' + data)
* ms.push({ 'clinet call getAPIData after': data })
* input.pm.resolve(data) //Use pm.resolve to retrieve the data, and pm.reject the message when get an error.
* })
* .catch((err) => {
* input.pm.reject(err)
* })
*
* })
*
* //getData
* wsdc.on('getData', (data) => {
* console.log('client: getData', data)
* //前端取得數據
* })
*
* //beforeUpdateTableTags, afterUpdateTableTags
* wsdc.on('beforeUpdateTableTags', (msg) => {
* // console.log('client: beforeUpdateTableTags', msg)
* })
* wsdc.on('afterUpdateTableTags', (msg) => {
* // console.log('client: afterUpdateTableTags', msg)
* })
*
* //error
* wsdc.on('error', (err) => {
* console.log('client: error', err)
* })
*
* setTimeout(() => {
* console.log('ms', ms)
* }, 2000)
*
* // server: nowTableTags { tabA: 'tag-0-a', tabB: 'tag-0-b' }
* // server: changeTableTags { tabA: 'tag-1', tabB: 'tag-0-b' }
* // server: push
* // client: refreshState needToRefresh true
* // client: getAPIData before: tabA
* // client: getAPIData after: table[tabA] = 1
* // client: getData { tableName: 'tabA', timeTag: 'tag-1', data: 'table[tabA] = 1' }
* // server: changeTableTags { tabA: 'tag-1', tabB: 'tag-2' }
* // server: push
* // client: refreshState needToRefresh true
* // client: getAPIData before: tabB
* // client: getAPIData after: table[tabB] = 1
* // client: getData { tableName: 'tabB', timeTag: 'tag-2', data: 'table[tabB] = 1' }
* // server: changeTableTags { tabA: 'tag-3', tabB: 'tag-2' }
* // server: push
* // client: refreshState needToRefresh true
* // client: getAPIData before: tabA
* // client: getAPIData after: table[tabA] = 2
* // client: getData { tableName: 'tabA', timeTag: 'tag-3', data: 'table[tabA] = 2' }
* // ms [
* // { 'server nowTableTags': '{"tabA":"tag-0-a","tabB":"tag-0-b"}' },
* // { 'clinet setTableTags': '{"tabA":"tag-0-a","tabB":"tag-0-b"}' },
* // { 'server updateTableTag': 'tabA' },
* // { 'server changeTableTags': '{"tabA":"tag-1","tabB":"tag-0-b"}' },
* // { 'clinet updateTableTags': '{"tabA":"tag-1","tabB":"tag-0-b"}' },
* // { 'clinet call getAPIData before': 'tabA' },
* // { 'server call getAPIData before': 'tabA' },
* // { 'server updateTableTag': 'tabB' },
* // { 'server call getAPIData after': 'table[tabA] = 1' },
* // { 'clinet call getAPIData after': 'table[tabA] = 1' },
* // { 'server changeTableTags': '{"tabA":"tag-1","tabB":"tag-2"}' },
* // { 'clinet updateTableTags': '{"tabA":"tag-1","tabB":"tag-2"}' },
* // { 'clinet call getAPIData before': 'tabB' },
* // { 'server call getAPIData before': 'tabB' },
* // { 'server updateTableTag': 'tabA' },
* // { 'server call getAPIData after': 'table[tabB] = 1' },
* // { 'clinet call getAPIData after': 'table[tabB] = 1' },
* // { 'server changeTableTags': '{"tabA":"tag-3","tabB":"tag-2"}' },
* // { 'clinet updateTableTags': '{"tabA":"tag-3","tabB":"tag-2"}' },
* // { 'clinet call getAPIData before': 'tabA' },
* // { 'server call getAPIData before': 'tabA' },
* // { 'server call getAPIData after': 'table[tabA] = 2' },
* // { 'clinet call getAPIData after': 'table[tabA] = 2' }
* // ]
*
*/
function WSyncWebdataClient(instWConverClient, opt = {}) {
let nowTableTags = {}
let isPolling = false
//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`)
}
//autoPollingTableTagsForActive
let autoPollingTableTagsForActive = get(opt, 'autoPollingTableTagsForActive')
if (!isbol(autoPollingTableTagsForActive)) {
autoPollingTableTagsForActive = false //若使用後端broadcast就不用自動輪循
}
//pollingDelayTime
let pollingDelayTime = get(opt, 'pollingDelayTime')
if (!ispint(pollingDelayTime)) {
pollingDelayTime = 2000
}
pollingDelayTime = cint(pollingDelayTime)
//eeEmit
let eeEmit = (name, ...args) => {
setTimeout(() => {
instWConverClient.emit(name, ...args)
}, 1)
}
/**
* 直接設定各資料表時間資料
*
* @memberof WSyncWebdataClient
* @param {Object} tableTags 輸入各資料表時間戳物件
* @example
*
* let tableTags = {...}
* wsdc.setTableTags(tableTags)
*
*/
function setTableTags(tableTags = {}) {
nowTableTags = tableTags
}
/**
* 主動更新指定資料表之時間戳,當有新的資料表時間戳資料時調用此函數進行更新
*
* @memberof WSyncWebdataClient
* @param {Object} tableTags 輸入各資料表時間戳物件
* @example
*
* let tableTags = {...}
* wsdc.updateTableTags(tableTags)
*
*/
async function updateTableTags(tableTags = {}) {
let pms = []
//emit
eeEmit('beforeUpdateTableTags', {
oldTableTags: cloneDeep(nowTableTags),
newTableTags: cloneDeep(tableTags),
})
//needToRefresh
let needToRefresh = false
each(tableTags, (v, k) => {
//原有更新時間戳
let vv = nowTableTags[k]
//check
if (vv !== v) {
needToRefresh = true
}
})
//emit,
eeEmit('refreshState', {
needToRefresh,
oldTableTags: cloneDeep(nowTableTags),
newTableTags: cloneDeep(tableTags),
})
//確認各tags的時間戳
each(tableTags, (v, k) => {
//原有更新時間戳
let vv = nowTableTags[k]
//check
if (vv !== v) {
//pm
let pm = genPm()
//tt
let tt = { tableName: k, timeTag: v, pm }
//push
pms.push(pm)
//emit事件, 外部事件on收到通知時打API去撈指定表數據, 外部由tt.pm回傳成功取得數據或失敗
eeEmit('refreshTable', tt)
//wait
pm
.then((data) => {
//save
nowTableTags[k] = v
//emit, 外部事件on可收到API回傳之數據
eeEmit('getData', { tableName: k, timeTag: v, data })
})
.catch((err) => {
//emit
eeEmit('error', {
msg: 'can not get table data: ' + k,
err,
})
})
}
})
//Promise.all
await Promise.all(pms) //不會有catch
//emit
eeEmit('afterUpdateTableTags', {
oldTableTags: cloneDeep(nowTableTags),
newTableTags: cloneDeep(tableTags),
})
}
/**
* 主動觸發輪詢更新各資料表之時間戳
*
* @memberof WSyncWebdataClient
* @example
*
* wsdc.pollingTableTags()
*
*/
async function pollingTableTags() {
//check
if (isPolling) {
return
}
isPolling = true
//pm
let pm = genPm()
//emit, 外部事件on收到通知時打API去撈各資料表時間戳, 需通過pm回傳成功取得數據或失敗
eeEmit('refreshTags', { pm })
//wait
let tableTags = await pm
.catch((err) => {
//emit
eeEmit('error', {
msg: 'can not get tags data',
err,
})
})
//check
if (tableTags) {
//updateTableTags
await updateTableTags(tableTags) //不會有catch
}
//強制延遲
await delay(pollingDelayTime)
//恢復isPolling
isPolling = false
}
//pollingTableTags
if (autoPollingTableTagsForActive) {
//mouseover
if (isWindow()) {
window.addEventListener('mouseover', (e) => {
pollingTableTags()
}, false)
window.addEventListener('touchmove', (e) => {
pollingTableTags()
}, false)
}
}
//save
instWConverClient.setTableTags = setTableTags
instWConverClient.updateTableTags = updateTableTags
instWConverClient.pollingTableTags = pollingTableTags
return instWConverClient
}
export default WSyncWebdataClient