import axios from 'axios'
import get from 'lodash-es/get.js'
import size from 'lodash-es/size.js'
import isWindow from 'wsemi/src/isWindow.mjs'
import evem from 'wsemi/src/evem.mjs'
import genPm from 'wsemi/src/genPm.mjs'
import haskey from 'wsemi/src/haskey.mjs'
import isfun from 'wsemi/src/isfun.mjs'
import ispint from 'wsemi/src/ispint.mjs'
import isp0int from 'wsemi/src/isp0int.mjs'
import isestr from 'wsemi/src/isestr.mjs'
import isobj from 'wsemi/src/isobj.mjs'
import iseobj from 'wsemi/src/iseobj.mjs'
import isbol from 'wsemi/src/isbol.mjs'
import ispm from 'wsemi/src/ispm.mjs'
import cint from 'wsemi/src/cint.mjs'
import dig from 'wsemi/src/dig.mjs'
import strright from 'wsemi/src/strright.mjs'
import b642str from 'wsemi/src/b642str.mjs'
import blob2u8arr from 'wsemi/src/blob2u8arr.mjs'
import obj2u8arr from 'wsemi/src/obj2u8arr.mjs'
import u8arr2obj from 'wsemi/src/u8arr2obj.mjs'
import pmConvertResolve from 'wsemi/src/pmConvertResolve.mjs'
import delay from 'wsemi/src/delay.mjs'
import getFileXxHash from 'wsemi/src/getFileXxHash.mjs'
/**
* 建立Hapi使用者(Node.js與Browser)端物件
*
* @class
* @param {Object} opt 輸入設定參數物件
* @param {String} [opt.url='http://localhost:8080'] 輸入Hapi伺服器網址,預設為'http://localhost:8080'
* @param {String} [opt.apiName='api'] 輸入API名稱字串,預設'api'
* @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
* @returns {Object} 回傳事件物件,可使用函數execute、upload
* @example
*
* import path from 'path'
* import fs from 'fs'
* import _ from 'lodash-es'
* import w from 'wsemi'
* import FormData from 'form-data'
* import WConverhpClient from './src/WConverhpClient.mjs'
*
* let ms = []
*
* let opt = {
* FormData,
* url: 'http://localhost:8080',
* apiName: 'api',
* getToken: () => {
* return 'token-for-test'
* },
* }
*
* //new
* let wo = new WConverhpClient(opt)
*
* wo.on('error', (err) => {
* console.log(`error`, err)
* })
*
* function downloadLargeFile() {
* let core = async() => {
*
* await wo.download('id-for-file',
* function ({ prog, p, m }) {
* // console.log('client web: download: prog', prog, p, m)
* if (m === 'download') {
* console.log('client web: download: prog', prog)
* }
* },
* {
* fdDownload: './', //於nodejs環境才能提供
* })
* .then(function(res) {
* console.log('client web: download: then', res)
* ms.push({ 'download output': res })
* })
* .catch(function (err) {
* console.log('client web: download: catch', err)
* })
*
* console.log('ms', ms)
*
* }
* core()
* }
*
* downloadLargeFile()
*
*/
function WConverhpClient(opt) {
//_url
let _url = get(opt, 'url')
if (!isestr(_url)) {
_url = 'http://localhost:8080'
}
//apiName
let apiName = get(opt, 'apiName')
if (!isestr(apiName)) {
apiName = 'api'
}
//url
let url = ''
if (strright(_url, 1) === '/') {
url = _url + apiName
}
else {
url = _url + '/' + apiName
}
//getToken
let getToken = get(opt, 'getToken', null)
if (!isfun(getToken)) {
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
}
//env
let env = isWindow() ? 'browser' : 'nodejs'
// console.log('env', env)
//ee
let ee = evem() //new events.EventEmitter()
//eeEmit
let eeEmit = (name, ...args) => {
setTimeout(() => {
ee.emit(name, ...args)
}, 1)
}
//getUrlUse
let getUrlUse = (type) => {
//urlUse
let urlUse = ''
if (type === 'basic') {
urlUse = `${url}/main`
}
else if (type === 'upload-controller') {
urlUse = `${url}/ulctr`
}
else if (type === 'slice') {
urlUse = `${url}/slc`
}
else if (type === 'download-get-filename') {
urlUse = `${url}/dwgfn`
}
else if (type === 'download-get') {
urlUse = `${url}/dwgf`
}
else if (type === 'download') {
urlUse = `${url}/dw`
}
else {
throw new Error(`invalid type[${type}]`)
}
return urlUse
}
//res2u8arr
let res2u8arr = async(bb) => {
//blob(in browser) or buffer(in nodejs) to u8a
let u8a
if (env === 'browser') {
u8a = await blob2u8arr(bb)
}
else {
u8a = new Uint8Array(bb)
}
return u8a
}
//u8arr2bb
let u8arr2bb = (u8a) => {
//u8a to blob(in browser) or buffer(in nodejs)
let bb
if (env === 'browser') {
bb = new Blob([u8a.buffer])
}
else { //nodejs
bb = Buffer.from(u8a)
}
return bb
}
//send
let send = async(type, pkg, opt = {}) => {
//headers
let headers = get(opt, 'headers')
if (!isobj(headers)) {
headers = {}
}
// console.log('headers', headers)
//dataType
let dataType = get(opt, 'dataType', '')
if (dataType !== 'blob' && dataType !== 'fmd' && dataType !== 'json') {
dataType = 'blob'
}
// console.log('dataType', dataType)
//cbProgress
let cbProgress = get(opt, 'cbProgress')
if (!isfun(cbProgress)) {
cbProgress = () => {}
}
//retry
let retry = get(opt, 'retry')
if (!isp0int(retry)) {
retry = 1
}
//urlUse
let urlUse = getUrlUse(type)
//pm
let pm = genPm()
//dd, ct
let dd = null
let ct = {}
if (dataType === 'blob') {
//set ct
ct = {
'Content-Type': 'application/octet-stream',
}
//set dd
dd = pkg
}
else if (dataType === 'fmd') {
//fmd
let fmd
if (env === 'browser') {
fmd = new FormData()
}
else {
if (isfun(opt.FormData)) {
fmd = new opt.FormData({ maxDataSize: 1024 * 1024 * 1024 * 1024 }) //nodejs, 使用套件form-data設定資料量最大為1tb
}
else {
// console.log(`invalid opt.FormData, need [npm i form-data] and [import FormData from 'form-data'] to set opt.FormData = FormData`)
eeEmit('error', `invalid opt.FormData, need [npm i form-data] and [import FormData from 'form-data'] to set opt.FormData = FormData`)
throw new Error('invalid opt.FormData')
}
}
//append
fmd.append('bb', pkg)
// console.log('fmd', fmd)
//set ct
if (env === 'nodejs') {
ct = {
'Content-Type': `multipart/form-data; boundary=${fmd.getBoundary()}` //nodejs, 使用套件form-data需設定boundary
}
// console.log('ct', ct)
}
else {
//browser會自動根據fmd的邊界boundary來設定
}
//set dd
dd = fmd
}
else if (dataType === 'json') {
//axios預設會將物件自動轉換為JSON, 此處指定Content-Type且強制轉, 避免可能問題
//set ct
ct = {
'Content-Type': 'application/json',
}
dd = JSON.stringify(pkg)
}
// console.log('dd', dd)
//rt
let rt = null
if (env === 'nodejs') {
if (type === 'download') {
rt = 'stream' //nodejs download模式採用stream接收
}
else {
rt = 'arraybuffer' //nodejs下沒有blob, 只能設定'json', 'arraybuffer', 'document', 'json', 'text', 'stream'
}
}
else {
if (type === 'download') {
rt = 'blob' //瀏覽器使用blob下載
}
else {
rt = 'blob' //瀏覽器使用blob取得資料
}
}
// console.log('rt', rt)
//token
let token = getToken()
if (ispm(token)) {
token = await token
}
//getFilenameByHeader
let getFilenameByHeader = (contentDisposition) => {
let fn = 'unknow'
try {
let reg = /filename="(.+?)"/
let matches = reg.exec(contentDisposition)
fn = matches ? matches[1] : 'unknown'
}
catch (err) {}
return fn
}
//downloadStream
let downloadStream = async(res) => {
// console.log('res.headers', res.headers)
//pm
let pm = genPm()
//returnType
let returnType = get(res, `headers['return-type']`, '')
// console.log('returnType', returnType)
//returnMsg
let returnMsg = get(res, `headers['return-msg']`, '')
// console.log('returnMsg', returnMsg)
//check
if (returnType === 'error') {
pm.reject(returnMsg)
return pm
}
//contentDisposition
let contentDisposition = get(res, `headers['content-disposition']`, '')
// console.log('contentDisposition', contentDisposition)
//filename
let filename = getFilenameByHeader(contentDisposition)
filename = b642str(filename) //headers內對中文支援度不佳須用base64傳, 此處解析提取後須反轉
// console.log('filename', filename)
//streamRecv
let streamRecv = get(res, 'data')
// console.log(env, 'streamRecv', streamRecv)
if (env === 'browser') {
//browser通過axios使用blob接收時會自動把串流接收並組合成blob, 此時streamRecv已是blob
pm.resolve({
filename,
bb: streamRecv,
})
}
else {
//nodejs通過fs與stream接收檔案, stream出錯只會觸發error事件, 此處try catch為攔截其他非stream程式碼錯誤
try {
//path, fs, 使用動態import供nodejs使用, 須用變數字串給予載入套件, 否則用於前端時會被webpack偵測而報錯
let cImPath = 'path'
let cImFs = 'fs'
let path = await import(cImPath)
let fs = await import(cImFs)
//fdDownload, 只有nodejs下載才使用fdDownload
let fdDownload = get(opt, 'fdDownload', '')
fs.mkdirSync(fdDownload, { recursive: true }) //須使用mkdirSync, 不要用fsIsFolder與fsCreateFolder避免編譯
// console.log('fdDownload', fdDownload)
//fp
let fp = path.resolve(fdDownload, filename)
// console.log('fp', fp)
//streamWriter
let streamWriter = fs.createWriteStream(fp)
//pipe
streamRecv.pipe(streamWriter)
//finish
streamWriter.on('finish', () => {
pm.resolve(fp)
})
//error
streamWriter.on('error', (err) => {
pm.reject(err)
})
}
catch (err) {
pm.reject(err)
}
}
return pm
}
//s
let s = {
method: 'POST',
url: urlUse,
data: dd,
headers: {
Authorization: `${tokenType} ${token}`,
...ct,
...headers,
},
timeout,
maxContentLength: Infinity, //1024 * 1024 * 1024, Infinity //axios於nodejs中會限制內容大小故需改為無限
maxBodyLength: Infinity, //1024 * 1024 * 1024, Infinity //axios於nodejs中會限制內容大小故需改為無限
responseType: rt,
onUploadProgress: function(ev) {
//console.log('onUploadProgress', ev)
//r
let r = 0
let loaded = ev.loaded
let total = ev.total
if (ispint(total)) {
r = (loaded * 100) / total
}
//cbProgress
cbProgress({ prog: Math.floor(r), p: loaded, m: 'upload' })
},
onDownloadProgress: function (ev) {
// console.log('onDownloadProgress', ev)
//r
let r = 0
let loaded = ev.loaded
// let total = ev.srcElement.getResponseHeader('Content-length') //若需要得知下載進度, 需於伺服器回傳時提供Content-length
let total = ev.total
if (ispint(total)) {
r = (loaded * 100) / total
}
//cbProgress
cbProgress({ prog: Math.floor(r), p: loaded, m: 'download' })
},
}
// console.log('s', s)
//callApiCore, 處理axios成功then時訊息, catch時直接向外傳遞
let callApiCore = async() => {
//axios, catch時直接向外傳遞
let res = await axios(s)
//check, download時直接轉由downloadStream處理res, catch時直接向外傳遞
if (type === 'download') {
return await downloadStream(res)
}
//bb
let bb = get(res, 'data')
// console.log('bb', bb)
//res2u8arr, catch時直接向外傳遞
let u8a = await res2u8arr(bb)
// console.log('u8a', u8a)
//u8arr2obj
let data = u8arr2obj(u8a)
// console.log('data', data)
//check
if (!iseobj(data)) {
eeEmit('error', `data is not an effective object`)
return Promise.reject(`data is not an effective object`)
}
//分離伺服器資料的success或error
if (haskey(data, 'success')) {
return Promise.resolve(data.success)
}
else if (haskey(data, 'error')) {
eeEmit('error', data.error)
return Promise.reject(data.error)
}
else {
eeEmit('error', `data does not contain success or error`)
return Promise.reject(`data does not contain success or error`)
}
}
//callApi, 處理callApiCore失敗catch時retry
let callApi = async() => {
//pmConvertResolve
let fun = pmConvertResolve(callApiCore)
//fun
let r = await fun(s)
// console.log('r', r)
//while
let maxRetry = 10
let baseDelay = 1000 //初始延遲為1秒
let ratio = Math.pow(180000 / baseDelay, 1 / (maxRetry - 1)) //1.888
let n = 0
while (r.state === 'error') {
//add
n += 1
//check
if (n > retry) {
break
}
//delay
let t = Math.round(baseDelay * Math.pow(ratio, n - 1))
console.log(`wait ${dig(t / 1000, 1)}(second) to retry...`)
await delay(t)
//retry
console.log(`retry n=${n}...`)
r = await fun(s)
// console.log(`retry n=${n} done`)
}
if (r.state === 'success') {
return r.msg
}
else {
return Promise.reject(r.msg)
}
}
//callApi
await callApi()
.then((res) => {
pm.resolve(res)
})
.catch(async(res) => {
// console.log('axios catch', res.toJSON())
//Network Error除可能是網路斷線之外, 可能被瀏覽器外掛封鎖阻擋, 亦可能因硬碟空間不足無法下載被瀏覽器拒絕
//data
let data = null
//statusText, err
let statusText = get(res, 'response.statusText') || get(res, 'message')
let err = get(res, 'response.data') || get(res, 'stack')
// console.log(`get(res, 'response.statusText')`, get(res, 'response.statusText'))
// console.log(`get(res, 'message')`, get(res, 'message'))
// console.log(`get(res, 'response.data')`, get(res, 'response.data'))
// console.log(`get(res, 'stack')`, get(res, 'stack'))
if (statusText) {
// console.log('statusText', statusText)
data = statusText
}
else if (err) {
// console.log('err', err)
data = err
}
else {
try {
res = res.toJSON()
}
catch (err) {}
// console.log('err', res)
eeEmit('error', res)
data = 'Can not connect to server.'
}
if (data === 'Network Error') {
data = `Network Error. Make sure your space of hard drive is large enough or blocking by browser plugins.`
}
// console.log('data', data)
pm.reject(data)
})
return pm
}
//sendPkg
let sendPkg = async(type, data, cbProgress) => {
//主要為中心化控制器使用, 通過execute進行上下傳數據
//bb
let bb = null
try {
//obj2u8arr
let u8a = obj2u8arr(data)
// console.log('u8a', u8a)
//u8a to blob(in browser) or buffer(in nodejs)
bb = u8arr2bb(u8a)
// console.log('bb', bb)
}
catch (err) {
return Promise.reject(err)
}
//send
let res = await send(type, bb, { dataType: 'blob', retry: retryMain, cbProgress })
return res
}
//calcHash
let calcHash = async(inp) => {
//bb
let bb = null
if (env === 'browser') {
bb = inp
}
else {
//於nodejs時, 因尚無法提供檔名上傳, 故會是readFileSync讀入的buffer, 再轉成new Blob([buffer]), 供getFileXxHash使用
bb = new Blob([inp])
}
//hash
let hash = await getFileXxHash(bb)
return hash
}
//sendDataSlice
let sendDataSlice = async(fileTotalName, bb, cbProgress) => {
//n
let n = 0
if (n === 0) {
try {
n = bb.size //for Blob
n = cint(n)
}
catch (err) {}
}
if (n === 0) {
try {
n = bb.length //for ArrayBuffer //nodejs用fs讀有檔案大小上限, 除非改傳入檔名用stream讀, 否則無法支援超大檔
n = cint(n)
}
catch (err) {}
}
if (n === 0) {
// eeEmit('error', `can not get size of bb`)
// return Promise.reject(`can not get size of bb`)
n = 1 //最小給1, 使能支援無大小檔案上傳
}
// console.log('n', n)
//fileTotalSize
let fileTotalSize = n
//chunkTotal
let chunkTotal = Math.ceil(fileTotalSize / sizeSlice)
// console.log('chunkTotal', chunkTotal)
//progCount, progWeightSlice
let progCount = 0
let progWeightSlice = 0.99 //上傳階段進度使用99%
// let progWeightMerge = 0.01 //合併階段進度使用1%
//cbProgressSlice, 以累積機制計算進度, 累積片數配合總切片數量即可算出進度, 故不須輸入msg
let cbProgressSlice = () => {
progCount++
let r = progCount / chunkTotal
let prog = r * progWeightSlice * 100
let psiz = r * fileTotalSize
cbProgress({ prog, p: psiz, m: 'upload' })
}
//cbProgressMerge
let cbProgressMerge = (msg) => {
let perc = msg.prog
let dir = msg.m
if (dir === 'download' && perc === 100) {
cbProgress({ prog: 100, p: fileTotalSize, m: 'upload' })
}
}
//fileTotalHash
// console.log(`calc hash for fileTotalSize[${fileTotalSize}]...`)
let fileTotalHash = await calcHash(bb)
// console.log(`calc hash for fileTotalSize[${fileTotalSize}] done`, fileTotalHash)
//send check-total-hash
// console.log(`send check-total-hash...`)
let resUpCkt = await send('upload-controller', { mode: 'check-total-hash', fileHash: fileTotalHash, filename: fileTotalName, fileSize: fileTotalSize }, { dataType: 'json', retry: retryUpload })
// console.log('resUpCkt', resUpCkt)
// resUpCkt {
// path: 'uploadTemp\\2429b7ef08ce6ba9',
// bAllExist: false,
// bAllSize: false,
// bAllHash: false,
// bSls: true,
// slks: [
// 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
// ]
// }
//check
if (resUpCkt.bAllHash) {
// console.log('已有上傳大檔')
//cbProgressMerge
cbProgressMerge({ prog: 100, m: 'download' }) //觸發上傳完畢後之下載回應, 故m須為download
//resMg, 直接回傳
let resMg = {
filename: fileTotalName,
path: resUpCkt.path,
}
return resMg
}
//針對伺服器上已有切片檔案計算hash與比對
if (resUpCkt.bSls) {
// console.log('receive slks...', resUpCkt.slks[0], size(resUpCkt.slks))
//fileSliceHashs
let fileSliceHashs = []
// let n = Math.max(resUpCkt.slks.length, 1)
// let nr = Math.floor(n / 100)
for (let k = 0; k < size(resUpCkt.slks); k++) {
// if (k % nr === 0) {
// console.log(`calc hash for slices`, round(k / resUpCkt.slks.length * 100, 1), '%')
// }
//i
let i = resUpCkt.slks[k]
//start
let start = i * sizeSlice
//end
let end = Math.min(start + sizeSlice, fileTotalSize)
//chunk
let chunk = bb.slice(start, end)
//fileSliceHash
let fileSliceHash = await calcHash(chunk)
// console.log('fileSliceHash', fileSliceHash)
//push
fileSliceHashs.push({
i,
h: fileSliceHash,
})
}
// console.log('fileSliceHashs', fileSliceHashs)
//send check-slices-hash
// console.log(`send check-slices-hash...`)
let resUpCks = await send('upload-controller', { mode: 'check-slices-hash', fileHash: fileTotalHash, fileSliceHashs }, { dataType: 'json', retry: retryUpload })
// console.log('resUpCks', resUpCks)
// resUpCks {
// slks: [
// 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
// 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
// 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,
// 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
// 48, 49, 50, 51, 52, 53
// ]
// }
//update, 伺服器針對各切片計算hash與比對, 回傳resUpCks.slks代表hash一致的切片編號, 非一致hash的切片則須重傳, 尚未傳切片亦須繼續傳
resUpCkt.slks = resUpCks.slks
}
//packageId
let packageId = fileTotalHash
// console.log('packageId', packageId)
//upload slice
// console.log(`upload slice...`)
for (let i = 0; i < chunkTotal; i++) {
//check
if (resUpCkt.slks.indexOf(i) >= 0) {
// console.log('已有上傳切片檔')
cbProgressSlice() //直接觸發更新進度
continue
}
//start
let start = i * sizeSlice
//end
let end = Math.min(start + sizeSlice, fileTotalSize)
//chunk
let chunk = bb.slice(start, end)
//hd
let hd = { //用header傳key與value時, key不分大小寫, 故使用kebabCase
'chunk-index': i,
'chunk-total': chunkTotal,
'package-id': packageId,
}
//send slice
await send('slice', chunk, {
headers: hd,
dataType: 'blob',
cbProgress: (msg) => {
// console.log('cbProgress', msg)
// let perc = msg.prog
// let dir = msg.m
// if (dir === 'upload' && perc === 100) {
// }
},
retry: retryUpload,
})
// console.log('resSl', resSl)
//cbProgressSlice, 因其內有累加progCount, 實際代表是須成功傳輸後才能計算與回應外部進度, 故不能直接用於send的opt.cbProgress
cbProgressSlice()
}
//send merge-slices-push
// console.log(`send merge-slices-push...`)
let resUpMgp = await send('upload-controller', { mode: 'merge-slices-push', fileHash: fileTotalHash, chunkTotal }, { dataType: 'json', retry: retryUpload })
// console.log('resUpMgp', resUpMgp)
//checkMerging
let checkMerging = () => {
let pm = genPm()
//queueId
let queueId = resUpMgp.queueId
// console.log('queueId', queueId)
let t = setInterval(() => {
//send merge-slices-get
// console.log(`send merge-slices-get...`)
send('upload-controller', { mode: 'merge-slices-get', fileHash: fileTotalHash, filename: fileTotalName, queueId }, { dataType: 'json', retry: retryUpload })
.then((res) => {
// console.log('res', res)
// res => {
// queueId,
// state,
// filename,
// path,
// msg: ...
// }
//check
if (res.state === 'success') {
//clearInterval
clearInterval(t)
//cbProgressMerge
cbProgressMerge({ prog: 100, m: 'download' }) //觸發上傳完畢後之下載回應, 故m須為download
//resolve, state為'success'時提取msg回傳
pm.resolve(res.msg)
}
else if (res.state === 'error') {
console.log('merge-slices-get error', res)
//clearInterval
clearInterval(t)
//reject, state為'error'時會於msg提供錯誤訊息
pm.resolve(res.msg)
}
})
.catch((err) => {
console.log('merge-slices-get catch', err)
//可能發生網路斷訊錯誤, 不clearInterval, 持續輪循測試合併大檔之狀態
})
}, 2000)
return pm
}
//checkMerging
let resMg = await checkMerging()
// console.log('resMg', resMg)
// console.log(`upload slice done`)
return resMg
}
//execute
let execute = async(func, input, cbProgress) => {
//msg
let msg = {
// _mode: mode,
// clientId,
func,
input,
}
//sendPkg
let state = ''
let res = null
await sendPkg('basic', msg, cbProgress)
.then((msg) => {
// console.log('msg', msg)
//check, 若為字串為錯誤訊息
if (isestr(msg)) {
state = 'error'
res = msg
return
}
//check, 若為非物件為非預期錯誤
if (!iseobj(msg)) {
console.log('msg is not an effective object', msg)
state = 'error'
res = 'msg is not an effective object'
return
}
//check, 若不存在output為非預期錯誤, msg格式為{func,input,output}但input會刪除
if (!haskey(msg, 'output')) {
console.log('invalid msg.output', msg)
state = 'error'
res = 'invalid msg.output'
return
}
state = 'success'
res = msg.output
})
.catch((msg) => {
state = 'error'
res = msg
})
//check
if (state === '') {
// console.log('invalid state', r)
eeEmit('error', `invalid state`)
return Promise.reject('invalid state')
}
//check
if (state === 'error') {
// console.log('send data error', r)
// eeEmit('error', res) //一般錯誤會嘗試n次, 每次也都會emit, 故此處不再基於已知state='error'時再emit
return Promise.reject(res)
}
return res
}
//upload
let upload = (filename, input, cbProgress) => {
//bb
let bb = input
return sendDataSlice(filename, bb, cbProgress)
}
//downloadNodejs
let downloadNodejs = async(fileId, cbProgress, opt = {}) => {
//send download
let msg = { fileId }
let resMg = await send('download', msg, { ...opt, dataType: 'json', retry: retryDownload, cbProgress })
// console.log('resMg', resMg)
return resMg
}
//downloadBrowser
let downloadBrowser = async(fileId, cbProgress, opt = {}) => {
//交由瀏覽器下載與管理故無法監聽進度, 不使用cbProgress
//downloadByManager
let downloadByManager = get(opt, 'downloadByManager')
if (!isbol(downloadByManager)) {
downloadByManager = true
}
// console.log('downloadByManager', downloadByManager)
if (downloadByManager) {
//由瀏覽器的下載管理器下載, 使用get+stream
//token
let token = getToken()
if (ispm(token)) {
token = await token
}
// console.log('token', token)
//send download-get-filename
let msg = { fileId }
let resMg = await send('download-get-filename', msg, { dataType: 'json', retry: retryDownload })
// console.log('resMg', resMg)
//filename
let filename = get(resMg, 'filename', '')
// console.log('filename', filename)
//urlUse
let urlUse = getUrlUse('download-get')
// console.log('urlUse', urlUse)
//url
let url = `${urlUse}?fileId=${fileId}&token=${token}`
// console.log('url', url)
//透過a元素打url下載, 讓瀏覽器認定為直接下載模式, 由瀏覽器展示下載進度與排入正在下載清單
let a = document.createElement('a')
a.href = url
a.download = filename
a.click()
return filename
}
else {
//通過axios下載得到blob, 回傳檔案名稱與blob供後續處理
//send download
let msg = { fileId }
let resMg = await send('download', msg, { ...opt, dataType: 'json', retry: retryDownload, cbProgress })
// console.log('resMg', resMg)
return resMg
}
}
//download
let download = async(fileId, cbProgress, opt = {}) => {
if (env === 'browser') {
return downloadBrowser(fileId, cbProgress, opt)
}
else {
return downloadNodejs(fileId, cbProgress, opt)
}
}
//save
ee.execute = execute
ee.upload = upload
ee.download = download
return ee
}
export default WConverhpClient