w-converhp 2.0.47 → 2.0.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * w-converhp-server v2.0.47
2
+ * w-converhp-server v2.0.49
3
3
  * (c) 2018-2021 yuda-lyu(semisphere)
4
4
  * Released under the MIT License.
5
5
  */
@@ -80,7 +80,7 @@
80
80
 
81
81
  <dt class="tag-source">Source:</dt>
82
82
  <dd class="tag-source"><ul class="dummy"><li>
83
- <a href="WConverhpClient.mjs.html">WConverhpClient.mjs</a>, <a href="WConverhpClient.mjs.html#line93">line 93</a>
83
+ <a href="WConverhpClient.mjs.html">WConverhpClient.mjs</a>, <a href="WConverhpClient.mjs.html#line96">line 96</a>
84
84
  </li></ul></dd>
85
85
 
86
86
 
@@ -402,6 +402,46 @@
402
402
 
403
403
 
404
404
 
405
+ <tr>
406
+
407
+ <td class="name"><code>timeout</code></td>
408
+
409
+
410
+ <td class="type">
411
+
412
+
413
+ <span class="param-type">Integer</span>
414
+
415
+
416
+
417
+
418
+ </td>
419
+
420
+
421
+ <td class="attributes">
422
+
423
+ &lt;optional><br>
424
+
425
+
426
+
427
+
428
+
429
+ </td>
430
+
431
+
432
+
433
+ <td class="default">
434
+
435
+ <code>5*60*1000</code>
436
+
437
+ </td>
438
+
439
+
440
+ <td class="description last"><p>輸入最長等待時間整數,單位ms,預設為5<em>60</em>1000、為5分鐘</p></td>
441
+ </tr>
442
+
443
+
444
+
405
445
  <tr>
406
446
 
407
447
  <td class="name"><code>retry</code></td>
@@ -432,12 +472,12 @@
432
472
 
433
473
  <td class="default">
434
474
 
435
- <code>3</code>
475
+ <code>10</code>
436
476
 
437
477
  </td>
438
478
 
439
479
 
440
- <td class="description last"><p>輸入傳輸失敗重試次數整數,預設為3</p></td>
480
+ <td class="description last"><p>輸入傳輸失敗重試次數整數,預設為10</p></td>
441
481
  </tr>
442
482
 
443
483
 
@@ -529,7 +569,7 @@
529
569
  <br class="clear">
530
570
 
531
571
  <footer>
532
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.2</a> on Fri May 16 2025 14:05:37 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
572
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.2</a> on Sat May 17 2025 21:33:07 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
533
573
  </footer>
534
574
 
535
575
  <script>prettyPrint();</script>
@@ -59,12 +59,14 @@ import isobj from 'wsemi/src/isobj.mjs'
59
59
  import iseobj from 'wsemi/src/iseobj.mjs'
60
60
  import ispm from 'wsemi/src/ispm.mjs'
61
61
  import cint from 'wsemi/src/cint.mjs'
62
+ import dig from 'wsemi/src/dig.mjs'
62
63
  import strright from 'wsemi/src/strright.mjs'
63
64
  import b642str from 'wsemi/src/b642str.mjs'
64
65
  import blob2u8arr from 'wsemi/src/blob2u8arr.mjs'
65
66
  import obj2u8arr from 'wsemi/src/obj2u8arr.mjs'
66
67
  import u8arr2obj from 'wsemi/src/u8arr2obj.mjs'
67
68
  import pmConvertResolve from 'wsemi/src/pmConvertResolve.mjs'
69
+ import delay from 'wsemi/src/delay.mjs'
68
70
  import getFileXxHash from 'wsemi/src/getFileXxHash.mjs'
69
71
 
70
72
 
@@ -78,7 +80,8 @@ import getFileXxHash from 'wsemi/src/getFileXxHash.mjs'
78
80
  * @param {Function} [opt.getToken=()=>''] 輸入取得使用者token的回調函數,預設()=>''
79
81
  * @param {String} [opt.tokenType='Bearer'] 輸入token類型字串,預設'Bearer'
