w-email 1.1.3 → 1.1.4

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.
@@ -45,161 +45,394 @@
45
45
 
46
46
  <section>
47
47
  <article>
48
- <pre class="prettyprint source linenums"><code>import nodemailer from 'nodemailer'
49
- import get from 'lodash-es/get.js'
50
- import genPm from 'wsemi/src/genPm.mjs'
51
- import isstr from 'wsemi/src/isstr.mjs'
52
- import isobj from 'wsemi/src/isobj.mjs'
53
- import isEmail from 'wsemi/src/isEmail.mjs'
54
-
55
-
56
- /**
57
- * 寄送email
58
- *
59
- * @param {*} [opt={}] 輸入設定物件,預設{}
60
- * @param {String} [opt.srcName='system by w-email'] 輸入寄件人顯示名稱字串,預設'system by w-email'
61
- * @param {String} opt.srcEmail 輸入寄件人email信箱字串
62
- * @param {String} opt.srcPW 輸入寄件人email密碼字串
63
- * @param {String} [opt.srcHost='smtp.gmail.com'] 輸入郵件伺服器位址,預設'smtp.gmail.com'
64
- * @param {String} [opt.srcPort=587] 輸入郵件伺服器連接埠,預設587
65
- * @param {String} [opt.emTitle=''] 輸入郵件名稱字串,預設''
66
- * @param {String} [opt.emContent=''] 輸入郵件訊息字串,預設''
67
- * @param {String|Array} [opt.toEmails=[]] 輸入收件人email字串或陣列,預設[]
68
- * @param {String|Array} [opt.toEmailsCC=[]] 輸入副本收件人email字串或陣列,預設[]
69
- * @param {String|Array} [opt.toEmailsBCC=[]] 輸入密件副本收件人email字串或陣列,預設[]
70
- * @param {Object|Array} [opt.emAttachments=[]] 輸入附件物件或陣列,預設[],物件內需包含filename與path
71
- * @returns {Promise} 回傳Promise,resolve回傳成功訊息,reject回傳錯誤訊息
72
- * @example
73
- * import WEmail from 'w-email'
74
- *
75
- * //opt
76
- * let opt = {
77
- * srcName: 'test name',
78
- * srcEmail: 'your email',
79
- * srcPW: 'your password',
80
- * emTitle: 'test title',
81
- * emContent: '&lt;h4>test head&lt;/h4>'+'&lt;div>test content&lt;/div>',
82
- * toEmails: 'to email',
83
- * //toEmailsCC: 'to email',
84
- * //toEmailsBCC: 'to email',
85
- * emAttachments: {
86
- * filename: 'your filename',
87
- * path: 'your filepath'
88
- * }
89
- * }
90
- *
91
- * //WEmail
92
- * new WEmail(opt)
93
- * .then((msg)=>{
94
- * console.log('then',msg)
95
- * })
96
- * .catch((err)=>{
97
- * console.log('catch',err)
98
- * })
99
- */
100
- function WEmail(opt = {}) {
101
- let pm = genPm()
102
-
103
- //寄件人顯示名稱
104
- let srcName = get(opt, 'srcName', 'system by w-email')
105
-
106
- //寄件人email信箱
107
- let srcEmail = get(opt, 'srcEmail', '')
108
- if (!isEmail(srcEmail)) {
109
- pm.reject('srcEmail is not email')
110
- return pm
111
- }
112
-
113
- //寄件人email密碼
114
- let srcPW = get(opt, 'srcPW', '')
115
-
116
- //郵件伺服器位址
117
- let srcHost = get(opt, 'srcHost', 'smtp.gmail.com')
118
- //gmail外寄郵件 (SMTP) 伺服器
119
-
120
- //郵件伺服器連接埠
121
- let srcPort = get(opt, 'srcPort', 587)
122
- //安全資料傳輸層 (SSL) 通訊埠:465
123
- //傳輸層安全性 (TLS)/STARTTLS 通訊埠:587
124
-
125
- //郵件名稱
126
- let emTitle = get(opt, 'emTitle', '')
127
-
128
- //郵件訊息
129
- let emContent = get(opt, 'emContent', '')
130
-
131
- //附件
132
- let emAttachments = get(opt, 'emAttachments', [])
133
- if (isobj(emAttachments)) {
134
- emAttachments = [emAttachments]
135
- }
136
-
137
- //收件人email
138
- let toEmails = get(opt, 'toEmails', [])
139
- if (isstr(toEmails)) {
140
- toEmails = [toEmails]
141
- }
142
-
143
- //副本收件人email
144
- let toEmailsCC = get(opt, 'toEmailsCC', [])
145
- if (isstr(toEmailsCC)) {
146
- toEmailsCC = [toEmailsCC]
147
- }
148
-
149
- //密件副本收件人email
150
- let toEmailsBCC = get(opt, 'toEmailsBCC', [])
151
- if (isstr(toEmailsBCC)) {
152
- toEmailsBCC = [toEmailsBCC]
153
- }
154
-
155
- //transporter
156
- let transporter = nodemailer.createTransport({
157
- host: srcHost,
158
- port: srcPort,
159
- auth: {
160
- user: srcEmail,
161
- pass: srcPW
162
- },
163
-
164
- //secureConnecton: true,
165
- secure: srcPort === 465, // true for 465, false for other ports
166
-
167
- // ssl: false,
168
- // ignoreTLS: false,
169
- // requireTLS: true,
170
- // tls: {
171
- // secureProtocol: 'TLSv1_2_method',
172
- // rejectUnauthorized: false
173
- // }
174
-
175
- })
176
-
177
- //optt
178
- let optt = {
179
- from: `${srcName} &lt;${srcEmail}>`,
180
- to: toEmails.join(','),
181
- cc: toEmailsCC.join(','),
182
- bcc: toEmailsBCC.join(','),
183
- subject: emTitle,
184
- html: emContent,
185
- attachments: emAttachments
186
- }
187
-
188
- //sendMail
189
- transporter.sendMail(optt, function(err, info) {
190
- if (err) {
191
- pm.reject(err)
192
- }
193
- else {
194
- pm.resolve(info.response)
195
- }
196
- })
197
-
198
- return pm
199
- }
200
-
201
-
202
- export default WEmail
48
+ <pre class="prettyprint source linenums"><code>import nodemailer from 'nodemailer'
49
+ import get from 'lodash-es/get.js'
50
+ import genPm from 'wsemi/src/genPm.mjs'
51
+ import isstr from 'wsemi/src/isstr.mjs'
52
+ import isarr from 'wsemi/src/isarr.mjs'
53
+ import isobj from 'wsemi/src/isobj.mjs'
54
+ import ispint from 'wsemi/src/ispint.mjs'
55
+ import cint from 'wsemi/src/cint.mjs'
56
+ import isEmail from 'wsemi/src/isEmail.mjs'
57
+
58
+
59
+ //getOpt, 取opt欄位值, undefined與null皆視為未給定而回傳預設值
60
+ function getOpt(opt, key, def) {
61
+ let v = get(opt, key)
62
+ if (v === undefined || v === null) {
63
+ return def
64
+ }
65
+ return v
66
+ }
67
+
68
+
69
+ //toText, 文字欄位: 字串原樣回傳, 有限數字轉為字串, 其餘回傳null代表不合規
70
+ function toText(v) {
71
+ if (isstr(v)) {
72
+ return v
73
+ }
74
+ if (typeof v === 'number' &amp;&amp; Number.isFinite(v)) {
75
+ return String(v)
76
+ }
77
+ return null
78
+ }
79
+
80
+
81
+ //isSkip, 陣列元素為falsy或空白字串者視為未給定而略過
82
+ function isSkip(v) {
83
+ return !v || (isstr(v) &amp;&amp; v.trim() === '')
84
+ }
85
+
86
+
87
+ //hasNonAscii, 字串是否含非ASCII字元
88
+ function hasNonAscii(s) {
89
+ return [...s].some((c) => {
90
+ return c.charCodeAt(0) > 0x7f
91
+ })
92
+ }
93
+
94
+
95
+ //hasUrlUnsafe, 字串是否含URL剖析器會截斷或改寫網域之字元(控制字元、空白、DEL與/\?#%:@[])
96
+ function hasUrlUnsafe(s) {
97
+ return [...s].some((c) => {
98
+ let code = c.charCodeAt(0)
99
+ return code &lt;= 0x20 || code === 0x7f || '/\\?#%:@[]'.includes(c)
100
+ })
101
+ }
102
+
103
+
104
+ //normEmail, email正規化: 去除前後空白, 網域含非ASCII字元時先轉為punycode, 再以isEmail檢核, 回傳正規化後之email, 不合規回傳null
105
+ function normEmail(v) {
106
+ if (!isstr(v)) {
107
+ return null
108
+ }
109
+ let email = v.trim()
110
+ let i = email.lastIndexOf('@')
111
+ if (i &lt; 0) {
112
+ return null
113
+ }
114
+ let local = email.slice(0, i)
115
+ let domain = email.slice(i + 1)
116
+ if (hasNonAscii(domain)) {
117
+ //國際化網域, 先排除URL剖析器會截斷或改寫之字元, 使檢核值與寄送值一致
118
+ if (hasUrlUnsafe(domain)) {
119
+ return null
120
+ }
121
+ try {
122
+ domain = new URL(`http://${domain}`).hostname
123
+ }
124
+ catch (err) {
125
+ return null
126
+ }
127
+ }
128
+ email = `${local}@${domain}`
129
+ if (!isEmail(email)) {
130
+ return null
131
+ }
132
+ return email
133
+ }
134
+
135
+
136
+ //normEmails, 收件人欄位正規化: 可為email字串或其陣列, falsy視為無收件人, 陣列元素為falsy或空白字串者略過
137
+ function normEmails(opt, key) {
138
+ let v = get(opt, key)
139
+ if (!v) {
140
+ return { emails: [] }
141
+ }
142
+ let isArr = isarr(v)
143
+ if (isstr(v)) {
144
+ v = [v]
145
+ }
146
+ if (!isarr(v)) {
147
+ return { err: `${key} is not string or array` }
148
+ }
149
+ let emails = []
150
+ for (let i = 0; i &lt; v.length; i++) {
151
+ if (isSkip(v[i])) {
152
+ continue
153
+ }
154
+ let email = normEmail(v[i])
155
+ if (email === null) {
156
+ return { err: isArr ? `${key}[${i}] is not email` : `${key} is not email` }
157
+ }
158
+ emails.push(email)
159
+ }
160
+ return { emails }
161
+ }
162
+
163
+
164
+ //isAttachment, 附件物件須具nodemailer之內容來源raw、path、href或content其一, 且filename若給定須為字串或false
165
+ function isAttachment(a) {
166
+ if (!isobj(a)) {
167
+ return false
168
+ }
169
+ let fn = a.filename
170
+ if (fn !== undefined &amp;&amp; fn !== null &amp;&amp; fn !== false &amp;&amp; !isstr(fn)) {
171
+ return false
172
+ }
173
+ if (a.raw) {
174
+ return true
175
+ }
176
+ if (isstr(a.path) &amp;&amp; a.path !== '') {
177
+ return true
178
+ }
179
+ if (isstr(a.href) &amp;&amp; a.href !== '') {
180
+ return true
181
+ }
182
+ let c = a.content
183
+ return isstr(c) || Buffer.isBuffer(c) || (!!c &amp;&amp; typeof c.pipe === 'function')
184
+ }
185
+
186
+
187
+ //normAttachments, 附件正規化: 可為附件物件或其陣列, falsy視為無附件, 陣列元素為falsy或空白字串者略過
188
+ function normAttachments(opt) {
189
+ let v = get(opt, 'emAttachments')
190
+ if (!v) {
191
+ return { atts: [] }
192
+ }
193
+ let isArr = isarr(v)
194
+ if (isobj(v)) {
195
+ v = [v]
196
+ }
197
+ if (!isarr(v)) {
198
+ return { err: 'emAttachments is not object or array' }
199
+ }
200
+ let atts = []
201
+ for (let i = 0; i &lt; v.length; i++) {
202
+ if (isSkip(v[i])) {
203
+ continue
204
+ }
205
+ if (!isAttachment(v[i])) {
206
+ return { err: isArr ? `emAttachments[${i}] is not valid attachment` : 'emAttachments is not valid attachment' }
207
+ }
208
+ atts.push(v[i])
209
+ }
210
+ return { atts }
211
+ }
212
+
213
+
214
+ /**
215
+ * 寄送email
216
+ *
217
+ * opt各欄位給予undefined或null時皆視為未給定而使用預設值;欄位型別或值不合規時,不連線即reject錯誤訊息字串(見下方「錯誤訊息」)。
218
+ *
219
+ * **email檢核規則**(srcEmail,以及toEmails、toEmailsCC、toEmailsBCC之每一筆皆適用):
220
+ * 1. 須為字串,先去除前後空白。
221
+ * 2. 網域(@之後)含非ASCII字元者視為國際化網域,先自動轉為punycode再檢核,並以轉換後之值寄送,例如`user@例子.測試`寄至`user@xn--fsqu00a.xn--g6w251d`;國際化網域內不得含空白、控制字元與`/`、`\`、`?`、`#`、`%`、`:`、`@`、`[`、`]`。
222
+ * 3. 再以wsemi之isEmail檢核,須同時符合:
223
+ * - 全長至多254字元,帳號部分(@之前)至多64字元。
224
+ * - 帳號部分僅限ASCII之英數字與特殊字元 ! # $ % &amp; ' * + / = ? ^ _ { | } ~ - 及反引號,點只能作為分隔,不得位於開頭、結尾或連續出現。
225
+ * - 網域為一段以上之主機名稱(英數字與中間之連字號,每段至多63字元,不得以連字號開頭或結尾)加頂級網域,頂級網域須為2字元以上之英文字母,或xn--開頭之punycode。
226
+ * 4. 因此以下格式一律拒收:帶顯示名稱或角括號(`Alice &lt;alice@example.com>`、`&lt;alice@example.com>`)、帶註解(`bob@example.com (Bob)`)、一個字串內含多筆(以逗號、分號或空白分隔)、引號形式之帳號(`"john..doe"@example.com`)、非ASCII之帳號(`張三@example.com`)、無頂級網域(`user@localhost`)、IP位址(`user@[127.0.0.1]`)、網域含底線。
227
+ *
228
+ * **收件人規則**(toEmails、toEmailsCC、toEmailsBCC):
229
+ * - 可為一個email字串(僅一位收件人)或email字串陣列,多位收件人須以陣列給予。
230
+ * - 給予null、undefined、false、0或空字串時,視為該欄無收件人;陣列內為falsy或空白字串之元素略過。
231
+ * - 三欄合計至少須有一位收件人。密件副本(toEmailsBCC)不出現於信件標頭。
232
+ *
233
+ * **附件規則**(emAttachments):
234
+ * - 可為附件物件或附件物件陣列;給予null、undefined、false、0或空字串時視為無附件,陣列內為falsy或空白字串之元素略過。
235
+ * - 每個附件物件須具下列內容來源之一:`raw`(非空)、`path`(非空字串,本機路徑或網址)、`href`(非空字串網址)、`content`(字串、Buffer或可讀串流);`filename`若給予須為字串,或給予false表示不帶檔名。
236
+ * - path與href會讀取本機檔案或連線下載,不可直接採用不可信任之輸入。
237
+ *
238
+ * **錯誤訊息**(輸入檢核失敗時reject之字串,皆於連線前發生):
239
+ * - `srcEmail is not email`
240
+ * - `srcName is not string`、`srcPW is not string`、`emTitle is not string`、`emContent is not string`
241
+ * - `srcHost is not effective string`
242
+ * - `srcPort is not valid port`
243
+ * - `toEmails is not string or array`、`toEmails is not email`(以字串給予時)、`toEmails[i] is not email`(以陣列給予時,i為元素索引);toEmailsCC與toEmailsBCC同,僅欄位名不同
244
+ * - `no recipients`
245
+ * - `emAttachments is not object or array`、`emAttachments is not valid attachment`(以物件給予時)、`emAttachments[i] is not valid attachment`(以陣列給予時)
246
+ *
247
+ * @param {Object} [opt={}] 輸入設定物件,預設{}
248
+ * @param {String} [opt.srcName='system by w-email'] 輸入寄件人顯示名稱字串,可為數字,換行字元轉為空白並去除前後空白,給予空字串則不顯示名稱,預設'system by w-email'
249
+ * @param {String} opt.srcEmail 輸入寄件人email信箱字串,亦作為登入郵件伺服器之帳號,須符合email檢核規則
250
+ * @param {String} [opt.srcPW=''] 輸入寄件人email密碼字串,可為數字,郵件伺服器未要求登入時可為空字串,預設''
251
+ * @param {String} [opt.srcHost='smtp.gmail.com'] 輸入郵件伺服器位址字串,去除前後空白後不可為空,預設'smtp.gmail.com'
252
+ * @param {Number|String} [opt.srcPort=587] 輸入郵件伺服器連接埠,可為正整數或其字串,範圍1至65535,465使用SSL/TLS連線,其他連接埠於伺服器支援時使用STARTTLS(伺服器未宣告STARTTLS時以明文連線與登入),預設587
253
+ * @param {String} [opt.emTitle=''] 輸入郵件名稱字串,可為數字,預設''
254
+ * @param {String|Buffer} [opt.emContent=''] 輸入郵件訊息字串,以html寄送,可為數字或Buffer,預設''
255
+ * @param {String|Array} [opt.toEmails=[]] 輸入收件人email字串或陣列,須符合email檢核規則與收件人規則,預設[]
256
+ * @param {String|Array} [opt.toEmailsCC=[]] 輸入副本收件人email字串或陣列,規則同toEmails,預設[]
257
+ * @param {String|Array} [opt.toEmailsBCC=[]] 輸入密件副本收件人email字串或陣列,規則同toEmails,預設[]
258
+ * @param {Object|Array} [opt.emAttachments=[]] 輸入附件物件或陣列,須符合附件規則,預設[]
259
+ * @returns {Promise} 回傳Promise,resolve回傳郵件伺服器回應字串,部分收件人被伺服器拒收時仍為resolve且不回傳被拒名單;reject回傳錯誤,輸入檢核失敗時為錯誤訊息字串,其餘為Error物件(通常帶code,伺服器回應之錯誤另帶responseCode);toEmails、toEmailsCC、toEmailsBCC合計至少須有一位收件人;郵件伺服器無回應時依nodemailer預設逾時(連線2分鐘、招呼30秒、閒置10分鐘)後reject
260
+ * @example
261
+ * import WEmail from 'w-email'
262
+ *
263
+ * //opt
264
+ * let opt = {
265
+ * srcName: 'test name',
266
+ * srcEmail: 'your email',
267
+ * srcPW: 'your password',
268
+ * emTitle: 'test title',
269
+ * emContent: '&lt;h4>test head&lt;/h4>'+'&lt;div>test content&lt;/div>',
270
+ * toEmails: 'to email',
271
+ * //toEmailsCC: 'to email',
272
+ * //toEmailsBCC: 'to email',
273
+ * emAttachments: {
274
+ * filename: 'your filename',
275
+ * path: 'your filepath'
276
+ * }
277
+ * }
278
+ *
279
+ * //WEmail
280
+ * new WEmail(opt)
281
+ * .then((msg)=>{
282
+ * console.log('then',msg)
283
+ * })
284
+ * .catch((err)=>{
285
+ * console.log('catch',err)
286
+ * })
287
+ */
288
+ function WEmail(opt = {}) {
289
+ let pm = genPm()
290
+
291
+ //fail, 輸入檢核失敗, 不連線即reject錯誤訊息字串
292
+ let fail = (msg) => {
293
+ pm.reject(msg)
294
+ return pm
295
+ }
296
+
297
+ try {
298
+
299
+ //寄件人email信箱
300
+ let srcEmail = normEmail(get(opt, 'srcEmail'))
301
+ if (srcEmail === null) {
302
+ return fail('srcEmail is not email')
303
+ }
304
+
305
+ //寄件人顯示名稱, 換行字元轉為空白並去除前後空白
306
+ let srcName = toText(getOpt(opt, 'srcName', 'system by w-email'))
307
+ if (srcName === null) {
308
+ return fail('srcName is not string')
309
+ }
310
+ srcName = srcName.replace(/[\r\n]+/g, ' ').trim()
311
+
312
+ //寄件人email密碼, 郵件伺服器未要求登入時可為空字串
313
+ let srcPW = toText(getOpt(opt, 'srcPW', ''))
314
+ if (srcPW === null) {
315
+ return fail('srcPW is not string')
316
+ }
317
+
318
+ //郵件伺服器位址
319
+ let srcHost = getOpt(opt, 'srcHost', 'smtp.gmail.com')
320
+ if (!isstr(srcHost) || srcHost.trim() === '') {
321
+ return fail('srcHost is not effective string')
322
+ }
323
+ srcHost = srcHost.trim()
324
+ //gmail外寄郵件 (SMTP) 伺服器
325
+
326
+ //郵件伺服器連接埠, 可為正整數或其字串
327
+ let srcPort = getOpt(opt, 'srcPort', 587)
328
+ if (!ispint(srcPort) || cint(srcPort) > 65535) {
329
+ return fail('srcPort is not valid port')
330
+ }
331
+ srcPort = cint(srcPort)
332
+ //安全資料傳輸層 (SSL) 通訊埠:465
333
+ //傳輸層安全性 (TLS)/STARTTLS 通訊埠:587
334
+
335
+ //郵件名稱
336
+ let emTitle = toText(getOpt(opt, 'emTitle', ''))
337
+ if (emTitle === null) {
338
+ return fail('emTitle is not string')
339
+ }
340
+
341
+ //郵件訊息, 可為字串、數字或Buffer
342
+ let emContent = getOpt(opt, 'emContent', '')
343
+ if (!Buffer.isBuffer(emContent)) {
344
+ emContent = toText(emContent)
345
+ if (emContent === null) {
346
+ return fail('emContent is not string')
347
+ }
348
+ }
349
+
350
+ //附件
351
+ let ra = normAttachments(opt)
352
+ if (ra.err) {
353
+ return fail(ra.err)
354
+ }
355
+
356
+ //收件人email
357
+ let rt = normEmails(opt, 'toEmails')
358
+ if (rt.err) {
359
+ return fail(rt.err)
360
+ }
361
+
362
+ //副本收件人email
363
+ let rc = normEmails(opt, 'toEmailsCC')
364
+ if (rc.err) {
365
+ return fail(rc.err)
366
+ }
367
+
368
+ //密件副本收件人email
369
+ let rb = normEmails(opt, 'toEmailsBCC')
370
+ if (rb.err) {
371
+ return fail(rb.err)
372
+ }
373
+
374
+ //須至少一位收件人
375
+ if (rt.emails.length + rc.emails.length + rb.emails.length === 0) {
376
+ return fail('no recipients')
377
+ }
378
+
379
+ //transporter
380
+ let transporter = nodemailer.createTransport({
381
+ host: srcHost,
382
+ port: srcPort,
383
+ auth: {
384
+ user: srcEmail,
385
+ pass: srcPW
386
+ },
387
+
388
+ //secureConnecton: true,
389
+ secure: srcPort === 465, // true for 465, false for other ports
390
+
391
+ // ssl: false,
392
+ // ignoreTLS: false,
393
+ // requireTLS: true,
394
+ // tls: {
395
+ // secureProtocol: 'TLSv1_2_method',
396
+ // rejectUnauthorized: false
397
+ // }
398
+
399
+ })
400
+
401
+ //optt
402
+ let optt = {
403
+ from: {
404
+ name: srcName,
405
+ address: srcEmail,
406
+ }, //以位址物件給予, 避免srcName被當成位址字串重新解析
407
+ to: rt.emails,
408
+ cc: rc.emails,
409
+ bcc: rb.emails,
410
+ subject: emTitle,
411
+ html: emContent,
412
+ attachments: ra.atts
413
+ }
414
+
415
+ //sendMail
416
+ transporter.sendMail(optt, function(err, info) {
417
+ if (err) {
418
+ pm.reject(err)
419
+ }
420
+ else {
421
+ pm.resolve(info.response)
422
+ }
423
+ })
424
+
425
+ }
426
+ catch (err) {
427
+ //非預期之例外(例如opt之getter拋錯), reject原例外, 確保不同步拋出
428
+ pm.reject(err)
429
+ }
430
+
431
+ return pm
432
+ }
433
+
434
+
435
+ export default WEmail
203
436
  </code></pre>
204
437
  </article>
205
438
  </section>
@@ -214,7 +447,7 @@ export default WEmail
214
447
  <br class="clear">
215
448
 
216
449
  <footer>
217
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Tue Sep 22 2026 18:29:00 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
450
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Thu Oct 01 2026 23:06:13 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
218
451
  </footer>
219
452
 
220
453
  <script>prettyPrint();</script>