waibu 1.2.12 → 2.0.1
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.
- package/.github/FUNDING.yml +13 -0
- package/.github/workflows/repo-lockdown.yml +24 -0
- package/.jsdoc.conf.json +45 -0
- package/LICENSE +1 -1
- package/README.md +40 -7
- package/config-prod.json +1 -1
- package/docs/Waibu.html +3 -0
- package/docs/data/search.json +1 -0
- package/docs/fonts/Inconsolata-Regular.ttf +0 -0
- package/docs/fonts/OpenSans-Regular.ttf +0 -0
- package/docs/fonts/WorkSans-Bold.ttf +0 -0
- package/docs/global.html +3 -0
- package/docs/index.html +3 -0
- package/docs/index.js.html +566 -0
- package/docs/scripts/core.js +726 -0
- package/docs/scripts/core.min.js +23 -0
- package/docs/scripts/resize.js +90 -0
- package/docs/scripts/search.js +265 -0
- package/docs/scripts/search.min.js +6 -0
- package/docs/scripts/third-party/Apache-License-2.0.txt +202 -0
- package/docs/scripts/third-party/fuse.js +9 -0
- package/docs/scripts/third-party/hljs-line-num-original.js +369 -0
- package/docs/scripts/third-party/hljs-line-num.js +1 -0
- package/docs/scripts/third-party/hljs-original.js +5171 -0
- package/docs/scripts/third-party/hljs.js +1 -0
- package/docs/scripts/third-party/popper.js +5 -0
- package/docs/scripts/third-party/tippy.js +1 -0
- package/docs/scripts/third-party/tocbot.js +672 -0
- package/docs/scripts/third-party/tocbot.min.js +1 -0
- package/docs/static/bitcoin.jpeg +0 -0
- package/docs/static/home.md +31 -0
- package/docs/static/logo-ecosystem.png +0 -0
- package/docs/static/logo.png +0 -0
- package/docs/styles/clean-jsdoc-theme-base.css +1159 -0
- package/docs/styles/clean-jsdoc-theme-dark.css +412 -0
- package/docs/styles/clean-jsdoc-theme-light.css +482 -0
- package/docs/styles/clean-jsdoc-theme-scrollbar.css +30 -0
- package/docs/styles/clean-jsdoc-theme-without-scrollbar.min.css +1 -0
- package/docs/styles/clean-jsdoc-theme.min.css +1 -0
- package/extend/bajo/hook/waibu@on-close.js +5 -0
- package/extend/bajo/hook/waibu@on-ready.js +5 -0
- package/{bajo/hook/on-request.js → extend/bajo/hook/waibu@on-request.js} +6 -6
- package/{bajo/hook/on-response.js → extend/bajo/hook/waibu@on-response.js} +2 -2
- package/index.js +267 -51
- package/lib/app-hook.js +2 -2
- package/lib/app.js +9 -8
- package/lib/build-locals.js +8 -8
- package/lib/collect-route-path-handlers.js +3 -2
- package/lib/handle-download.js +2 -2
- package/lib/handle-forward.js +1 -1
- package/lib/home.js +2 -2
- package/lib/{log-routes.js → print-routes.js} +3 -3
- package/lib/webapp-scope/attach-intl.js +4 -4
- package/lib/webapp-scope/handle-compress.js +1 -1
- package/lib/webapp-scope/handle-cors.js +1 -1
- package/lib/webapp-scope/handle-helmet.js +1 -1
- package/lib/webapp-scope/handle-multipart-body.js +3 -3
- package/lib/webapp-scope/handle-rate-limit.js +2 -2
- package/lib/webapp-scope/is-route-disabled.js +9 -5
- package/lib/webapp-scope/merge-route-hooks.js +2 -2
- package/lib/webapp-scope/route-hook.js +1 -1
- package/package.json +7 -2
- package/wiki/CONFIG.md +45 -0
- package/wiki/CONTRIBUTING.md +5 -0
- package/wiki/DEV-GUIDE.md +1 -0
- package/wiki/ECOSYSTEM.md +19 -0
- package/wiki/GETTING-STARTED.md +76 -0
- package/wiki/USER-GUIDE.md +1 -0
- package/bajo/hook/on-close.js +0 -5
- package/bajo/hook/on-ready.js +0 -5
- /package/{bajo/hook/on-route.js → extend/bajo/hook/waibu@on-route.js} +0 -0
- /package/{bajo → extend/bajo}/intl/en-US.json +0 -0
- /package/{bajo → extend/bajo}/intl/id.json +0 -0
- /package/{bajoTemplate → extend/bajoTemplate}/layout/email.html +0 -0
- /package/{bajoTemplate → extend/bajoTemplate}/layout/email.id.html +0 -0
package/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import collectRoutePathHandlers from './lib/collect-route-path-handlers.js'
|
|
|
2
2
|
import fastify from 'fastify'
|
|
3
3
|
import appHook from './lib/app-hook.js'
|
|
4
4
|
import routeHook from './lib/webapp-scope/route-hook.js'
|
|
5
|
-
import
|
|
5
|
+
import printRoutes from './lib/print-routes.js'
|
|
6
6
|
import { boot } from './lib/app.js'
|
|
7
7
|
import sensible from '@fastify/sensible'
|
|
8
8
|
import noIcon from 'fastify-no-icon'
|
|
@@ -12,15 +12,83 @@ import handleRedirect from './lib/handle-redirect.js'
|
|
|
12
12
|
import buildLocals from './lib/build-locals.js'
|
|
13
13
|
import queryString from 'query-string'
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* @typedef TEscapeChars
|
|
17
|
+
* @type {Object}
|
|
18
|
+
* @memberof Waibu
|
|
19
|
+
* @property {string} <=<
|
|
20
|
+
* @property {string} >=>
|
|
21
|
+
* @property {string} "="
|
|
22
|
+
* @property {string} '='
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Plugin factory
|
|
27
|
+
*
|
|
28
|
+
* @param {string} pkgName - NPM package name
|
|
29
|
+
* @returns {class}
|
|
30
|
+
*/
|
|
15
31
|
async function factory (pkgName) {
|
|
16
32
|
const me = this
|
|
17
33
|
|
|
18
|
-
|
|
34
|
+
/**
|
|
35
|
+
* Waibu Web Framework plugin for Bajo. This is the main foundation of all web apps attached to
|
|
36
|
+
* the system through a route prefix. Those web apps are then build as childrens with
|
|
37
|
+
* its own fastify's context.
|
|
38
|
+
*
|
|
39
|
+
* There are currently 3 web apps available:
|
|
40
|
+
* - {@link https://github.com/ardhi/waibu-static|waibu-static} for static content delivery
|
|
41
|
+
* - {@link https://github.com/ardhi/waibu-rest-api|waibu-rest-api} for rest api setup
|
|
42
|
+
* - and {@link https://github.com/ardhi/waibu-mpa|waibu-mpa} for normal multi-page application
|
|
43
|
+
*
|
|
44
|
+
* You should write your code as the extension of above web apps. Not to this main app.
|
|
45
|
+
* Unless, of course, if you want to write custom web apps with its own context.
|
|
46
|
+
*
|
|
47
|
+
* @class
|
|
48
|
+
*/
|
|
49
|
+
class Waibu extends this.app.pluginClass.base {
|
|
50
|
+
/**
|
|
51
|
+
* @constant {string[]}
|
|
52
|
+
* @default ['onRequest', 'onResponse', 'preParsing', 'preValidation', 'preHandler', 'preSerialization', 'onSend', 'onTimeout', 'onError']
|
|
53
|
+
* @memberof Waibu
|
|
54
|
+
*/
|
|
55
|
+
static hookTypes = ['onRequest', 'onResponse', 'preParsing', 'preValidation', 'preHandler',
|
|
56
|
+
'preSerialization', 'onSend', 'onTimeout', 'onError']
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @constant {string}
|
|
60
|
+
* @memberof Waibu
|
|
61
|
+
* @default 'w'
|
|
62
|
+
*/
|
|
63
|
+
static alias = 'w'
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @constant {string[]}
|
|
67
|
+
* @default ['bajo-extra']
|
|
68
|
+
* @memberof Waibu
|
|
69
|
+
*/
|
|
70
|
+
static dependencies = ['bajo-extra']
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* @constant {TEscapeChars}
|
|
74
|
+
* @memberof Waibu
|
|
75
|
+
*/
|
|
76
|
+
static escapeChars = {
|
|
77
|
+
'<': '<',
|
|
78
|
+
'>': '>',
|
|
79
|
+
'"': '"',
|
|
80
|
+
"'": '''
|
|
81
|
+
}
|
|
82
|
+
|
|
19
83
|
constructor () {
|
|
20
84
|
super(pkgName, me.app)
|
|
21
|
-
|
|
22
|
-
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* @see {@tutorial config}
|
|
88
|
+
* @type {Object}
|
|
89
|
+
*/
|
|
23
90
|
this.config = {
|
|
91
|
+
home: undefined,
|
|
24
92
|
server: {
|
|
25
93
|
host: '127.0.0.1',
|
|
26
94
|
port: 7771
|
|
@@ -46,7 +114,7 @@ async function factory (pkgName) {
|
|
|
46
114
|
lang: 'lang'
|
|
47
115
|
},
|
|
48
116
|
paramsCharMap: {},
|
|
49
|
-
|
|
117
|
+
printRoutes: true,
|
|
50
118
|
pageTitleFormat: '%s : %s',
|
|
51
119
|
siteInfo: {
|
|
52
120
|
title: 'My Website',
|
|
@@ -77,12 +145,6 @@ async function factory (pkgName) {
|
|
|
77
145
|
}
|
|
78
146
|
}
|
|
79
147
|
}
|
|
80
|
-
this.escapeChars = {
|
|
81
|
-
'<': '<',
|
|
82
|
-
'>': '>',
|
|
83
|
-
'"': '"',
|
|
84
|
-
"'": '''
|
|
85
|
-
}
|
|
86
148
|
this.qs = {
|
|
87
149
|
parse: (item) => {
|
|
88
150
|
return queryString.parse(item, {
|
|
@@ -94,15 +156,25 @@ async function factory (pkgName) {
|
|
|
94
156
|
stringify: queryString.stringify,
|
|
95
157
|
stringifyUrl: queryString.stringifyUrl
|
|
96
158
|
}
|
|
97
|
-
this.hookTypes = ['onRequest', 'onResponse', 'preParsing', 'preValidation', 'preHandler',
|
|
98
|
-
'preSerialization', 'onSend', 'onTimeout', 'onError']
|
|
99
159
|
}
|
|
100
160
|
|
|
161
|
+
/**
|
|
162
|
+
* Initialize plugin
|
|
163
|
+
*
|
|
164
|
+
* @method
|
|
165
|
+
* @async
|
|
166
|
+
*/
|
|
101
167
|
init = async () => {
|
|
102
168
|
if (this.config.home === '/') this.config.home = false
|
|
103
169
|
await collectRoutePathHandlers.call(this)
|
|
104
170
|
}
|
|
105
171
|
|
|
172
|
+
/**
|
|
173
|
+
* Start plugin
|
|
174
|
+
*
|
|
175
|
+
* @method
|
|
176
|
+
* @async
|
|
177
|
+
*/
|
|
106
178
|
start = async () => {
|
|
107
179
|
const { generateId, runHook } = this.app.bajo
|
|
108
180
|
const cfg = this.getConfig()
|
|
@@ -132,21 +204,33 @@ async function factory (pkgName) {
|
|
|
132
204
|
await handleRedirect.call(this, instance)
|
|
133
205
|
await handleForward.call(this, instance)
|
|
134
206
|
await appHook.call(this)
|
|
135
|
-
await routeHook.call(this, this.
|
|
207
|
+
await routeHook.call(this, this.ns)
|
|
136
208
|
await boot.call(this)
|
|
137
209
|
await instance.listen(cfg.server)
|
|
138
|
-
if (cfg.
|
|
210
|
+
if (cfg.printRoutes) printRoutes.call(this)
|
|
139
211
|
}
|
|
140
212
|
|
|
213
|
+
/**
|
|
214
|
+
* Exit handler
|
|
215
|
+
*
|
|
216
|
+
* @method
|
|
217
|
+
* @async
|
|
218
|
+
*/
|
|
141
219
|
exit = async () => {
|
|
142
220
|
this.instance.close()
|
|
143
221
|
}
|
|
144
222
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
223
|
+
/**
|
|
224
|
+
* Find route by route name
|
|
225
|
+
*
|
|
226
|
+
* @param {string} name - ns based route name
|
|
227
|
+
* @returns {Object} Route object
|
|
228
|
+
*/
|
|
229
|
+
findRoute = (name) => {
|
|
230
|
+
const { outmatch } = this.app.lib
|
|
231
|
+
const { find } = this.app.lib._
|
|
148
232
|
const { breakNsPath } = this.app.bajo
|
|
149
|
-
let { ns, subNs = '', path } = breakNsPath(
|
|
233
|
+
let { ns, subNs = '', path } = breakNsPath(name)
|
|
150
234
|
const params = path.split('|')
|
|
151
235
|
if (params.length > 1) path = params[0]
|
|
152
236
|
return find(this.routes, r => {
|
|
@@ -158,15 +242,37 @@ async function factory (pkgName) {
|
|
|
158
242
|
})
|
|
159
243
|
}
|
|
160
244
|
|
|
245
|
+
get escapeChars () {
|
|
246
|
+
return this.constructor.escapeChars
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Escape text
|
|
251
|
+
*
|
|
252
|
+
* @method
|
|
253
|
+
* @param {string} text
|
|
254
|
+
* @returns {string}
|
|
255
|
+
*/
|
|
161
256
|
escape = (text = '') => {
|
|
162
257
|
if (typeof text !== 'string') return text
|
|
163
|
-
const { forOwn } = this.lib._
|
|
258
|
+
const { forOwn } = this.app.lib._
|
|
164
259
|
forOwn(this.escapeChars, (v, k) => {
|
|
165
260
|
text = text.replaceAll(k, v)
|
|
166
261
|
})
|
|
167
262
|
return text
|
|
168
263
|
}
|
|
169
264
|
|
|
265
|
+
/**
|
|
266
|
+
* Fetch something from url. A wrapper of bajo-extra's fetchUrl which support
|
|
267
|
+
* bajo's ns based url.
|
|
268
|
+
*
|
|
269
|
+
* @method
|
|
270
|
+
* @async
|
|
271
|
+
* @param {string} url - Also support ns based url
|
|
272
|
+
* @param {Object} [opts={}] - node's fetch options
|
|
273
|
+
* @param {Object} [extra={}] - See {@link https://ardhi.github.io/bajo-extra|bajo-extra}
|
|
274
|
+
* @returns {Object}
|
|
275
|
+
*/
|
|
170
276
|
fetch = async (url, opts = {}, extra = {}) => {
|
|
171
277
|
const { fetch } = this.app.bajoExtra
|
|
172
278
|
extra.rawResponse = true
|
|
@@ -183,22 +289,43 @@ async function factory (pkgName) {
|
|
|
183
289
|
return result
|
|
184
290
|
}
|
|
185
291
|
|
|
292
|
+
/**
|
|
293
|
+
* Get visitor IP from fastify's request object
|
|
294
|
+
*
|
|
295
|
+
* @method
|
|
296
|
+
* @param {Object} req - request object
|
|
297
|
+
* @returns {string}
|
|
298
|
+
*/
|
|
186
299
|
getIp = (req) => {
|
|
187
|
-
const { isEmpty } = this.lib._
|
|
300
|
+
const { isEmpty } = this.app.lib._
|
|
188
301
|
let fwd = req.headers['x-forwarded-for'] ?? ''
|
|
189
302
|
if (!Array.isArray(fwd)) fwd = fwd.split(',').map(ip => ip.trim())
|
|
190
303
|
return isEmpty(fwd[0]) ? req.ip : fwd[0]
|
|
191
304
|
}
|
|
192
305
|
|
|
306
|
+
/**
|
|
307
|
+
* Get origin of fastify's request object
|
|
308
|
+
*
|
|
309
|
+
* @method
|
|
310
|
+
* @param {Object} req
|
|
311
|
+
* @returns {string}
|
|
312
|
+
*/
|
|
193
313
|
getOrigin = (req) => {
|
|
194
|
-
const { isEmpty } = this.lib._
|
|
314
|
+
const { isEmpty } = this.app.lib._
|
|
195
315
|
let host = req.host
|
|
196
316
|
if (isEmpty(host) || host === ':authority') host = `${this.config.server.host}:${this.config.server.port}`
|
|
197
317
|
return `${req.protocol}://${host}`
|
|
198
318
|
}
|
|
199
319
|
|
|
320
|
+
/**
|
|
321
|
+
* Get plugin by prefix
|
|
322
|
+
*
|
|
323
|
+
* @method
|
|
324
|
+
* @param {string} prefix
|
|
325
|
+
* @returns {Object}
|
|
326
|
+
*/
|
|
200
327
|
getPluginByPrefix = (prefix) => {
|
|
201
|
-
const { get, find } = this.lib._
|
|
328
|
+
const { get, find } = this.app.lib._
|
|
202
329
|
const item = find(this.app.waibu.routes, r => {
|
|
203
330
|
return get(r, 'config.prefix') === prefix
|
|
204
331
|
})
|
|
@@ -206,19 +333,34 @@ async function factory (pkgName) {
|
|
|
206
333
|
if (ns) return this.app[ns]
|
|
207
334
|
}
|
|
208
335
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
336
|
+
/**
|
|
337
|
+
* Get plugin's prefix by name
|
|
338
|
+
*
|
|
339
|
+
* @method
|
|
340
|
+
* @param {string} name - Plugin's name
|
|
341
|
+
* @param {string} [webApp=waibuMpa] - Web app to use
|
|
342
|
+
* @returns {string}
|
|
343
|
+
*/
|
|
344
|
+
getPluginPrefix = (name, webApp = 'waibuMpa') => {
|
|
345
|
+
const { get, trim } = this.app.lib._
|
|
346
|
+
let prefix = get(this, `app.${name}.config.${webApp}.prefix`, get(this, `app.${name}.config.waibu.prefix`, this.app[name].alias))
|
|
347
|
+
if (name === 'main') {
|
|
213
348
|
const cfg = this.app[webApp].config
|
|
214
349
|
if (cfg.mountMainAsRoot) prefix = ''
|
|
215
350
|
}
|
|
216
|
-
|
|
217
351
|
return trim(prefix, '/')
|
|
218
352
|
}
|
|
219
353
|
|
|
220
|
-
|
|
221
|
-
|
|
354
|
+
/**
|
|
355
|
+
* Get all available routes
|
|
356
|
+
*
|
|
357
|
+
* @method
|
|
358
|
+
* @param {boolean} [grouped=false] - Returns as groups of urls and methods
|
|
359
|
+
* @param {*} [lite=false] - Retuns only urls and methods
|
|
360
|
+
* @returns {Array}
|
|
361
|
+
*/
|
|
362
|
+
getRoutes = (grouped = false, lite = false) => {
|
|
363
|
+
const { groupBy, orderBy, mapValues, map, pick } = this.app.lib._
|
|
222
364
|
const all = this.routes
|
|
223
365
|
let routes
|
|
224
366
|
if (grouped) {
|
|
@@ -229,18 +371,34 @@ async function factory (pkgName) {
|
|
|
229
371
|
return routes
|
|
230
372
|
}
|
|
231
373
|
|
|
232
|
-
|
|
374
|
+
/**
|
|
375
|
+
* Get uploaded files by request ID
|
|
376
|
+
*
|
|
377
|
+
* @method
|
|
378
|
+
* @param {string} reqId - Request ID
|
|
379
|
+
* @param {boolean} [fileUrl=false] - If ```true```, files returned as file url format (```file:///...```)
|
|
380
|
+
* @param {*} returnDir - If ```true```, also return its directory
|
|
381
|
+
* @returns {(Object|Array)} - Returns object if ```returnDir``` is ```true```, array of files otherwise
|
|
382
|
+
*/
|
|
383
|
+
getUploadedFiles = async (reqId, fileUrl = false, returnDir = false) => {
|
|
233
384
|
const { getPluginDataDir, resolvePath } = this.app.bajo
|
|
234
|
-
const { fastGlob } = this.lib
|
|
235
|
-
const dir = `${getPluginDataDir(this.
|
|
385
|
+
const { fastGlob } = this.app.lib
|
|
386
|
+
const dir = `${getPluginDataDir(this.ns)}/upload/${reqId}`
|
|
236
387
|
const result = await fastGlob(`${dir}/*`)
|
|
237
388
|
if (!fileUrl) return returnDir ? { dir, files: result } : result
|
|
238
389
|
const files = result.map(f => resolvePath(f, true))
|
|
239
390
|
return returnDir ? { dir, files } : files
|
|
240
391
|
}
|
|
241
392
|
|
|
393
|
+
/**
|
|
394
|
+
* Is namespace's path contains language detector token?
|
|
395
|
+
*
|
|
396
|
+
* @method
|
|
397
|
+
* @param {string} ns - Plugin name
|
|
398
|
+
* @returns {boolean}
|
|
399
|
+
*/
|
|
242
400
|
isIntlPath = (ns) => {
|
|
243
|
-
const { get } = this.lib._
|
|
401
|
+
const { get } = this.app.lib._
|
|
244
402
|
return get(this.app[ns], 'config.intl.detectors', []).includes('path')
|
|
245
403
|
}
|
|
246
404
|
|
|
@@ -248,6 +406,13 @@ async function factory (pkgName) {
|
|
|
248
406
|
throw this.error('_notFound', { path: name })
|
|
249
407
|
}
|
|
250
408
|
|
|
409
|
+
/**
|
|
410
|
+
* Parse filter found from Fastify's request based on keys set in config object
|
|
411
|
+
*
|
|
412
|
+
* @method
|
|
413
|
+
* @param {Object} req - Request object
|
|
414
|
+
* @returns {Object}
|
|
415
|
+
*/
|
|
251
416
|
parseFilter = (req) => {
|
|
252
417
|
const result = {}
|
|
253
418
|
const items = Object.keys(this.config.qsKey)
|
|
@@ -257,26 +422,45 @@ async function factory (pkgName) {
|
|
|
257
422
|
return result
|
|
258
423
|
}
|
|
259
424
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
425
|
+
/**
|
|
426
|
+
* Get route directory by plugin's name
|
|
427
|
+
*
|
|
428
|
+
* @param {*} ns - Namespace
|
|
429
|
+
* @param {*} [baseNs] - Base namespace. If not provided, defaults to scope's ns
|
|
430
|
+
* @returns {string}
|
|
431
|
+
*/
|
|
432
|
+
routeDir = (ns, baseNs) => {
|
|
433
|
+
const { get } = this.app.lib._
|
|
434
|
+
if (!baseNs) baseNs = ns
|
|
435
|
+
const cfg = this.app[baseNs].config
|
|
436
|
+
const prefix = get(cfg, 'waibu.prefix', this.app[baseNs].alias)
|
|
265
437
|
const dir = prefix === '' ? '' : `/${prefix}`
|
|
266
|
-
if (!ns) return dir
|
|
267
438
|
const cfgMpa = get(this, 'app.waibuMpa.config')
|
|
268
|
-
if (ns === this.app.
|
|
269
|
-
if (ns ===
|
|
439
|
+
if (ns === this.app.mainNs && cfgMpa.mountMainAsRoot) return ''
|
|
440
|
+
if (ns === baseNs) return dir
|
|
270
441
|
return dir + `/${get(this.app[ns].config, 'waibu.prefix', this.app[ns].alias)}`
|
|
271
442
|
}
|
|
272
443
|
|
|
273
|
-
|
|
444
|
+
/**
|
|
445
|
+
* Get route path by route's name:
|
|
446
|
+
* - If it is a ```mailto:``` or ```tel:``` url, it returns as is
|
|
447
|
+
* - If it is a ns based name, it will be parsed first
|
|
448
|
+
*
|
|
449
|
+
* @method
|
|
450
|
+
* @param {string} name
|
|
451
|
+
* @param {Object} [options={}] - Options object
|
|
452
|
+
* @param {string} [options.base=waibu] - Base namespace
|
|
453
|
+
* @param {boolean} [options.guessHost] - If true, guest host if host is not set
|
|
454
|
+
* @param {Object} [options.query={}] - Query string's object. If provided, it will be added to returned value
|
|
455
|
+
* @param {Object} [options.params={}] - Parameter object. If provided, it will be merged to returned value
|
|
456
|
+
* @returns {string}
|
|
457
|
+
*/
|
|
458
|
+
routePath = (name, options = {}) => {
|
|
274
459
|
const { getPlugin } = this.app.bajo
|
|
275
|
-
const { defaultsDeep } = this.lib.aneka
|
|
276
|
-
const { isEmpty, get, trimEnd, trimStart } = this.lib._
|
|
460
|
+
const { defaultsDeep } = this.app.lib.aneka
|
|
461
|
+
const { isEmpty, get, trimEnd, trimStart } = this.app.lib._
|
|
277
462
|
const { breakNsPath } = this.app.bajo
|
|
278
|
-
const { query = {}, base =
|
|
279
|
-
if (isEmpty(name)) return name
|
|
463
|
+
const { query = {}, base = this.ns, params = {}, guessHost } = options
|
|
280
464
|
|
|
281
465
|
const plugin = getPlugin(base)
|
|
282
466
|
const cfg = plugin.config ?? {}
|
|
@@ -304,10 +488,22 @@ async function factory (pkgName) {
|
|
|
304
488
|
return url
|
|
305
489
|
}
|
|
306
490
|
|
|
491
|
+
/**
|
|
492
|
+
* Method to send mail through Masohi Messaging System. It is a thin wrapper
|
|
493
|
+
* for {@link https://github.com/ardhi/masohi-mail|masohi-mail} send method.
|
|
494
|
+
*
|
|
495
|
+
* If masohi is not loaded, nothing is delivered.
|
|
496
|
+
*
|
|
497
|
+
* @method
|
|
498
|
+
* @async
|
|
499
|
+
* @param {(string|Array)} tpl - Mail's template to use. If a string is given, the same template will be used for html & plaintext versions. Otherwise, the first template will be used for html mail, and the second one is for it's plaintext version
|
|
500
|
+
* @param {Object} [params={}] - {@link https://github.com/ardhi/masohi-mail|masohi-mail}'s params object.
|
|
501
|
+
* @returns
|
|
502
|
+
*/
|
|
307
503
|
sendMail = async (tpl, { to, cc, bcc, from, subject, data = {}, conn, source, options = {} }) => {
|
|
308
504
|
conn = conn ?? 'masohiMail:default'
|
|
309
505
|
if (!this.app.masohi || !this.app.masohiMail) return
|
|
310
|
-
const { get, isString } = this.lib._
|
|
506
|
+
const { get, isString } = this.app.lib._
|
|
311
507
|
const { generateId } = this.app.bajo
|
|
312
508
|
const { render } = this.app.bajoTemplate
|
|
313
509
|
if (isString(tpl)) tpl = [tpl]
|
|
@@ -319,11 +515,22 @@ async function factory (pkgName) {
|
|
|
319
515
|
const message = await render(tpl[0], locals, opts)
|
|
320
516
|
if (tpl[1]) opts.messageText = await render(tpl[1], locals, opts)
|
|
321
517
|
const payload = { type: 'object', data: { to, cc, bcc, from, subject, message, options: opts } }
|
|
322
|
-
await this.app.masohi.send({ payload, source: source ?? this.
|
|
518
|
+
await this.app.masohi.send({ payload, source: source ?? this.ns, conn }) // mail sent through worker
|
|
323
519
|
}
|
|
324
520
|
|
|
521
|
+
/**
|
|
522
|
+
* Recursively unescape block of texts
|
|
523
|
+
*
|
|
524
|
+
* @method
|
|
525
|
+
* @param {string} content - Source content
|
|
526
|
+
* @param {string} start - Block's start
|
|
527
|
+
* @param {string} end - Block's end
|
|
528
|
+
* @param {string} startReplacer - Token to use as block's start replacer
|
|
529
|
+
* @param {string} endReplacer - Token to use as block's end replacer
|
|
530
|
+
* @returns {string}
|
|
531
|
+
*/
|
|
325
532
|
unescapeBlock = (content, start, end, startReplacer, endReplacer) => {
|
|
326
|
-
const { extractText } = this.lib.aneka
|
|
533
|
+
const { extractText } = this.app.lib.aneka
|
|
327
534
|
const { result } = extractText(content, start, end)
|
|
328
535
|
if (result.length === 0) return content
|
|
329
536
|
const unescaped = this.unescape(result)
|
|
@@ -333,8 +540,15 @@ async function factory (pkgName) {
|
|
|
333
540
|
return this.unescapeBlock(block, start, end, startReplacer, endReplacer)
|
|
334
541
|
}
|
|
335
542
|
|
|
543
|
+
/**
|
|
544
|
+
* Unescape text using {@link TEscapeChars} rules
|
|
545
|
+
*
|
|
546
|
+
* @method
|
|
547
|
+
* @param {string} text - Text to unescape
|
|
548
|
+
* @returns {string}
|
|
549
|
+
*/
|
|
336
550
|
unescape = (text) => {
|
|
337
|
-
const { forOwn, invert } = this.lib._
|
|
551
|
+
const { forOwn, invert } = this.app.lib._
|
|
338
552
|
const mapping = invert(this.escapeChars)
|
|
339
553
|
forOwn(mapping, (v, k) => {
|
|
340
554
|
text = text.replaceAll(k, v)
|
|
@@ -342,6 +556,8 @@ async function factory (pkgName) {
|
|
|
342
556
|
return text
|
|
343
557
|
}
|
|
344
558
|
}
|
|
559
|
+
|
|
560
|
+
return Waibu
|
|
345
561
|
}
|
|
346
562
|
|
|
347
563
|
export default factory
|
package/lib/app-hook.js
CHANGED
|
@@ -5,8 +5,8 @@ async function appHook () {
|
|
|
5
5
|
for (const hook of hooks) {
|
|
6
6
|
me.instance.addHook(hook, async function (...args) {
|
|
7
7
|
args.push(this)
|
|
8
|
-
if (['onClose', 'onReady'].includes(hook)) await runHook(`${me.
|
|
9
|
-
else await runHook(`${me.
|
|
8
|
+
if (['onClose', 'onReady'].includes(hook)) await runHook(`${me.ns}:${hook}`, ...args)
|
|
9
|
+
else await runHook(`${me.ns}:${hook}`, ...args)
|
|
10
10
|
})
|
|
11
11
|
}
|
|
12
12
|
}
|
package/lib/app.js
CHANGED
|
@@ -2,11 +2,12 @@ import home from './home.js'
|
|
|
2
2
|
|
|
3
3
|
export async function collect (glob = 'boot.js', baseNs) {
|
|
4
4
|
const { eachPlugins, importModule } = this.app.bajo
|
|
5
|
-
const { orderBy, get } = this.lib._
|
|
6
|
-
if (!baseNs) baseNs = this.
|
|
5
|
+
const { orderBy, get } = this.app.lib._
|
|
6
|
+
if (!baseNs) baseNs = this.ns
|
|
7
7
|
const mods = []
|
|
8
8
|
|
|
9
|
-
await eachPlugins(async function ({
|
|
9
|
+
await eachPlugins(async function ({ file }) {
|
|
10
|
+
const { ns, alias, config } = this
|
|
10
11
|
const mod = await importModule(file, { asHandler: true })
|
|
11
12
|
mod.prefix = get(config, 'waibu.prefix', alias)
|
|
12
13
|
if (get(config, 'intl.detectors', []).includes('path')) mod.prefix = `/:lang${mod.prefix}`
|
|
@@ -26,23 +27,23 @@ export async function collect (glob = 'boot.js', baseNs) {
|
|
|
26
27
|
export async function boot () {
|
|
27
28
|
const { runHook } = this.app.bajo
|
|
28
29
|
const mods = await collect.call(this)
|
|
29
|
-
await runHook(`${this.
|
|
30
|
+
await runHook(`${this.ns}:beforeAppBoot`)
|
|
30
31
|
for (const m of mods) {
|
|
31
32
|
const disabled = this.app[m.ns].config.disabled
|
|
32
33
|
if (Array.isArray(disabled) && disabled.length === 1 && ['*', 'all'].includes(disabled[0])) {
|
|
33
34
|
this.log.warn('allRoutesConfigDisabled%s', m.ns)
|
|
34
35
|
continue
|
|
35
36
|
}
|
|
36
|
-
await runHook(`${this.
|
|
37
|
+
await runHook(`${this.ns}.${m.ns}:beforeAppBoot`)
|
|
37
38
|
this.log.debug('bootApp%s', m.ns)
|
|
38
39
|
await this.instance.register(async (ctx) => {
|
|
39
40
|
const plugin = this.app[m.ns]
|
|
40
41
|
plugin.instance = ctx
|
|
41
|
-
await runHook(`${plugin.
|
|
42
|
+
await runHook(`${plugin.ns}:afterCreateContext`, ctx)
|
|
42
43
|
await m.handler.call(plugin, ctx, m.prefix)
|
|
43
44
|
}, { prefix: m.prefix })
|
|
44
|
-
await runHook(`${this.
|
|
45
|
+
await runHook(`${this.ns}.${m.ns}:afterAppBoot`)
|
|
45
46
|
}
|
|
46
|
-
await runHook(`${this.
|
|
47
|
+
await runHook(`${this.ns}:afterAppBoot`)
|
|
47
48
|
await home.call(this)
|
|
48
49
|
}
|
package/lib/build-locals.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
async function buildHomesMenu (req) {
|
|
2
2
|
const { callHandler } = this.app.bajo
|
|
3
|
-
const { get, find, orderBy } = this.lib._
|
|
3
|
+
const { get, find, orderBy } = this.app.lib._
|
|
4
4
|
const routes = []
|
|
5
|
-
for (const ns of this.app.
|
|
5
|
+
for (const ns of this.app.getAllNs()) {
|
|
6
6
|
let home = get(this, `app.${ns}.config.waibuMpa.home`)
|
|
7
7
|
const homeHandler = get(this, `app.${ns}.config.waibuMpa.homeHandler`)
|
|
8
8
|
if (homeHandler) home = await callHandler(this.app[ns], homeHandler)
|
|
@@ -14,15 +14,15 @@ async function buildHomesMenu (req) {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
async function buildPagesMenu (req) {
|
|
17
|
-
const { find, orderBy, merge, isString } = this.lib._
|
|
17
|
+
const { find, orderBy, merge, isString } = this.app.lib._
|
|
18
18
|
const { callHandler, runHook } = this.app.bajo
|
|
19
19
|
const all = [{ icon: 'house', href: '/', level: 1 }]
|
|
20
|
-
for (const ns of this.app.
|
|
20
|
+
for (const ns of this.app.getAllNs()) {
|
|
21
21
|
const items = []
|
|
22
22
|
let pages = this.app[ns].getConfig('waibuMpa.menuHandler', { defValue: [] })
|
|
23
23
|
if (isString(pages)) pages = await callHandler(this.app[ns], pages, req)
|
|
24
24
|
if (pages.length === 0) continue
|
|
25
|
-
await runHook(`${this.
|
|
25
|
+
await runHook(`${this.ns}.${ns}:afterBuildPagesMenu`, pages, req)
|
|
26
26
|
for (const page of pages) {
|
|
27
27
|
const existing = find(all, { title: page.title })
|
|
28
28
|
page.level = page.level ?? 1000
|
|
@@ -51,7 +51,7 @@ async function buildPagesMenu (req) {
|
|
|
51
51
|
}
|
|
52
52
|
async function buildLocals ({ tpl, params = {}, opts = {} } = {}) {
|
|
53
53
|
const { runHook } = this.app.bajo
|
|
54
|
-
const { set, merge, pick, get, isEmpty, find } = this.lib._
|
|
54
|
+
const { set, merge, pick, get, isEmpty, find } = this.app.lib._
|
|
55
55
|
const { req, reply } = opts
|
|
56
56
|
|
|
57
57
|
const appTitle = this.app.waibuMpa ? req.t(this.app.waibuMpa.getAppTitle(req.ns)) : ''
|
|
@@ -86,8 +86,8 @@ async function buildLocals ({ tpl, params = {}, opts = {} } = {}) {
|
|
|
86
86
|
set(params, 'menu.homes', await buildHomesMenu.call(this, req))
|
|
87
87
|
set(params, 'menu.pages', await buildPagesMenu.call(this, req))
|
|
88
88
|
const merged = merge({}, params, { _meta })
|
|
89
|
-
await runHook(`${this.
|
|
90
|
-
if (!isEmpty(routeOpts.ns)) await runHook(`${this.
|
|
89
|
+
await runHook(`${this.ns}:afterBuildLocals`, merged, req)
|
|
90
|
+
if (!isEmpty(routeOpts.ns)) await runHook(`${this.ns}.${routeOpts.ns}:afterBuildLocals`, merged, req)
|
|
91
91
|
return merged
|
|
92
92
|
}
|
|
93
93
|
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
async function collectRoutePathHandlers () {
|
|
2
2
|
const { eachPlugins } = this.app.bajo
|
|
3
|
-
const { isEmpty } = this.lib._
|
|
3
|
+
const { isEmpty } = this.app.lib._
|
|
4
4
|
this.routePathHandlers = this.routePathHandlers ?? {}
|
|
5
5
|
const me = this
|
|
6
6
|
|
|
7
|
-
await eachPlugins(async function (
|
|
7
|
+
await eachPlugins(async function () {
|
|
8
|
+
const { ns } = this
|
|
8
9
|
if (isEmpty(this.routePathHandlers) || !this.routePath) return undefined
|
|
9
10
|
for (const key of this.routePathHandlers) {
|
|
10
11
|
me.routePathHandlers[key] = { handler: this.routePath, ns }
|
package/lib/handle-download.js
CHANGED
|
@@ -2,9 +2,9 @@ import path from 'path'
|
|
|
2
2
|
|
|
3
3
|
async function handleDownload (input, req, reply) {
|
|
4
4
|
const { importPkg } = this.app.bajo
|
|
5
|
-
const { isFunction } = this.lib._
|
|
5
|
+
const { isFunction } = this.app.lib._
|
|
6
6
|
const mime = await importPkg('waibu:mime')
|
|
7
|
-
const { fs } = this.lib
|
|
7
|
+
const { fs } = this.app.lib
|
|
8
8
|
const file = isFunction(input) ? (await input.call(this, req)) : input
|
|
9
9
|
if (!fs.existsSync(file)) throw this.error('_notFound')
|
|
10
10
|
const mimeType = mime.getType(path.extname(file))
|
package/lib/handle-forward.js
CHANGED
package/lib/home.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
async function home () {
|
|
2
2
|
const { callHandler } = this.app.bajo
|
|
3
|
-
const { defaultsDeep } = this.lib.aneka
|
|
4
|
-
const { isString, pick } = this.lib._
|
|
3
|
+
const { defaultsDeep } = this.app.lib.aneka
|
|
4
|
+
const { isString, pick } = this.app.lib._
|
|
5
5
|
const config = this.getConfig()
|
|
6
6
|
if (config.home) {
|
|
7
7
|
if (isString(config.home)) config.home = { path: config.home }
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
function printRoutes () {
|
|
2
|
-
const { findIndex, orderBy, isArray } = this.lib._
|
|
2
|
+
const { findIndex, orderBy, isArray } = this.app.lib._
|
|
3
3
|
let items = []
|
|
4
4
|
this.routes.forEach(r => {
|
|
5
5
|
const idx = findIndex(items, { url: r.url })
|
|
@@ -10,9 +10,9 @@ function printRoutes () {
|
|
|
10
10
|
}
|
|
11
11
|
})
|
|
12
12
|
items = orderBy(items, ['url'])
|
|
13
|
-
this.log.
|
|
13
|
+
this.log.trace('loaded%s', this.t('routesL'))
|
|
14
14
|
items.forEach(item => {
|
|
15
|
-
this.log.
|
|
15
|
+
this.log.trace('- %s (%s)', item.url, item.methods.join('|'))
|
|
16
16
|
})
|
|
17
17
|
}
|
|
18
18
|
|