waibu 2.0.0 → 2.1.0
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 +568 -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/extend/bajo/hook/{on-request.js → waibu@on-request.js} +6 -6
- package/extend/bajo/hook/{on-response.js → waibu@on-response.js} +2 -2
- package/extend/bajo/hook/{on-route.js → waibu@on-route.js} +0 -0
- package/extend/bajo/intl/en-US.json +0 -0
- package/extend/bajo/intl/id.json +0 -0
- package/extend/bajoTemplate/layout/email.html +0 -0
- package/extend/bajoTemplate/layout/email.id.html +0 -0
- package/index.js +259 -55
- package/lib/app-hook.js +2 -2
- package/lib/app.js +8 -8
- package/lib/build-locals.js +22 -10
- package/lib/collect-route-path-handlers.js +2 -2
- package/lib/handle-download.js +2 -2
- package/lib/handle-forward.js +1 -1
- package/lib/handle-redirect.js +0 -0
- package/lib/handle-xml-body.js +0 -0
- 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/error-handler.js +0 -0
- 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 +2 -2
- package/lib/webapp-scope/merge-route-hooks.js +2 -2
- package/lib/webapp-scope/rerouted-path.js +0 -0
- package/lib/webapp-scope/route-hook.js +1 -1
- package/logo.png +0 -0
- package/package.json +24 -15
- package/wiki/CHANGES.md +16 -0
- 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/extend/bajo/hook/on-close.js +0 -5
- package/extend/bajo/hook/on-ready.js +0 -5
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,18 +12,72 @@ 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.baseClass.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 {TEscapeChars}
|
|
60
|
+
* @memberof Waibu
|
|
61
|
+
*/
|
|
62
|
+
static escapeChars = {
|
|
63
|
+
'<': '<',
|
|
64
|
+
'>': '>',
|
|
65
|
+
'"': '"',
|
|
66
|
+
"'": '''
|
|
67
|
+
}
|
|
68
|
+
|
|
19
69
|
constructor () {
|
|
20
70
|
super(pkgName, me.app)
|
|
21
|
-
|
|
22
|
-
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* @see {@tutorial config}
|
|
74
|
+
* @type {Object}
|
|
75
|
+
*/
|
|
23
76
|
this.config = {
|
|
77
|
+
home: undefined,
|
|
24
78
|
server: {
|
|
25
79
|
host: '127.0.0.1',
|
|
26
|
-
port:
|
|
80
|
+
port: 17845
|
|
27
81
|
},
|
|
28
82
|
factory: {
|
|
29
83
|
trustProxy: true,
|
|
@@ -46,7 +100,7 @@ async function factory (pkgName) {
|
|
|
46
100
|
lang: 'lang'
|
|
47
101
|
},
|
|
48
102
|
paramsCharMap: {},
|
|
49
|
-
|
|
103
|
+
printRoutes: true,
|
|
50
104
|
pageTitleFormat: '%s : %s',
|
|
51
105
|
siteInfo: {
|
|
52
106
|
title: 'My Website',
|
|
@@ -77,12 +131,6 @@ async function factory (pkgName) {
|
|
|
77
131
|
}
|
|
78
132
|
}
|
|
79
133
|
}
|
|
80
|
-
this.escapeChars = {
|
|
81
|
-
'<': '<',
|
|
82
|
-
'>': '>',
|
|
83
|
-
'"': '"',
|
|
84
|
-
"'": '''
|
|
85
|
-
}
|
|
86
134
|
this.qs = {
|
|
87
135
|
parse: (item) => {
|
|
88
136
|
return queryString.parse(item, {
|
|
@@ -94,17 +142,28 @@ async function factory (pkgName) {
|
|
|
94
142
|
stringify: queryString.stringify,
|
|
95
143
|
stringifyUrl: queryString.stringifyUrl
|
|
96
144
|
}
|
|
97
|
-
this.hookTypes = ['onRequest', 'onResponse', 'preParsing', 'preValidation', 'preHandler',
|
|
98
|
-
'preSerialization', 'onSend', 'onTimeout', 'onError']
|
|
99
145
|
}
|
|
100
146
|
|
|
147
|
+
/**
|
|
148
|
+
* Initialize plugin
|
|
149
|
+
*
|
|
150
|
+
* @method
|
|
151
|
+
* @async
|
|
152
|
+
*/
|
|
101
153
|
init = async () => {
|
|
102
154
|
if (this.config.home === '/') this.config.home = false
|
|
103
155
|
await collectRoutePathHandlers.call(this)
|
|
104
156
|
}
|
|
105
157
|
|
|
158
|
+
/**
|
|
159
|
+
* Start plugin
|
|
160
|
+
*
|
|
161
|
+
* @method
|
|
162
|
+
* @async
|
|
163
|
+
*/
|
|
106
164
|
start = async () => {
|
|
107
|
-
const {
|
|
165
|
+
const { runHook } = this.app.bajo
|
|
166
|
+
const { generateId } = this.app.lib.aneka
|
|
108
167
|
const cfg = this.getConfig()
|
|
109
168
|
if (this.app.bajoLogger) {
|
|
110
169
|
cfg.factory.loggerInstance = this.app.bajoLogger.instance.child(
|
|
@@ -132,21 +191,33 @@ async function factory (pkgName) {
|
|
|
132
191
|
await handleRedirect.call(this, instance)
|
|
133
192
|
await handleForward.call(this, instance)
|
|
134
193
|
await appHook.call(this)
|
|
135
|
-
await routeHook.call(this, this.
|
|
194
|
+
await routeHook.call(this, this.ns)
|
|
136
195
|
await boot.call(this)
|
|
137
196
|
await instance.listen(cfg.server)
|
|
138
|
-
if (cfg.
|
|
197
|
+
if (cfg.printRoutes) printRoutes.call(this)
|
|
139
198
|
}
|
|
140
199
|
|
|
200
|
+
/**
|
|
201
|
+
* Exit handler
|
|
202
|
+
*
|
|
203
|
+
* @method
|
|
204
|
+
* @async
|
|
205
|
+
*/
|
|
141
206
|
exit = async () => {
|
|
142
207
|
this.instance.close()
|
|
143
208
|
}
|
|
144
209
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
210
|
+
/**
|
|
211
|
+
* Find route by route name
|
|
212
|
+
*
|
|
213
|
+
* @param {string} name - ns based route name
|
|
214
|
+
* @returns {Object} Route object
|
|
215
|
+
*/
|
|
216
|
+
findRoute = (name) => {
|
|
217
|
+
const { outmatch } = this.app.lib
|
|
218
|
+
const { find } = this.app.lib._
|
|
148
219
|
const { breakNsPath } = this.app.bajo
|
|
149
|
-
let { ns, subNs = '', path } = breakNsPath(
|
|
220
|
+
let { ns, subNs = '', path } = breakNsPath(name)
|
|
150
221
|
const params = path.split('|')
|
|
151
222
|
if (params.length > 1) path = params[0]
|
|
152
223
|
return find(this.routes, r => {
|
|
@@ -158,15 +229,37 @@ async function factory (pkgName) {
|
|
|
158
229
|
})
|
|
159
230
|
}
|
|
160
231
|
|
|
232
|
+
get escapeChars () {
|
|
233
|
+
return this.constructor.escapeChars
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Escape text
|
|
238
|
+
*
|
|
239
|
+
* @method
|
|
240
|
+
* @param {string} text
|
|
241
|
+
* @returns {string}
|
|
242
|
+
*/
|
|
161
243
|
escape = (text = '') => {
|
|
162
244
|
if (typeof text !== 'string') return text
|
|
163
|
-
const { forOwn } = this.lib._
|
|
245
|
+
const { forOwn } = this.app.lib._
|
|
164
246
|
forOwn(this.escapeChars, (v, k) => {
|
|
165
247
|
text = text.replaceAll(k, v)
|
|
166
248
|
})
|
|
167
249
|
return text
|
|
168
250
|
}
|
|
169
251
|
|
|
252
|
+
/**
|
|
253
|
+
* Fetch something from url. A wrapper of bajo-extra's fetchUrl which support
|
|
254
|
+
* bajo's ns based url.
|
|
255
|
+
*
|
|
256
|
+
* @method
|
|
257
|
+
* @async
|
|
258
|
+
* @param {string} url - Also support ns based url
|
|
259
|
+
* @param {Object} [opts={}] - node's fetch options
|
|
260
|
+
* @param {Object} [extra={}] - See {@link https://ardhi.github.io/bajo-extra|bajo-extra}
|
|
261
|
+
* @returns {Object}
|
|
262
|
+
*/
|
|
170
263
|
fetch = async (url, opts = {}, extra = {}) => {
|
|
171
264
|
const { fetch } = this.app.bajoExtra
|
|
172
265
|
extra.rawResponse = true
|
|
@@ -183,22 +276,43 @@ async function factory (pkgName) {
|
|
|
183
276
|
return result
|
|
184
277
|
}
|
|
185
278
|
|
|
279
|
+
/**
|
|
280
|
+
* Get visitor IP from fastify's request object
|
|
281
|
+
*
|
|
282
|
+
* @method
|
|
283
|
+
* @param {Object} req - request object
|
|
284
|
+
* @returns {string}
|
|
285
|
+
*/
|
|
186
286
|
getIp = (req) => {
|
|
187
|
-
const { isEmpty } = this.lib._
|
|
287
|
+
const { isEmpty } = this.app.lib._
|
|
188
288
|
let fwd = req.headers['x-forwarded-for'] ?? ''
|
|
189
289
|
if (!Array.isArray(fwd)) fwd = fwd.split(',').map(ip => ip.trim())
|
|
190
290
|
return isEmpty(fwd[0]) ? req.ip : fwd[0]
|
|
191
291
|
}
|
|
192
292
|
|
|
293
|
+
/**
|
|
294
|
+
* Get origin of fastify's request object
|
|
295
|
+
*
|
|
296
|
+
* @method
|
|
297
|
+
* @param {Object} req
|
|
298
|
+
* @returns {string}
|
|
299
|
+
*/
|
|
193
300
|
getOrigin = (req) => {
|
|
194
|
-
const { isEmpty } = this.lib._
|
|
301
|
+
const { isEmpty } = this.app.lib._
|
|
195
302
|
let host = req.host
|
|
196
303
|
if (isEmpty(host) || host === ':authority') host = `${this.config.server.host}:${this.config.server.port}`
|
|
197
304
|
return `${req.protocol}://${host}`
|
|
198
305
|
}
|
|
199
306
|
|
|
307
|
+
/**
|
|
308
|
+
* Get plugin by prefix
|
|
309
|
+
*
|
|
310
|
+
* @method
|
|
311
|
+
* @param {string} prefix
|
|
312
|
+
* @returns {Object}
|
|
313
|
+
*/
|
|
200
314
|
getPluginByPrefix = (prefix) => {
|
|
201
|
-
const { get, find } = this.lib._
|
|
315
|
+
const { get, find } = this.app.lib._
|
|
202
316
|
const item = find(this.app.waibu.routes, r => {
|
|
203
317
|
return get(r, 'config.prefix') === prefix
|
|
204
318
|
})
|
|
@@ -206,19 +320,34 @@ async function factory (pkgName) {
|
|
|
206
320
|
if (ns) return this.app[ns]
|
|
207
321
|
}
|
|
208
322
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
323
|
+
/**
|
|
324
|
+
* Get plugin's prefix by name
|
|
325
|
+
*
|
|
326
|
+
* @method
|
|
327
|
+
* @param {string} name - Plugin's name
|
|
328
|
+
* @param {string} [webApp=waibuMpa] - Web app to use
|
|
329
|
+
* @returns {string}
|
|
330
|
+
*/
|
|
331
|
+
getPluginPrefix = (name, webApp = 'waibuMpa') => {
|
|
332
|
+
const { get, trim } = this.app.lib._
|
|
333
|
+
let prefix = get(this, `app.${name}.config.${webApp}.prefix`, get(this, `app.${name}.config.waibu.prefix`, this.app[name].alias))
|
|
334
|
+
if (name === 'main') {
|
|
213
335
|
const cfg = this.app[webApp].config
|
|
214
336
|
if (cfg.mountMainAsRoot) prefix = ''
|
|
215
337
|
}
|
|
216
|
-
|
|
217
338
|
return trim(prefix, '/')
|
|
218
339
|
}
|
|
219
340
|
|
|
220
|
-
|
|
221
|
-
|
|
341
|
+
/**
|
|
342
|
+
* Get all available routes
|
|
343
|
+
*
|
|
344
|
+
* @method
|
|
345
|
+
* @param {boolean} [grouped=false] - Returns as groups of urls and methods
|
|
346
|
+
* @param {*} [lite=false] - Retuns only urls and methods
|
|
347
|
+
* @returns {Array}
|
|
348
|
+
*/
|
|
349
|
+
getRoutes = (grouped = false, lite = false) => {
|
|
350
|
+
const { groupBy, orderBy, mapValues, map, pick } = this.app.lib._
|
|
222
351
|
const all = this.routes
|
|
223
352
|
let routes
|
|
224
353
|
if (grouped) {
|
|
@@ -229,18 +358,35 @@ async function factory (pkgName) {
|
|
|
229
358
|
return routes
|
|
230
359
|
}
|
|
231
360
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
361
|
+
/**
|
|
362
|
+
* Get uploaded files by request ID
|
|
363
|
+
*
|
|
364
|
+
* @method
|
|
365
|
+
* @param {string} reqId - Request ID
|
|
366
|
+
* @param {boolean} [fileUrl=false] - If ```true```, files returned as file url format (```file:///...```)
|
|
367
|
+
* @param {*} returnDir - If ```true```, also return its directory
|
|
368
|
+
* @returns {(Object|Array)} - Returns object if ```returnDir``` is ```true```, array of files otherwise
|
|
369
|
+
*/
|
|
370
|
+
getUploadedFiles = async (reqId, fileUrl = false, returnDir = false) => {
|
|
371
|
+
const { getPluginDataDir } = this.app.bajo
|
|
372
|
+
const { resolvePath } = this.app.lib.aneka
|
|
373
|
+
const { fastGlob } = this.app.lib
|
|
374
|
+
const dir = `${getPluginDataDir(this.ns)}/upload/${reqId}`
|
|
236
375
|
const result = await fastGlob(`${dir}/*`)
|
|
237
376
|
if (!fileUrl) return returnDir ? { dir, files: result } : result
|
|
238
377
|
const files = result.map(f => resolvePath(f, true))
|
|
239
378
|
return returnDir ? { dir, files } : files
|
|
240
379
|
}
|
|
241
380
|
|
|
381
|
+
/**
|
|
382
|
+
* Is namespace's path contains language detector token?
|
|
383
|
+
*
|
|
384
|
+
* @method
|
|
385
|
+
* @param {string} ns - Plugin name
|
|
386
|
+
* @returns {boolean}
|
|
387
|
+
*/
|
|
242
388
|
isIntlPath = (ns) => {
|
|
243
|
-
const { get } = this.lib._
|
|
389
|
+
const { get } = this.app.lib._
|
|
244
390
|
return get(this.app[ns], 'config.intl.detectors', []).includes('path')
|
|
245
391
|
}
|
|
246
392
|
|
|
@@ -248,6 +394,13 @@ async function factory (pkgName) {
|
|
|
248
394
|
throw this.error('_notFound', { path: name })
|
|
249
395
|
}
|
|
250
396
|
|
|
397
|
+
/**
|
|
398
|
+
* Parse filter found from Fastify's request based on keys set in config object
|
|
399
|
+
*
|
|
400
|
+
* @method
|
|
401
|
+
* @param {Object} req - Request object
|
|
402
|
+
* @returns {Object}
|
|
403
|
+
*/
|
|
251
404
|
parseFilter = (req) => {
|
|
252
405
|
const result = {}
|
|
253
406
|
const items = Object.keys(this.config.qsKey)
|
|
@@ -257,26 +410,45 @@ async function factory (pkgName) {
|
|
|
257
410
|
return result
|
|
258
411
|
}
|
|
259
412
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
413
|
+
/**
|
|
414
|
+
* Get route directory by plugin's name
|
|
415
|
+
*
|
|
416
|
+
* @param {*} ns - Namespace
|
|
417
|
+
* @param {*} [baseNs] - Base namespace. If not provided, defaults to scope's ns
|
|
418
|
+
* @returns {string}
|
|
419
|
+
*/
|
|
420
|
+
routeDir = (ns, baseNs) => {
|
|
421
|
+
const { get } = this.app.lib._
|
|
422
|
+
if (!baseNs) baseNs = ns
|
|
423
|
+
const cfg = this.app[baseNs].config
|
|
424
|
+
const prefix = get(cfg, 'waibu.prefix', this.app[baseNs].alias)
|
|
265
425
|
const dir = prefix === '' ? '' : `/${prefix}`
|
|
266
|
-
if (!ns) return dir
|
|
267
426
|
const cfgMpa = get(this, 'app.waibuMpa.config')
|
|
268
|
-
if (ns === this.app.
|
|
269
|
-
if (ns ===
|
|
427
|
+
if (ns === this.app.mainNs && cfgMpa.mountMainAsRoot) return ''
|
|
428
|
+
if (ns === baseNs) return dir
|
|
270
429
|
return dir + `/${get(this.app[ns].config, 'waibu.prefix', this.app[ns].alias)}`
|
|
271
430
|
}
|
|
272
431
|
|
|
273
|
-
|
|
432
|
+
/**
|
|
433
|
+
* Get route path by route's name:
|
|
434
|
+
* - If it is a ```mailto:``` or ```tel:``` url, it returns as is
|
|
435
|
+
* - If it is a ns based name, it will be parsed first
|
|
436
|
+
*
|
|
437
|
+
* @method
|
|
438
|
+
* @param {string} name
|
|
439
|
+
* @param {Object} [options={}] - Options object
|
|
440
|
+
* @param {string} [options.base=waibu] - Base namespace
|
|
441
|
+
* @param {boolean} [options.guessHost] - If true, guest host if host is not set
|
|
442
|
+
* @param {Object} [options.query={}] - Query string's object. If provided, it will be added to returned value
|
|
443
|
+
* @param {Object} [options.params={}] - Parameter object. If provided, it will be merged to returned value
|
|
444
|
+
* @returns {string}
|
|
445
|
+
*/
|
|
446
|
+
routePath = (name, options = {}) => {
|
|
274
447
|
const { getPlugin } = this.app.bajo
|
|
275
|
-
const { defaultsDeep } = this.lib.aneka
|
|
276
|
-
const { isEmpty, get, trimEnd, trimStart } = this.lib._
|
|
448
|
+
const { defaultsDeep } = this.app.lib.aneka
|
|
449
|
+
const { isEmpty, get, trimEnd, trimStart } = this.app.lib._
|
|
277
450
|
const { breakNsPath } = this.app.bajo
|
|
278
|
-
const { query = {}, base =
|
|
279
|
-
if (isEmpty(name)) return name
|
|
451
|
+
const { query = {}, base = this.ns, params = {}, guessHost } = options
|
|
280
452
|
|
|
281
453
|
const plugin = getPlugin(base)
|
|
282
454
|
const cfg = plugin.config ?? {}
|
|
@@ -304,11 +476,23 @@ async function factory (pkgName) {
|
|
|
304
476
|
return url
|
|
305
477
|
}
|
|
306
478
|
|
|
479
|
+
/**
|
|
480
|
+
* Method to send mail through Masohi Messaging System. It is a thin wrapper
|
|
481
|
+
* for {@link https://github.com/ardhi/masohi-mail|masohi-mail} send method.
|
|
482
|
+
*
|
|
483
|
+
* If masohi is not loaded, nothing is delivered.
|
|
484
|
+
*
|
|
485
|
+
* @method
|
|
486
|
+
* @async
|
|
487
|
+
* @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
|
|
488
|
+
* @param {Object} [params={}] - {@link https://github.com/ardhi/masohi-mail|masohi-mail}'s params object.
|
|
489
|
+
* @returns
|
|
490
|
+
*/
|
|
307
491
|
sendMail = async (tpl, { to, cc, bcc, from, subject, data = {}, conn, source, options = {} }) => {
|
|
308
492
|
conn = conn ?? 'masohiMail:default'
|
|
309
493
|
if (!this.app.masohi || !this.app.masohiMail) return
|
|
310
|
-
const { get, isString } = this.lib._
|
|
311
|
-
const { generateId } = this.app.
|
|
494
|
+
const { get, isString } = this.app.lib._
|
|
495
|
+
const { generateId } = this.app.lib.aneka
|
|
312
496
|
const { render } = this.app.bajoTemplate
|
|
313
497
|
if (isString(tpl)) tpl = [tpl]
|
|
314
498
|
const locals = await buildLocals.call(this, { tpl, params: data, opts: options })
|
|
@@ -319,11 +503,22 @@ async function factory (pkgName) {
|
|
|
319
503
|
const message = await render(tpl[0], locals, opts)
|
|
320
504
|
if (tpl[1]) opts.messageText = await render(tpl[1], locals, opts)
|
|
321
505
|
const payload = { type: 'object', data: { to, cc, bcc, from, subject, message, options: opts } }
|
|
322
|
-
await this.app.masohi.send({ payload, source: source ?? this.
|
|
506
|
+
await this.app.masohi.send({ payload, source: source ?? this.ns, conn }) // mail sent through worker
|
|
323
507
|
}
|
|
324
508
|
|
|
509
|
+
/**
|
|
510
|
+
* Recursively unescape block of texts
|
|
511
|
+
*
|
|
512
|
+
* @method
|
|
513
|
+
* @param {string} content - Source content
|
|
514
|
+
* @param {string} start - Block's start
|
|
515
|
+
* @param {string} end - Block's end
|
|
516
|
+
* @param {string} startReplacer - Token to use as block's start replacer
|
|
517
|
+
* @param {string} endReplacer - Token to use as block's end replacer
|
|
518
|
+
* @returns {string}
|
|
519
|
+
*/
|
|
325
520
|
unescapeBlock = (content, start, end, startReplacer, endReplacer) => {
|
|
326
|
-
const { extractText } = this.lib.aneka
|
|
521
|
+
const { extractText } = this.app.lib.aneka
|
|
327
522
|
const { result } = extractText(content, start, end)
|
|
328
523
|
if (result.length === 0) return content
|
|
329
524
|
const unescaped = this.unescape(result)
|
|
@@ -333,8 +528,15 @@ async function factory (pkgName) {
|
|
|
333
528
|
return this.unescapeBlock(block, start, end, startReplacer, endReplacer)
|
|
334
529
|
}
|
|
335
530
|
|
|
531
|
+
/**
|
|
532
|
+
* Unescape text using {@link TEscapeChars} rules
|
|
533
|
+
*
|
|
534
|
+
* @method
|
|
535
|
+
* @param {string} text - Text to unescape
|
|
536
|
+
* @returns {string}
|
|
537
|
+
*/
|
|
336
538
|
unescape = (text) => {
|
|
337
|
-
const { forOwn, invert } = this.lib._
|
|
539
|
+
const { forOwn, invert } = this.app.lib._
|
|
338
540
|
const mapping = invert(this.escapeChars)
|
|
339
541
|
forOwn(mapping, (v, k) => {
|
|
340
542
|
text = text.replaceAll(k, v)
|
|
@@ -342,6 +544,8 @@ async function factory (pkgName) {
|
|
|
342
544
|
return text
|
|
343
545
|
}
|
|
344
546
|
}
|
|
547
|
+
|
|
548
|
+
return Waibu
|
|
345
549
|
}
|
|
346
550
|
|
|
347
551
|
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,12 +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
9
|
await eachPlugins(async function ({ file }) {
|
|
10
|
-
const {
|
|
10
|
+
const { ns, alias, config } = this
|
|
11
11
|
const mod = await importModule(file, { asHandler: true })
|
|
12
12
|
mod.prefix = get(config, 'waibu.prefix', alias)
|
|
13
13
|
if (get(config, 'intl.detectors', []).includes('path')) mod.prefix = `/:lang${mod.prefix}`
|
|
@@ -27,23 +27,23 @@ export async function collect (glob = 'boot.js', baseNs) {
|
|
|
27
27
|
export async function boot () {
|
|
28
28
|
const { runHook } = this.app.bajo
|
|
29
29
|
const mods = await collect.call(this)
|
|
30
|
-
await runHook(`${this.
|
|
30
|
+
await runHook(`${this.ns}:beforeAppBoot`)
|
|
31
31
|
for (const m of mods) {
|
|
32
32
|
const disabled = this.app[m.ns].config.disabled
|
|
33
33
|
if (Array.isArray(disabled) && disabled.length === 1 && ['*', 'all'].includes(disabled[0])) {
|
|
34
34
|
this.log.warn('allRoutesConfigDisabled%s', m.ns)
|
|
35
35
|
continue
|
|
36
36
|
}
|
|
37
|
-
await runHook(`${this.
|
|
37
|
+
await runHook(`${this.ns}.${m.ns}:beforeAppBoot`)
|
|
38
38
|
this.log.debug('bootApp%s', m.ns)
|
|
39
39
|
await this.instance.register(async (ctx) => {
|
|
40
40
|
const plugin = this.app[m.ns]
|
|
41
41
|
plugin.instance = ctx
|
|
42
|
-
await runHook(`${plugin.
|
|
42
|
+
await runHook(`${plugin.ns}:afterCreateContext`, ctx)
|
|
43
43
|
await m.handler.call(plugin, ctx, m.prefix)
|
|
44
44
|
}, { prefix: m.prefix })
|
|
45
|
-
await runHook(`${this.
|
|
45
|
+
await runHook(`${this.ns}.${m.ns}:afterAppBoot`)
|
|
46
46
|
}
|
|
47
|
-
await runHook(`${this.
|
|
47
|
+
await runHook(`${this.ns}:afterAppBoot`)
|
|
48
48
|
await home.call(this)
|
|
49
49
|
}
|
package/lib/build-locals.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
const errs = {}
|
|
2
|
+
|
|
1
3
|
async function buildHomesMenu (req) {
|
|
2
4
|
const { callHandler } = this.app.bajo
|
|
3
|
-
const { get, find, orderBy } = this.lib._
|
|
5
|
+
const { get, find, orderBy } = this.app.lib._
|
|
4
6
|
const routes = []
|
|
5
|
-
for (const ns of this.app.
|
|
7
|
+
for (const ns of this.app.getAllNs()) {
|
|
6
8
|
let home = get(this, `app.${ns}.config.waibuMpa.home`)
|
|
7
9
|
const homeHandler = get(this, `app.${ns}.config.waibuMpa.homeHandler`)
|
|
8
10
|
if (homeHandler) home = await callHandler(this.app[ns], homeHandler)
|
|
@@ -14,15 +16,15 @@ async function buildHomesMenu (req) {
|
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
async function buildPagesMenu (req) {
|
|
17
|
-
const { find, orderBy, merge, isString } = this.lib._
|
|
19
|
+
const { find, orderBy, merge, isString } = this.app.lib._
|
|
18
20
|
const { callHandler, runHook } = this.app.bajo
|
|
19
21
|
const all = [{ icon: 'house', href: '/', level: 1 }]
|
|
20
|
-
for (const ns of this.app.
|
|
22
|
+
for (const ns of this.app.getAllNs()) {
|
|
21
23
|
const items = []
|
|
22
24
|
let pages = this.app[ns].getConfig('waibuMpa.menuHandler', { defValue: [] })
|
|
23
25
|
if (isString(pages)) pages = await callHandler(this.app[ns], pages, req)
|
|
24
26
|
if (pages.length === 0) continue
|
|
25
|
-
await runHook(`${this.
|
|
27
|
+
await runHook(`${this.ns}.${ns}:afterBuildPagesMenu`, pages, req)
|
|
26
28
|
for (const page of pages) {
|
|
27
29
|
const existing = find(all, { title: page.title })
|
|
28
30
|
page.level = page.level ?? 1000
|
|
@@ -49,13 +51,15 @@ async function buildPagesMenu (req) {
|
|
|
49
51
|
}
|
|
50
52
|
return orderBy(all, ['level', 'title'])
|
|
51
53
|
}
|
|
54
|
+
|
|
52
55
|
async function buildLocals ({ tpl, params = {}, opts = {} } = {}) {
|
|
53
56
|
const { runHook } = this.app.bajo
|
|
54
|
-
const { set, merge, pick, get, isEmpty, find } = this.lib._
|
|
57
|
+
const { set, merge, pick, get, isEmpty, find, pullAt } = this.app.lib._
|
|
55
58
|
const { req, reply } = opts
|
|
56
59
|
|
|
57
60
|
const appTitle = this.app.waibuMpa ? req.t(this.app.waibuMpa.getAppTitle(req.ns)) : ''
|
|
58
61
|
params.page = merge(params.page ?? {}, { ns: req.ns, appTitle })
|
|
62
|
+
params.sidebar = params.sidebar ?? []
|
|
59
63
|
|
|
60
64
|
const { site, user, lang, darkMode } = req
|
|
61
65
|
const theme = pick(find(this.themes, { name: req.theme }) ?? {}, ['name', 'framework'])
|
|
@@ -72,6 +76,11 @@ async function buildLocals ({ tpl, params = {}, opts = {} } = {}) {
|
|
|
72
76
|
_meta.hostHeader = req.headers.host
|
|
73
77
|
_meta.statusCode = 200
|
|
74
78
|
_meta.isAdmin = _meta.user && find(_meta.user.teams, { alias: 'administrator' })
|
|
79
|
+
const pulled = []
|
|
80
|
+
for (const k in errs) {
|
|
81
|
+
if (Date.now() - errs[k] > 5000) pulled.push(k)
|
|
82
|
+
}
|
|
83
|
+
pullAt(errs, pulled)
|
|
75
84
|
if (params.error) {
|
|
76
85
|
if (params.error.statusCode) _meta.statusCode = params.error.statusCode
|
|
77
86
|
_meta.errorMessage = params.error.message
|
|
@@ -79,15 +88,18 @@ async function buildLocals ({ tpl, params = {}, opts = {} } = {}) {
|
|
|
79
88
|
params.page.ns = params.error.ns
|
|
80
89
|
params.page.appTitle = this.app.waibuMpa.getAppTitle(params.error.ns)
|
|
81
90
|
}
|
|
82
|
-
|
|
83
|
-
|
|
91
|
+
if (!errs[req.id]) {
|
|
92
|
+
this.log.error('error%s', params.error.message)
|
|
93
|
+
if (this.app.bajo.config.env === 'dev') console.log(params.error)
|
|
94
|
+
}
|
|
95
|
+
errs[req.id] = Date.now()
|
|
84
96
|
}
|
|
85
97
|
if (reply && req.session && req.flash && !opts.partial) _meta.flash = reply.flash()
|
|
86
98
|
set(params, 'menu.homes', await buildHomesMenu.call(this, req))
|
|
87
99
|
set(params, 'menu.pages', await buildPagesMenu.call(this, req))
|
|
88
100
|
const merged = merge({}, params, { _meta })
|
|
89
|
-
await runHook(`${this.
|
|
90
|
-
if (!isEmpty(routeOpts.ns)) await runHook(`${this.
|
|
101
|
+
await runHook(`${this.ns}:afterBuildLocals`, merged, req)
|
|
102
|
+
if (!isEmpty(routeOpts.ns)) await runHook(`${this.ns}.${routeOpts.ns}:afterBuildLocals`, merged, req)
|
|
91
103
|
return merged
|
|
92
104
|
}
|
|
93
105
|
|
|
@@ -1,11 +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
7
|
await eachPlugins(async function () {
|
|
8
|
-
const {
|
|
8
|
+
const { ns } = this
|
|
9
9
|
if (isEmpty(this.routePathHandlers) || !this.routePath) return undefined
|
|
10
10
|
for (const key of this.routePathHandlers) {
|
|
11
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/handle-redirect.js
CHANGED
|
File without changes
|
package/lib/handle-xml-body.js
CHANGED
|
File without changes
|