80
82
  * @param {Integer} [opt.sizeSlice=1024*1024] 輸入切片上傳檔案之切片檔案大小整數,單位為Byte,預設為1024*1024
81
- * @param {Integer} [opt.retry=3] 輸入傳輸失敗重試次數整數,預設為3
83
+ * @param {Integer} [opt.timeout=5*60*1000] 輸入最長等待時間整數,單位ms,預設為5*60*1000、為5分鐘
84
+ * @param {Integer} [opt.retry=10] 輸入傳輸失敗重試次數整數,預設為10
82
85
  * @returns {Object} 回傳事件物件,可使用函數execute、upload
83
86
  * @example
84
87
  *
@@ -180,10 +183,16 @@ function WConverhpClient(opt) {
180
183
  sizeSlice = 1024 * 1024 //1m
181
184
  }
182
185
 
186
+ //timeout
187
+ let timeout = get(opt, 'timeout')
188
+ if (!isp0int(timeout)) {
189
+ timeout = 5 * 60 * 1000 //5min
190
+ }
191
+
183
192
  //retry
184
193
  let retry = get(opt, 'retry')
185
194
  if (!isp0int(retry)) {
186
- retry = 3
195
+ retry = 10
187
196
  }
188
197
 
189
198
  //env
@@ -206,22 +215,22 @@ function WConverhpClient(opt) {
206
215
  //urlUse
207
216
  let urlUse = ''
208
217
  if (type === 'basic') {
209
- urlUse = `${url}/main` //bbb
218
+ urlUse = `${url}/main`
210
219
  }
211
220
  else if (type === 'upload-controller') {
212
- urlUse = `${url}/ulctr` //bbb
221
+ urlUse = `${url}/ulctr`
213
222
  }
214
223
  else if (type === 'slice') {
215
- urlUse = `${url}/slc` //bbb
224
+ urlUse = `${url}/slc`
216
225
  }
217
226
  else if (type === 'download-get-filename') {
218
- urlUse = `${url}/dwgfn` //bbb
227
+ urlUse = `${url}/dwgfn`
219
228
  }
220
229
  else if (type === 'download-get') {
221
- urlUse = `${url}/dwgf` //bbb
230
+ urlUse = `${url}/dwgf`
222
231
  }
223
232
  else if (type === 'download') {
224
- urlUse = `${url}/dw` //bbb
233
+ urlUse = `${url}/dw`
225
234
  }
226
235
  else {
227
236
  throw new Error(`invalid type[${type}]`)
@@ -231,7 +240,7 @@ function WConverhpClient(opt) {
231
240
  }
232
241
 
233
242
  //res2u8arr
234
- async function res2u8arr(bb) {
243
+ let res2u8arr = async(bb) => {
235
244
  //blob(in browser) or buffer(in nodejs) to u8a
236
245
  let u8a
237
246
  if (env === 'browser') {
@@ -244,7 +253,7 @@ function WConverhpClient(opt) {
244
253
  }
245
254
 
246
255
  //u8arr2bb
247
- function u8arr2bb(u8a) {
256
+ let u8arr2bb = (u8a) => {
248
257
  //u8a to blob(in browser) or buffer(in nodejs)
249
258
  let bb
250
259
  if (env === 'browser') {
@@ -257,7 +266,7 @@ function WConverhpClient(opt) {
257
266
  }
258
267
 
259
268
  //send
260
- async function send(type, pkg, opt = {}) {
269
+ let send = async(type, pkg, opt = {}) => {
261
270
 
262
271
  //headers
263
272
  let headers = get(opt, 'headers')
@@ -364,54 +373,6 @@ function WConverhpClient(opt) {
364
373
  token = await token
365
374
  }
366
375
 
367
- //s
368
- let s = {
369
- method: 'POST',
370
- url: urlUse,
371
- data: dd,
372
- headers: {
373
- Authorization: `${tokenType} ${token}`,
374
- ...ct,
375
- ...headers,
376
- },
377
- timeout: 60 * 60 * 1000, //1hr
378
- maxContentLength: Infinity, //1024 * 1024 * 1024, Infinity //axios於nodejs中會限制內容大小故需改為無限
379
- maxBodyLength: Infinity, //1024 * 1024 * 1024, Infinity //axios於nodejs中會限制內容大小故需改為無限
380
- responseType: rt,
381
- onUploadProgress: function(ev) {
382
- //console.log('onUploadProgress', ev)
383
-
384
- //r
385
- let r = 0
386
- let loaded = ev.loaded
387
- let total = ev.total
388
- if (ispint(total)) {
389
- r = (loaded * 100) / total
390
- }
391
-
392
- //cbProgress
393
- cbProgress({ prog: Math.floor(r), p: loaded, m: 'upload' })
394
-
395
- },
396
- onDownloadProgress: function (ev) {
397
- // console.log('onDownloadProgress', ev)
398
-
399
- //r
400
- let r = 0
401
- let loaded = ev.loaded
402
- // let total = ev.srcElement.getResponseHeader('Content-length') //若需要得知下載進度, 需於伺服器回傳時提供Content-length
403
- let total = ev.total
404
- if (ispint(total)) {
405
- r = (loaded * 100) / total
406
- }
407
-
408
- //cbProgress
409
- cbProgress({ prog: Math.floor(r), p: loaded, m: 'download' })
410
-
411
- },
412
- }
413
- // console.log('s', s)
414
-
415
376
  //getFilenameByHeader
416
377
  let getFilenameByHeader = (contentDisposition) => {
417
378
  let fn = 'unknow'
@@ -425,7 +386,7 @@ function WConverhpClient(opt) {
425
386
  }
426
387
 
427
388
  //downloadStream
428
- let downloadStream = async (res) => {
389
+ let downloadStream = async(res) => {
429
390
  // console.log('res.headers', res.headers)
430
391
 
431
392
  //pm
@@ -523,61 +484,151 @@ function WConverhpClient(opt) {
523
484
  return pm
524
485
  }
525
486
 
526
- //axios
527
- axios(s)
528
- .then(async (res) => {
529
- // console.log('axios then', res)
530
-
531
- //check download
532
- if (type === 'download') {
533
- await downloadStream(res)
534
- .then((_res) => {
535
- pm.resolve(_res)
536
- })
537
- .catch((_err) => {
538
- pm.reject(_err)
539
- })
540
- return
487
+ //s
488
+ let s = {
489
+ method: 'POST',
490
+ url: urlUse,
491
+ data: dd,
492
+ headers: {
493
+ Authorization: `${tokenType} ${token}`,
494
+ ...ct,
495
+ ...headers,
496
+ },
497
+ timeout,
498
+ maxContentLength: Infinity, //1024 * 1024 * 1024, Infinity //axios於nodejs中會限制內容大小故需改為無限
499
+ maxBodyLength: Infinity, //1024 * 1024 * 1024, Infinity //axios於nodejs中會限制內容大小故需改為無限
500
+ responseType: rt,
501
+ onUploadProgress: function(ev) {
502
+ //console.log('onUploadProgress', ev)
503
+
504
+ //r
505
+ let r = 0
506
+ let loaded = ev.loaded
507
+ let total = ev.total
508
+ if (ispint(total)) {
509
+ r = (loaded * 100) / total
541
510
  }
542
511
 
543
- //bb
544
- let bb = get(res, 'data')
545
- // console.log('bb', bb)
512
+ //cbProgress
513
+ cbProgress({ prog: Math.floor(r), p: loaded, m: 'upload' })
546
514
 
547
- //res2u8arr
548
- let u8a = await res2u8arr(bb)
549
- // console.log('u8a', u8a)
515
+ },
516
+ onDownloadProgress: function (ev) {
517
+ // console.log('onDownloadProgress', ev)
550
518
 
551
- //u8arr2obj
552
- let data = u8arr2obj(u8a)
553
- // console.log('data', data)
519
+ //r
520
+ let r = 0
521
+ let loaded = ev.loaded
522
+ // let total = ev.srcElement.getResponseHeader('Content-length') //若需要得知下載進度, 需於伺服器回傳時提供Content-length
523
+ let total = ev.total
524
+ if (ispint(total)) {
525
+ r = (loaded * 100) / total
526
+ }
527
+
528
+ //cbProgress
529
+ cbProgress({ prog: Math.floor(r), p: loaded, m: 'download' })
530
+
531
+ },
532
+ }
533
+ // console.log('s', s)
534
+
535
+ //callApiCore, 處理axios成功then時訊息, catch時直接向外傳遞
536
+ let callApiCore = async() => {
537
+
538
+ //axios, catch時直接向外傳遞
539
+ let res = await axios(s)
540
+
541
+ //check, download時直接轉由downloadStream處理res, catch時直接向外傳遞
542
+ if (type === 'download') {
543
+ return await downloadStream(res)
544
+ }
545
+
546
+ //bb
547
+ let bb = get(res, 'data')
548
+ // console.log('bb', bb)
549
+
550
+ //res2u8arr, catch時直接向外傳遞
551
+ let u8a = await res2u8arr(bb)
552
+ // console.log('u8a', u8a)
553
+
554
+ //u8arr2obj
555
+ let data = u8arr2obj(u8a)
556
+ // console.log('data', data)
557
+
558
+ //check
559
+ if (!iseobj(data)) {
560
+ eeEmit('error', `data is not an effective object`)
561
+ return Promise.reject(`data is not an effective object`)
562
+ }
563
+
564
+ //分離伺服器資料的success或error
565
+ if (haskey(data, 'success')) {
566
+ return Promise.resolve(data.success)
567
+ }
568
+ else if (haskey(data, 'error')) {
569
+ eeEmit('error', data.error)
570
+ return Promise.reject(data.error)
571
+ }
572
+ else {
573
+ eeEmit('error', `data does not contain success or error`)
574
+ return Promise.reject(`data does not contain success or error`)
575
+ }
576
+
577
+ }
578
+
579
+ //callApi, 處理callApiCore失敗catch時retry
580
+ let callApi = async() => {
581
+
582
+ //pmConvertResolve
583
+ let fun = pmConvertResolve(callApiCore)
584
+
585
+ //fun
586
+ let r = await fun(s)
587
+ // console.log('r', r)
588
+
589
+ //while
590
+ let maxRetry = 10
591
+ let baseDelay = 1000 //初始延遲為1秒
592
+ let ratio = Math.pow(180000 / baseDelay, 1 / (maxRetry - 1)) //1.888
593
+ let n = 0
594
+ while (r.state === 'error') {
595
+
596
+ //add
597
+ n += 1
554
598
 
555
599
  //check
556
- if (!iseobj(data)) {
557
- // console.log('data is not an effective object', data)
558
- eeEmit('error', `data is not an effective object`)
559
- pm.reject(`data is not an effective object`)
560
- return
600
+ if (n > retry) {
601
+ break
561
602
  }
562
603
 
563
- //分離伺服器資料的success或error
564
- if (haskey(data, 'success')) {
565
- pm.resolve(data.success)
566
- }
567
- else if (haskey(data, 'error')) {
568
- eeEmit('error', data.error)
569
- pm.reject(data.error)
570
- }
571
- else {
572
- // console.log('invalid data', data)
573
- eeEmit('error', `invalid data`)
574
- pm.reject(`invalid data`)
575
- }
604
+ //delay
605
+ let t = Math.round(baseDelay * Math.pow(ratio, n - 1))
606
+ console.log(`wait ${dig(t / 1000, 1)}(second) to retry...`)
607
+ await delay(t)
576
608
 
609
+ //retry
610
+ console.log(`retry n=${n}...`)
611
+ r = await fun(s)
612
+ // console.log(`retry n=${n} done`)
613
+
614
+ }
615
+
616
+ if (r.state === 'success') {
617
+ return r.msg
618
+ }
619
+ else {
620
+ return Promise.reject(r.msg)
621
+ }
622
+ }
623
+
624
+ //callApi
625
+ await callApi()
626
+ .then((res) => {
627
+ pm.resolve(res)
577
628
  })
578
- .catch(async (res) => {
629
+ .catch(async(res) => {
579
630
  // console.log('axios catch', res.toJSON())
580
- //Network Error除可能是網路斷線之外, 可能被瀏覽器外掛封鎖阻擋, 亦可能因硬碟空間不足(&lt;4g)無法下載被瀏覽器拒絕
631
+ //Network Error除可能是網路斷線之外, 可能被瀏覽器外掛封鎖阻擋, 亦可能因硬碟空間不足無法下載被瀏覽器拒絕
581
632
 
582
633
  //data
583
634
  let data = null
@@ -619,7 +670,7 @@ function WConverhpClient(opt) {
619
670
  }
620
671
 
621
672
  //sendPkg
622
- async function sendPkg(type, data, cbProgress) {
673
+ let sendPkg = async(type, data, cbProgress) => {
623
674
 
624
675
  //bb
625
676
  let bb = null
@@ -644,35 +695,8 @@ function WConverhpClient(opt) {
644
695
  return res
645
696
  }
646
697
 
647
- //sendData
648
- async function sendData(type, data, cbProgress) {
649
-
650
- //fun
651
- let fun = pmConvertResolve(sendPkg)
652
-
653
- //sendPkg
654
- let r = await fun(type, data, cbProgress)
655
-
656
- let n = 0
657
- while (r.state === 'error') {
658
- n += 1
659
- if (n > retry) {
660
- break
661
- }
662
- console.log(`retry n=${n}`)
663
- r = await fun(type, data, cbProgress)
664
- }
665
-
666
- if (r.state === 'success') {
667
- return r.msg
668
- }
669
- else {
670
- return Promise.reject(r.msg)
671
- }
672
- }
673
-
674
698
  //calcHash
675
- async function calcHash(inp) {
699
+ let calcHash = async(inp) => {
676
700
 
677
701
  //bb
678
702
  let bb = null
@@ -691,7 +715,7 @@ function WConverhpClient(opt) {
691
715
  }
692
716
 
693
717
  //sendDataSlice
694
- async function sendDataSlice(fileTotalName, bb, cbProgress) {
718
+ let sendDataSlice = async(fileTotalName, bb, cbProgress) => {
695
719
 
696
720
  //n
697
721
  let n = 0
@@ -863,65 +887,29 @@ function WConverhpClient(opt) {
863
887
  //chunk
864
888
  let chunk = bb.slice(start, end)
865
889
 
866
- //sendDataSlice
867
- let sendDataSlice = async() => {
868
-
869
- //hd
870
- let hd = { //用header傳key與value時, key不分大小寫, 故使用kebabCase
871
- 'chunk-index': i,
872
- 'chunk-total': chunkTotal,
873
- 'package-id': packageId,
874
- }
875
-
876
- //send slice
877
- let resSl = await send('slice', chunk, {
878
- headers: hd,
879
- dataType: 'blob',
880
- cbProgress: (msg) => {
881
- // console.log('cbProgress', msg)
882
- // let perc = msg.prog
883
- // let dir = msg.m
884
- // if (dir === 'upload' &amp;&amp; perc === 100) {
885
- // }
886
- }
887
- })
888
- // console.log('resSl', resSl)
889
-
890
- //cbProgressSlice, 因其內有累加progCount, 實際代表是須成功傳輸後才能計算與回應外部進度, 故不能直接用於send的opt.cbProgress
891
- cbProgressSlice()
892
-
893
- return resSl
890
+ //hd
891
+ let hd = { //用header傳key與value時, key不分大小寫, 故使用kebabCase
892
+ 'chunk-index': i,
893
+ 'chunk-total': chunkTotal,
894
+ 'package-id': packageId,
894
895
  }
895
896
 
896
- //sendDataSliceRetry
897
- let sendDataSliceRetry = async() => {
898
-
899
- //fun
900
- let fun = pmConvertResolve(sendDataSlice)
901
-
902
- //sendPkg
903
- let r = await fun()
904
-
905
- let n = 0
906
- while (r.state === 'error') {
907
- n += 1
908
- if (n > retry) {
909
- break
910
- }
911
- console.log(`retry n=${n}`)
912
- r = await fun()
913
- }
914
-
915
- if (r.state === 'success') {
916
- return r.msg
897
+ //send slice
898
+ await send('slice', chunk, {
899
+ headers: hd,
900
+ dataType: 'blob',
901
+ cbProgress: (msg) => {
902
+ // console.log('cbProgress', msg)
903
+ // let perc = msg.prog
904
+ // let dir = msg.m
905
+ // if (dir === 'upload' &amp;&amp; perc === 100) {
906
+ // }
917
907
  }
918
- else {
919
- return Promise.reject(r.msg)
920
- }
921
- }
908
+ })
909
+ // console.log('resSl', resSl)
922
910
 
923
- //sendDataSliceRetry
924
- await sendDataSliceRetry()
911
+ //cbProgressSlice, 因其內有累加progCount, 實際代表是須成功傳輸後才能計算與回應外部進度, 故不能直接用於send的opt.cbProgress
912
+ cbProgressSlice()
925
913
 
926
914
  }
927
915
 
@@ -997,7 +985,7 @@ function WConverhpClient(opt) {
997
985
  }
998
986
 
999
987
  //execute
1000
- async function execute(func, input, cbProgress) {
988
+ let execute = async(func, input, cbProgress) => {
1001
989
 
1002
990
  //msg
1003
991
  let msg = {
@@ -1007,10 +995,10 @@ function WConverhpClient(opt) {
1007
995
  input,
1008
996
  }
1009
997
 
1010
- //sendData
998
+ //sendPkg
1011
999
  let state = ''
1012
1000
  let res = null
1013
- await sendData('basic', msg, cbProgress)
1001
+ await sendPkg('basic', msg, cbProgress)
1014
1002
  .then((msg) => {
1015
1003
  // console.log('msg', msg)
1016
1004
 
@@ -1063,7 +1051,7 @@ function WConverhpClient(opt) {
1063
1051
  }
1064
1052
 
1065
1053
  //upload
1066
- async function upload(filename, input, cbProgress) {
1054
+ let upload = (filename, input, cbProgress) => {
1067
1055
 
1068
1056
  //bb
1069
1057
  let bb = input
@@ -1072,7 +1060,7 @@ function WConverhpClient(opt) {
1072
1060
  }
1073
1061
 
1074
1062
  //downloadNodejs
1075
- async function downloadNodejs(fileId, cbProgress, opt = {}) {
1063
+ let downloadNodejs = async(fileId, cbProgress, opt = {}) => {
1076
1064
 
1077
1065
  //send download
1078
1066
  let msg = { fileId }
@@ -1083,7 +1071,7 @@ function WConverhpClient(opt) {
1083
1071
  }
1084
1072
 
1085
1073
  //downloadBrowser
1086
- async function downloadBrowser(fileId, cbProgress, opt = {}) {
1074
+ let downloadBrowser = async(fileId, cbProgress, opt = {}) => {
1087
1075
  //交由瀏覽器下載與管理故無法監聽進度, 不使用cbProgress
1088
1076
 
1089
1077
  //token
@@ -1120,7 +1108,7 @@ function WConverhpClient(opt) {
1120
1108
  }
1121
1109
 
1122
1110
  //download
1123
- async function download(fileId, cbProgress, opt = {}) {
1111
+ let download = async(fileId, cbProgress, opt = {}) => {
1124
1112
  if (env === 'browser') {
1125
1113
  return downloadBrowser(fileId, cbProgress, opt)
1126
1114
  }
@@ -1153,7 +1141,7 @@ export default WConverhpClient
1153
1141
  <br class="clear">
1154
1142
 
1155
1143
  <footer>
1156
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.2</a> on Fri May 16 2025 14:05:37 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
1144
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.2</a> on Sat May 17 2025 21:33:07 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
1157
1145
  </footer>
1158
1146
 
1159
1147
  <script>prettyPrint();</script>
@@ -749,7 +749,7 @@
749
749
  <br class="clear">
750
750
 
751
751
  <footer>
752
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.2</a> on Fri May 16 2025 14:05:37 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
752
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.2</a> on Sat May 17 2025 21:33:07 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
753
753
  </footer>
754
754
 
755
755
  <script>prettyPrint();</script>