waibu 2.0.0 → 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/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/index.js +267 -51
- package/lib/app-hook.js +2 -2
- package/lib/app.js +8 -8
- package/lib/build-locals.js +8 -8
- 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/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 +2 -2
- 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/extend/bajo/hook/on-close.js +0 -5
- package/extend/bajo/hook/on-ready.js +0 -5
- /package/extend/bajo/hook/{on-route.js → waibu@on-route.js} +0 -0
|
@@ -0,0 +1,566 @@
|
|
|
1
|
+
<!DOCTYPE html><html lang="en" style="font-size:16px"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Source: index.js</title><!--[if lt IE 9]>
|
|
2
|
+
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
|
3
|
+
<![endif]--><script src="scripts/third-party/hljs.js" defer="defer"></script><script src="scripts/third-party/hljs-line-num.js" defer="defer"></script><script src="scripts/third-party/popper.js" defer="defer"></script><script src="scripts/third-party/tippy.js" defer="defer"></script><script src="scripts/third-party/tocbot.min.js"></script><script>var baseURL="/",locationPathname="";baseURL=(locationPathname=document.location.pathname).substr(0,locationPathname.lastIndexOf("/")+1)</script><link rel="stylesheet" href="styles/clean-jsdoc-theme.min.css"><svg aria-hidden="true" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="display:none"><defs><symbol id="copy-icon" viewbox="0 0 488.3 488.3"><g><path d="M314.25,85.4h-227c-21.3,0-38.6,17.3-38.6,38.6v325.7c0,21.3,17.3,38.6,38.6,38.6h227c21.3,0,38.6-17.3,38.6-38.6V124 C352.75,102.7,335.45,85.4,314.25,85.4z M325.75,449.6c0,6.4-5.2,11.6-11.6,11.6h-227c-6.4,0-11.6-5.2-11.6-11.6V124 c0-6.4,5.2-11.6,11.6-11.6h227c6.4,0,11.6,5.2,11.6,11.6V449.6z"/><path d="M401.05,0h-227c-21.3,0-38.6,17.3-38.6,38.6c0,7.5,6,13.5,13.5,13.5s13.5-6,13.5-13.5c0-6.4,5.2-11.6,11.6-11.6h227 c6.4,0,11.6,5.2,11.6,11.6v325.7c0,6.4-5.2,11.6-11.6,11.6c-7.5,0-13.5,6-13.5,13.5s6,13.5,13.5,13.5c21.3,0,38.6-17.3,38.6-38.6 V38.6C439.65,17.3,422.35,0,401.05,0z"/></g></symbol><symbol id="search-icon" viewBox="0 0 512 512"><g><g><path d="M225.474,0C101.151,0,0,101.151,0,225.474c0,124.33,101.151,225.474,225.474,225.474 c124.33,0,225.474-101.144,225.474-225.474C450.948,101.151,349.804,0,225.474,0z M225.474,409.323 c-101.373,0-183.848-82.475-183.848-183.848S124.101,41.626,225.474,41.626s183.848,82.475,183.848,183.848 S326.847,409.323,225.474,409.323z"/></g></g><g><g><path d="M505.902,476.472L386.574,357.144c-8.131-8.131-21.299-8.131-29.43,0c-8.131,8.124-8.131,21.306,0,29.43l119.328,119.328 c4.065,4.065,9.387,6.098,14.715,6.098c5.321,0,10.649-2.033,14.715-6.098C514.033,497.778,514.033,484.596,505.902,476.472z"/></g></g></symbol><symbol id="font-size-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M11.246 15H4.754l-2 5H.6L7 4h2l6.4 16h-2.154l-2-5zm-.8-2L8 6.885 5.554 13h4.892zM21 12.535V12h2v8h-2v-.535a4 4 0 1 1 0-6.93zM19 18a2 2 0 1 0 0-4 2 2 0 0 0 0 4z"/></symbol><symbol id="add-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M11 11V5h2v6h6v2h-6v6h-2v-6H5v-2z"/></symbol><symbol id="minus-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M5 11h14v2H5z"/></symbol><symbol id="dark-theme-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M10 7a7 7 0 0 0 12 4.9v.1c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2h.1A6.979 6.979 0 0 0 10 7zm-6 5a8 8 0 0 0 15.062 3.762A9 9 0 0 1 8.238 4.938 7.999 7.999 0 0 0 4 12z"/></symbol><symbol id="light-theme-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M12 18a6 6 0 1 1 0-12 6 6 0 0 1 0 12zm0-2a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM11 1h2v3h-2V1zm0 19h2v3h-2v-3zM3.515 4.929l1.414-1.414L7.05 5.636 5.636 7.05 3.515 4.93zM16.95 18.364l1.414-1.414 2.121 2.121-1.414 1.414-2.121-2.121zm2.121-14.85l1.414 1.415-2.121 2.121-1.414-1.414 2.121-2.121zM5.636 16.95l1.414 1.414-2.121 2.121-1.414-1.414 2.121-2.121zM23 11v2h-3v-2h3zM4 11v2H1v-2h3z"/></symbol><symbol id="reset-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M18.537 19.567A9.961 9.961 0 0 1 12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10c0 2.136-.67 4.116-1.81 5.74L17 12h3a8 8 0 1 0-2.46 5.772l.997 1.795z"/></symbol><symbol id="down-icon" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M12.7803 6.21967C13.0732 6.51256 13.0732 6.98744 12.7803 7.28033L8.53033 11.5303C8.23744 11.8232 7.76256 11.8232 7.46967 11.5303L3.21967 7.28033C2.92678 6.98744 2.92678 6.51256 3.21967 6.21967C3.51256 5.92678 3.98744 5.92678 4.28033 6.21967L8 9.93934L11.7197 6.21967C12.0126 5.92678 12.4874 5.92678 12.7803 6.21967Z"></path></symbol><symbol id="codepen-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M16.5 13.202L13 15.535v3.596L19.197 15 16.5 13.202zM14.697 12L12 10.202 9.303 12 12 13.798 14.697 12zM20 10.869L18.303 12 20 13.131V10.87zM19.197 9L13 4.869v3.596l3.5 2.333L19.197 9zM7.5 10.798L11 8.465V4.869L4.803 9 7.5 10.798zM4.803 15L11 19.131v-3.596l-3.5-2.333L4.803 15zM4 13.131L5.697 12 4 10.869v2.262zM2 9a1 1 0 0 1 .445-.832l9-6a1 1 0 0 1 1.11 0l9 6A1 1 0 0 1 22 9v6a1 1 0 0 1-.445.832l-9 6a1 1 0 0 1-1.11 0l-9-6A1 1 0 0 1 2 15V9z"/></symbol><symbol id="close-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M12 10.586l4.95-4.95 1.414 1.414-4.95 4.95 4.95 4.95-1.414 1.414-4.95-4.95-4.95 4.95-1.414-1.414 4.95-4.95-4.95-4.95L7.05 5.636z"/></symbol><symbol id="menu-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M3 4h18v2H3V4zm0 7h18v2H3v-2zm0 7h18v2H3v-2z"/></symbol></defs></svg></head><body data-theme="light"><div class="sidebar-container"><div class="sidebar" id="sidebar"><a href="/" class="sidebar-title sidebar-title-anchor">Waibu API</a><div class="sidebar-items-container"><div class="sidebar-section-title with-arrow" data-isopen="false" id="sidebar-classes"><div>Classes</div><svg><use xlink:href="#down-icon"></use></svg></div><div class="sidebar-section-children-container"><div class="sidebar-section-children"><a href="Waibu.html">Waibu</a></div></div><div class="sidebar-section-title with-arrow" data-isopen="false" id="sidebar-global"><div>Global</div><svg><use xlink:href="#down-icon"></use></svg></div><div class="sidebar-section-children-container"><div class="sidebar-section-children"><a href="global.html#factory">factory</a></div></div></div></div></div><div class="navbar-container" id="VuAckcnZhf"><nav class="navbar"><div class="navbar-left-items"><div class="navbar-item"><a id="" href="https://www.npmjs.com/package/waibu" target="">NPM</a></div><div class="navbar-item"><a id="" href="https://github.com/ardhi/waibu" target="">Github</a></div><div class="navbar-item"><a id="" href="https://waibu.bajo.app/" target="">Waibu</a></div><div class="navbar-item"><a id="" href="https://bajo.app/" target="">Bajo</a></div></div><div class="navbar-right-items"><div class="navbar-right-item"><button class="icon-button search-button" aria-label="open-search"><svg><use xlink:href="#search-icon"></use></svg></button></div><div class="navbar-right-item"><button class="icon-button theme-toggle" aria-label="toggle-theme"><svg><use class="theme-svg-use" xlink:href="#dark-theme-icon"></use></svg></button></div><div class="navbar-right-item"><button class="icon-button font-size" aria-label="change-font-size"><svg><use xlink:href="#font-size-icon"></use></svg></button></div></div><nav></nav></nav></div><div class="toc-container"><div class="toc-content"><span class="bold">On this page</span><div id="eed4d2a0bfd64539bb9df78095dec881"></div></div></div><div class="body-wrapper"><div class="main-content"><div class="main-wrapper"><section id="source-page" class="source-page"><header><h1 id="title" class="has-anchor">index.js</h1></header><article><pre class="prettyprint source lang-js"><code>import collectRoutePathHandlers from './lib/collect-route-path-handlers.js'
|
|
4
|
+
import fastify from 'fastify'
|
|
5
|
+
import appHook from './lib/app-hook.js'
|
|
6
|
+
import routeHook from './lib/webapp-scope/route-hook.js'
|
|
7
|
+
import printRoutes from './lib/print-routes.js'
|
|
8
|
+
import { boot } from './lib/app.js'
|
|
9
|
+
import sensible from '@fastify/sensible'
|
|
10
|
+
import noIcon from 'fastify-no-icon'
|
|
11
|
+
import underPressure from '@fastify/under-pressure'
|
|
12
|
+
import handleForward from './lib/handle-forward.js'
|
|
13
|
+
import handleRedirect from './lib/handle-redirect.js'
|
|
14
|
+
import buildLocals from './lib/build-locals.js'
|
|
15
|
+
import queryString from 'query-string'
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @typedef TEscapeChars
|
|
19
|
+
* @type {Object}
|
|
20
|
+
* @memberof Waibu
|
|
21
|
+
* @property {string} &lt;=&lt;
|
|
22
|
+
* @property {string} &gt;=&gt;
|
|
23
|
+
* @property {string} &quot;=&quot;
|
|
24
|
+
* @property {string} &apos;=&apos;
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Plugin factory
|
|
29
|
+
*
|
|
30
|
+
* @param {string} pkgName - NPM package name
|
|
31
|
+
* @returns {class}
|
|
32
|
+
*/
|
|
33
|
+
async function factory (pkgName) {
|
|
34
|
+
const me = this
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Waibu Web Framework plugin for Bajo. This is the main foundation of all web apps attached to
|
|
38
|
+
* the system through a route prefix. Those web apps are then build as childrens with
|
|
39
|
+
* its own fastify's context.
|
|
40
|
+
*
|
|
41
|
+
* There are currently 3 web apps available:
|
|
42
|
+
* - {@link https://github.com/ardhi/waibu-static|waibu-static} for static content delivery
|
|
43
|
+
* - {@link https://github.com/ardhi/waibu-rest-api|waibu-rest-api} for rest api setup
|
|
44
|
+
* - and {@link https://github.com/ardhi/waibu-mpa|waibu-mpa} for normal multi-page application
|
|
45
|
+
*
|
|
46
|
+
* You should write your code as the extension of above web apps. Not to this main app.
|
|
47
|
+
* Unless, of course, if you want to write custom web apps with its own context.
|
|
48
|
+
*
|
|
49
|
+
* @class
|
|
50
|
+
*/
|
|
51
|
+
class Waibu extends this.app.pluginClass.base {
|
|
52
|
+
/**
|
|
53
|
+
* @constant {string[]}
|
|
54
|
+
* @default ['onRequest', 'onResponse', 'preParsing', 'preValidation', 'preHandler', 'preSerialization', 'onSend', 'onTimeout', 'onError']
|
|
55
|
+
* @memberof Waibu
|
|
56
|
+
*/
|
|
57
|
+
static hookTypes = ['onRequest', 'onResponse', 'preParsing', 'preValidation', 'preHandler',
|
|
58
|
+
'preSerialization', 'onSend', 'onTimeout', 'onError']
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* @constant {string}
|
|
62
|
+
* @memberof Waibu
|
|
63
|
+
* @default 'w'
|
|
64
|
+
*/
|
|
65
|
+
static alias = 'w'
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* @constant {string[]}
|
|
69
|
+
* @default ['bajo-extra']
|
|
70
|
+
* @memberof Waibu
|
|
71
|
+
*/
|
|
72
|
+
static dependencies = ['bajo-extra']
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* @constant {TEscapeChars}
|
|
76
|
+
* @memberof Waibu
|
|
77
|
+
*/
|
|
78
|
+
static escapeChars = {
|
|
79
|
+
'<': '&lt;',
|
|
80
|
+
'>': '&gt;',
|
|
81
|
+
'"': '&quot;',
|
|
82
|
+
"'": '&apos;'
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
constructor () {
|
|
86
|
+
super(pkgName, me.app)
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* @see {@tutorial config}
|
|
90
|
+
* @type {Object}
|
|
91
|
+
*/
|
|
92
|
+
this.config = {
|
|
93
|
+
home: undefined,
|
|
94
|
+
server: {
|
|
95
|
+
host: '127.0.0.1',
|
|
96
|
+
port: 7771
|
|
97
|
+
},
|
|
98
|
+
factory: {
|
|
99
|
+
trustProxy: true,
|
|
100
|
+
bodyLimit: 10485760,
|
|
101
|
+
pluginTimeout: 30000
|
|
102
|
+
},
|
|
103
|
+
deferLog: false,
|
|
104
|
+
prefixVirtual: '~',
|
|
105
|
+
qsKey: {
|
|
106
|
+
bbox: 'bbox',
|
|
107
|
+
bboxLatField: 'bboxLatField',
|
|
108
|
+
bboxLngField: 'bboxLngField',
|
|
109
|
+
query: 'query',
|
|
110
|
+
match: 'match',
|
|
111
|
+
skip: 'skip',
|
|
112
|
+
page: 'page',
|
|
113
|
+
limit: 'limit',
|
|
114
|
+
sort: 'sort',
|
|
115
|
+
fields: 'fields',
|
|
116
|
+
lang: 'lang'
|
|
117
|
+
},
|
|
118
|
+
paramsCharMap: {},
|
|
119
|
+
printRoutes: true,
|
|
120
|
+
pageTitleFormat: '%s : %s',
|
|
121
|
+
siteInfo: {
|
|
122
|
+
title: 'My Website',
|
|
123
|
+
orgName: 'My Organization'
|
|
124
|
+
},
|
|
125
|
+
cors: {},
|
|
126
|
+
compress: {},
|
|
127
|
+
helmet: {},
|
|
128
|
+
rateLimit: {},
|
|
129
|
+
multipart: {
|
|
130
|
+
attachFieldsToBody: true,
|
|
131
|
+
limits: {
|
|
132
|
+
parts: 100,
|
|
133
|
+
fileSize: 10485760
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
noIcon: true,
|
|
137
|
+
underPressure: false,
|
|
138
|
+
forwardOpts: {
|
|
139
|
+
disableRequestLogging: true,
|
|
140
|
+
undici: {
|
|
141
|
+
connections: 128,
|
|
142
|
+
pipelining: 1,
|
|
143
|
+
keepAliveTimeout: 60 * 1000,
|
|
144
|
+
tls: {
|
|
145
|
+
rejectUnauthorized: false
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
this.qs = {
|
|
151
|
+
parse: (item) => {
|
|
152
|
+
return queryString.parse(item, {
|
|
153
|
+
parseBooleans: true,
|
|
154
|
+
parseNumbers: true
|
|
155
|
+
})
|
|
156
|
+
},
|
|
157
|
+
parseUrl: queryString.parseUrl,
|
|
158
|
+
stringify: queryString.stringify,
|
|
159
|
+
stringifyUrl: queryString.stringifyUrl
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Initialize plugin
|
|
165
|
+
*
|
|
166
|
+
* @method
|
|
167
|
+
* @async
|
|
168
|
+
*/
|
|
169
|
+
init = async () => {
|
|
170
|
+
if (this.config.home === '/') this.config.home = false
|
|
171
|
+
await collectRoutePathHandlers.call(this)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Start plugin
|
|
176
|
+
*
|
|
177
|
+
* @method
|
|
178
|
+
* @async
|
|
179
|
+
*/
|
|
180
|
+
start = async () => {
|
|
181
|
+
const { generateId, runHook } = this.app.bajo
|
|
182
|
+
const cfg = this.getConfig()
|
|
183
|
+
if (this.app.bajoLogger) {
|
|
184
|
+
cfg.factory.loggerInstance = this.app.bajoLogger.instance.child(
|
|
185
|
+
{},
|
|
186
|
+
{ msgPrefix: '[waibu] ' }
|
|
187
|
+
)
|
|
188
|
+
}
|
|
189
|
+
cfg.factory.genReqId = req => generateId()
|
|
190
|
+
cfg.factory.disableRequestLogging = true
|
|
191
|
+
cfg.factory.querystringParser = str => this.qs.parse(str)
|
|
192
|
+
|
|
193
|
+
const instance = fastify(cfg.factory)
|
|
194
|
+
instance.decorateRequest('lang', null)
|
|
195
|
+
instance.decorateRequest('t', () => {})
|
|
196
|
+
instance.decorateRequest('format', () => {})
|
|
197
|
+
instance.decorateRequest('langDetector', null)
|
|
198
|
+
instance.decorateRequest('site', null)
|
|
199
|
+
instance.decorateRequest('ns', null)
|
|
200
|
+
this.instance = instance
|
|
201
|
+
this.routes = this.routes || []
|
|
202
|
+
await runHook('waibu:afterCreateContext', instance)
|
|
203
|
+
await instance.register(sensible)
|
|
204
|
+
if (cfg.underPressure) await instance.register(underPressure)
|
|
205
|
+
if (cfg.noIcon) await instance.register(noIcon)
|
|
206
|
+
await handleRedirect.call(this, instance)
|
|
207
|
+
await handleForward.call(this, instance)
|
|
208
|
+
await appHook.call(this)
|
|
209
|
+
await routeHook.call(this, this.ns)
|
|
210
|
+
await boot.call(this)
|
|
211
|
+
await instance.listen(cfg.server)
|
|
212
|
+
if (cfg.printRoutes) printRoutes.call(this)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Exit handler
|
|
217
|
+
*
|
|
218
|
+
* @method
|
|
219
|
+
* @async
|
|
220
|
+
*/
|
|
221
|
+
exit = async () => {
|
|
222
|
+
this.instance.close()
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Find route by route name
|
|
227
|
+
*
|
|
228
|
+
* @param {string} name - ns based route name
|
|
229
|
+
* @returns {Object} Route object
|
|
230
|
+
*/
|
|
231
|
+
findRoute = (name) => {
|
|
232
|
+
const { outmatch } = this.app.lib
|
|
233
|
+
const { find } = this.app.lib._
|
|
234
|
+
const { breakNsPath } = this.app.bajo
|
|
235
|
+
let { ns, subNs = '', path } = breakNsPath(name)
|
|
236
|
+
const params = path.split('|')
|
|
237
|
+
if (params.length > 1) path = params[0]
|
|
238
|
+
return find(this.routes, r => {
|
|
239
|
+
if (r.path.startsWith('*')) return false
|
|
240
|
+
r.config = r.config ?? {}
|
|
241
|
+
const match = outmatch(r.config.pathSrc ?? r.path, { separator: false })
|
|
242
|
+
if (!match(path)) return false
|
|
243
|
+
return ns === r.config.ns && r.config.subNs === subNs
|
|
244
|
+
})
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
get escapeChars () {
|
|
248
|
+
return this.constructor.escapeChars
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Escape text
|
|
253
|
+
*
|
|
254
|
+
* @method
|
|
255
|
+
* @param {string} text
|
|
256
|
+
* @returns {string}
|
|
257
|
+
*/
|
|
258
|
+
escape = (text = '') => {
|
|
259
|
+
if (typeof text !== 'string') return text
|
|
260
|
+
const { forOwn } = this.app.lib._
|
|
261
|
+
forOwn(this.escapeChars, (v, k) => {
|
|
262
|
+
text = text.replaceAll(k, v)
|
|
263
|
+
})
|
|
264
|
+
return text
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Fetch something from url. A wrapper of bajo-extra's fetchUrl which support
|
|
269
|
+
* bajo's ns based url.
|
|
270
|
+
*
|
|
271
|
+
* @method
|
|
272
|
+
* @async
|
|
273
|
+
* @param {string} url - Also support ns based url
|
|
274
|
+
* @param {Object} [opts={}] - node's fetch options
|
|
275
|
+
* @param {Object} [extra={}] - See {@link https://ardhi.github.io/bajo-extra|bajo-extra}
|
|
276
|
+
* @returns {Object}
|
|
277
|
+
*/
|
|
278
|
+
fetch = async (url, opts = {}, extra = {}) => {
|
|
279
|
+
const { fetch } = this.app.bajoExtra
|
|
280
|
+
extra.rawResponse = true
|
|
281
|
+
|
|
282
|
+
url = this.routePath(url, { guessHost: true })
|
|
283
|
+
const resp = await fetch(url, opts, extra)
|
|
284
|
+
const result = await resp.json()
|
|
285
|
+
if (!resp.ok) {
|
|
286
|
+
throw this.error(result.message, {
|
|
287
|
+
statusCode: resp.status,
|
|
288
|
+
success: false
|
|
289
|
+
})
|
|
290
|
+
}
|
|
291
|
+
return result
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Get visitor IP from fastify's request object
|
|
296
|
+
*
|
|
297
|
+
* @method
|
|
298
|
+
* @param {Object} req - request object
|
|
299
|
+
* @returns {string}
|
|
300
|
+
*/
|
|
301
|
+
getIp = (req) => {
|
|
302
|
+
const { isEmpty } = this.app.lib._
|
|
303
|
+
let fwd = req.headers['x-forwarded-for'] ?? ''
|
|
304
|
+
if (!Array.isArray(fwd)) fwd = fwd.split(',').map(ip => ip.trim())
|
|
305
|
+
return isEmpty(fwd[0]) ? req.ip : fwd[0]
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Get origin of fastify's request object
|
|
310
|
+
*
|
|
311
|
+
* @method
|
|
312
|
+
* @param {Object} req
|
|
313
|
+
* @returns {string}
|
|
314
|
+
*/
|
|
315
|
+
getOrigin = (req) => {
|
|
316
|
+
const { isEmpty } = this.app.lib._
|
|
317
|
+
let host = req.host
|
|
318
|
+
if (isEmpty(host) || host === ':authority') host = `${this.config.server.host}:${this.config.server.port}`
|
|
319
|
+
return `${req.protocol}://${host}`
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Get plugin by prefix
|
|
324
|
+
*
|
|
325
|
+
* @method
|
|
326
|
+
* @param {string} prefix
|
|
327
|
+
* @returns {Object}
|
|
328
|
+
*/
|
|
329
|
+
getPluginByPrefix = (prefix) => {
|
|
330
|
+
const { get, find } = this.app.lib._
|
|
331
|
+
const item = find(this.app.waibu.routes, r => {
|
|
332
|
+
return get(r, 'config.prefix') === prefix
|
|
333
|
+
})
|
|
334
|
+
const ns = get(item, 'config.ns')
|
|
335
|
+
if (ns) return this.app[ns]
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Get plugin's prefix by name
|
|
340
|
+
*
|
|
341
|
+
* @method
|
|
342
|
+
* @param {string} name - Plugin's name
|
|
343
|
+
* @param {string} [webApp=waibuMpa] - Web app to use
|
|
344
|
+
* @returns {string}
|
|
345
|
+
*/
|
|
346
|
+
getPluginPrefix = (name, webApp = 'waibuMpa') => {
|
|
347
|
+
const { get, trim } = this.app.lib._
|
|
348
|
+
let prefix = get(this, `app.${name}.config.${webApp}.prefix`, get(this, `app.${name}.config.waibu.prefix`, this.app[name].alias))
|
|
349
|
+
if (name === 'main') {
|
|
350
|
+
const cfg = this.app[webApp].config
|
|
351
|
+
if (cfg.mountMainAsRoot) prefix = ''
|
|
352
|
+
}
|
|
353
|
+
return trim(prefix, '/')
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Get all available routes
|
|
358
|
+
*
|
|
359
|
+
* @method
|
|
360
|
+
* @param {boolean} [grouped=false] - Returns as groups of urls and methods
|
|
361
|
+
* @param {*} [lite=false] - Retuns only urls and methods
|
|
362
|
+
* @returns {Array}
|
|
363
|
+
*/
|
|
364
|
+
getRoutes = (grouped = false, lite = false) => {
|
|
365
|
+
const { groupBy, orderBy, mapValues, map, pick } = this.app.lib._
|
|
366
|
+
const all = this.routes
|
|
367
|
+
let routes
|
|
368
|
+
if (grouped) {
|
|
369
|
+
const group = groupBy(orderBy(all, ['url', 'method']), 'url')
|
|
370
|
+
routes = lite ? mapValues(group, (v, k) => map(v, 'method')) : group
|
|
371
|
+
} else if (lite) routes = map(all, a => pick(a, ['url', 'method']))
|
|
372
|
+
else routes = all
|
|
373
|
+
return routes
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Get uploaded files by request ID
|
|
378
|
+
*
|
|
379
|
+
* @method
|
|
380
|
+
* @param {string} reqId - Request ID
|
|
381
|
+
* @param {boolean} [fileUrl=false] - If ```true```, files returned as file url format (```file:///...```)
|
|
382
|
+
* @param {*} returnDir - If ```true```, also return its directory
|
|
383
|
+
* @returns {(Object|Array)} - Returns object if ```returnDir``` is ```true```, array of files otherwise
|
|
384
|
+
*/
|
|
385
|
+
getUploadedFiles = async (reqId, fileUrl = false, returnDir = false) => {
|
|
386
|
+
const { getPluginDataDir, resolvePath } = this.app.bajo
|
|
387
|
+
const { fastGlob } = this.app.lib
|
|
388
|
+
const dir = `${getPluginDataDir(this.ns)}/upload/${reqId}`
|
|
389
|
+
const result = await fastGlob(`${dir}/*`)
|
|
390
|
+
if (!fileUrl) return returnDir ? { dir, files: result } : result
|
|
391
|
+
const files = result.map(f => resolvePath(f, true))
|
|
392
|
+
return returnDir ? { dir, files } : files
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Is namespace's path contains language detector token?
|
|
397
|
+
*
|
|
398
|
+
* @method
|
|
399
|
+
* @param {string} ns - Plugin name
|
|
400
|
+
* @returns {boolean}
|
|
401
|
+
*/
|
|
402
|
+
isIntlPath = (ns) => {
|
|
403
|
+
const { get } = this.app.lib._
|
|
404
|
+
return get(this.app[ns], 'config.intl.detectors', []).includes('path')
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
notFound = (name, options) => {
|
|
408
|
+
throw this.error('_notFound', { path: name })
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Parse filter found from Fastify's request based on keys set in config object
|
|
413
|
+
*
|
|
414
|
+
* @method
|
|
415
|
+
* @param {Object} req - Request object
|
|
416
|
+
* @returns {Object}
|
|
417
|
+
*/
|
|
418
|
+
parseFilter = (req) => {
|
|
419
|
+
const result = {}
|
|
420
|
+
const items = Object.keys(this.config.qsKey)
|
|
421
|
+
for (const item of items) {
|
|
422
|
+
result[item] = req.query[this.config.qsKey[item]]
|
|
423
|
+
}
|
|
424
|
+
return result
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Get route directory by plugin's name
|
|
429
|
+
*
|
|
430
|
+
* @param {*} ns - Namespace
|
|
431
|
+
* @param {*} [baseNs] - Base namespace. If not provided, defaults to scope's ns
|
|
432
|
+
* @returns {string}
|
|
433
|
+
*/
|
|
434
|
+
routeDir = (ns, baseNs) => {
|
|
435
|
+
const { get } = this.app.lib._
|
|
436
|
+
if (!baseNs) baseNs = ns
|
|
437
|
+
const cfg = this.app[baseNs].config
|
|
438
|
+
const prefix = get(cfg, 'waibu.prefix', this.app[baseNs].alias)
|
|
439
|
+
const dir = prefix === '' ? '' : `/${prefix}`
|
|
440
|
+
const cfgMpa = get(this, 'app.waibuMpa.config')
|
|
441
|
+
if (ns === this.app.mainNs && cfgMpa.mountMainAsRoot) return ''
|
|
442
|
+
if (ns === baseNs) return dir
|
|
443
|
+
return dir + `/${get(this.app[ns].config, 'waibu.prefix', this.app[ns].alias)}`
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Get route path by route's name:
|
|
448
|
+
* - If it is a ```mailto:``` or ```tel:``` url, it returns as is
|
|
449
|
+
* - If it is a ns based name, it will be parsed first
|
|
450
|
+
*
|
|
451
|
+
* @method
|
|
452
|
+
* @param {string} name
|
|
453
|
+
* @param {Object} [options={}] - Options object
|
|
454
|
+
* @param {string} [options.base=waibu] - Base namespace
|
|
455
|
+
* @param {boolean} [options.guessHost] - If true, guest host if host is not set
|
|
456
|
+
* @param {Object} [options.query={}] - Query string's object. If provided, it will be added to returned value
|
|
457
|
+
* @param {Object} [options.params={}] - Parameter object. If provided, it will be merged to returned value
|
|
458
|
+
* @returns {string}
|
|
459
|
+
*/
|
|
460
|
+
routePath = (name, options = {}) => {
|
|
461
|
+
const { getPlugin } = this.app.bajo
|
|
462
|
+
const { defaultsDeep } = this.app.lib.aneka
|
|
463
|
+
const { isEmpty, get, trimEnd, trimStart } = this.app.lib._
|
|
464
|
+
const { breakNsPath } = this.app.bajo
|
|
465
|
+
const { query = {}, base = this.ns, params = {}, guessHost } = options
|
|
466
|
+
|
|
467
|
+
const plugin = getPlugin(base)
|
|
468
|
+
const cfg = plugin.config ?? {}
|
|
469
|
+
let info = {}
|
|
470
|
+
if (name.startsWith('mailto:') || name.startsWith('tel:')) return name
|
|
471
|
+
if (['%', '.', '/', '?', '#'].includes(name[0]) || name.slice(1, 2) === ':') info.path = name
|
|
472
|
+
else if (['~'].includes(name[0])) info.path = name.slice(1)
|
|
473
|
+
else {
|
|
474
|
+
info = breakNsPath(name)
|
|
475
|
+
}
|
|
476
|
+
if (info.path.slice(0, 2) === './') info.path = info.path.slice(2)
|
|
477
|
+
if (this.routePathHandlers[info.subNs]) return this.routePathHandlers[info.subNs].handler(name, options)
|
|
478
|
+
if (info.path.includes('//')) return info.path
|
|
479
|
+
|
|
480
|
+
info.path = info.path.split('/').map(p => {
|
|
481
|
+
return p[0] === ':' && params[p.slice(1)] ? params[p.slice(1)] : p
|
|
482
|
+
}).join('/')
|
|
483
|
+
let url = info.path
|
|
484
|
+
const langDetector = get(cfg, 'intl.detectors', [])
|
|
485
|
+
if (info.ns) url = trimEnd(langDetector.includes('path') ? `/${params.lang ?? ''}${this.routeDir(info.ns)}${info.path}` : `${this.routeDir(info.ns)}${info.path}`, '/')
|
|
486
|
+
if (options.uriEncoded) url = url.split('/').map(u => encodeURI(u)).join('/')
|
|
487
|
+
info.qs = defaultsDeep({}, query, info.qs)
|
|
488
|
+
if (!isEmpty(info.qs)) url += '?' + this.qs.stringify(info.qs)
|
|
489
|
+
if (!url.startsWith('http') && guessHost) url = `http://${this.config.server.host}:${this.config.server.port}/${trimStart(url, '/')}`
|
|
490
|
+
return url
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Method to send mail through Masohi Messaging System. It is a thin wrapper
|
|
495
|
+
* for {@link https://github.com/ardhi/masohi-mail|masohi-mail} send method.
|
|
496
|
+
*
|
|
497
|
+
* If masohi is not loaded, nothing is delivered.
|
|
498
|
+
*
|
|
499
|
+
* @method
|
|
500
|
+
* @async
|
|
501
|
+
* @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
|
|
502
|
+
* @param {Object} [params={}] - {@link https://github.com/ardhi/masohi-mail|masohi-mail}'s params object.
|
|
503
|
+
* @returns
|
|
504
|
+
*/
|
|
505
|
+
sendMail = async (tpl, { to, cc, bcc, from, subject, data = {}, conn, source, options = {} }) => {
|
|
506
|
+
conn = conn ?? 'masohiMail:default'
|
|
507
|
+
if (!this.app.masohi || !this.app.masohiMail) return
|
|
508
|
+
const { get, isString } = this.app.lib._
|
|
509
|
+
const { generateId } = this.app.bajo
|
|
510
|
+
const { render } = this.app.bajoTemplate
|
|
511
|
+
if (isString(tpl)) tpl = [tpl]
|
|
512
|
+
const locals = await buildLocals.call(this, { tpl, params: data, opts: options })
|
|
513
|
+
const opts = {
|
|
514
|
+
lang: get(options, 'req.lang'),
|
|
515
|
+
groupId: get(options, 'req.id', generateId())
|
|
516
|
+
}
|
|
517
|
+
const message = await render(tpl[0], locals, opts)
|
|
518
|
+
if (tpl[1]) opts.messageText = await render(tpl[1], locals, opts)
|
|
519
|
+
const payload = { type: 'object', data: { to, cc, bcc, from, subject, message, options: opts } }
|
|
520
|
+
await this.app.masohi.send({ payload, source: source ?? this.ns, conn }) // mail sent through worker
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* Recursively unescape block of texts
|
|
525
|
+
*
|
|
526
|
+
* @method
|
|
527
|
+
* @param {string} content - Source content
|
|
528
|
+
* @param {string} start - Block's start
|
|
529
|
+
* @param {string} end - Block's end
|
|
530
|
+
* @param {string} startReplacer - Token to use as block's start replacer
|
|
531
|
+
* @param {string} endReplacer - Token to use as block's end replacer
|
|
532
|
+
* @returns {string}
|
|
533
|
+
*/
|
|
534
|
+
unescapeBlock = (content, start, end, startReplacer, endReplacer) => {
|
|
535
|
+
const { extractText } = this.app.lib.aneka
|
|
536
|
+
const { result } = extractText(content, start, end)
|
|
537
|
+
if (result.length === 0) return content
|
|
538
|
+
const unescaped = this.unescape(result)
|
|
539
|
+
const token = `${start}${result}${end}`
|
|
540
|
+
const replacer = `${startReplacer}${unescaped}${endReplacer}`
|
|
541
|
+
const block = content.replaceAll(token, replacer)
|
|
542
|
+
return this.unescapeBlock(block, start, end, startReplacer, endReplacer)
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* Unescape text using {@link TEscapeChars} rules
|
|
547
|
+
*
|
|
548
|
+
* @method
|
|
549
|
+
* @param {string} text - Text to unescape
|
|
550
|
+
* @returns {string}
|
|
551
|
+
*/
|
|
552
|
+
unescape = (text) => {
|
|
553
|
+
const { forOwn, invert } = this.app.lib._
|
|
554
|
+
const mapping = invert(this.escapeChars)
|
|
555
|
+
forOwn(mapping, (v, k) => {
|
|
556
|
+
text = text.replaceAll(k, v)
|
|
557
|
+
})
|
|
558
|
+
return text
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
return Waibu
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
export default factory
|
|
566
|
+
</code></pre></article></section></div></div></div><div class="search-container" id="PkfLWpAbet" style="display:none"><div class="wrapper" id="iCxFxjkHbP"><button class="icon-button search-close-button" id="VjLlGakifb" aria-label="close search"><svg><use xlink:href="#close-icon"></use></svg></button><div class="search-box-c"><svg><use xlink:href="#search-icon"></use></svg> <input type="text" id="vpcKVYIppa" class="search-input" placeholder="Search..." autofocus></div><div class="search-result-c" id="fWwVHRuDuN"><span class="search-result-c-text">Type anything to view search result</span></div></div></div><div class="mobile-menu-icon-container"><button class="icon-button" id="mobile-menu" data-isopen="false" aria-label="menu"><svg><use xlink:href="#menu-icon"></use></svg></button></div><div id="mobile-sidebar" class="mobile-sidebar-container"><div class="mobile-sidebar-wrapper"><a href="/" class="sidebar-title sidebar-title-anchor">Waibu API</a><div class="mobile-nav-links"><div class="navbar-item"><a id="" href="https://www.npmjs.com/package/waibu" target="">NPM</a></div><div class="navbar-item"><a id="" href="https://github.com/ardhi/waibu" target="">Github</a></div><div class="navbar-item"><a id="" href="https://waibu.bajo.app/" target="">Waibu</a></div><div class="navbar-item"><a id="" href="https://bajo.app/" target="">Bajo</a></div></div><div class="mobile-sidebar-items-c"><div class="sidebar-section-title with-arrow" data-isopen="false" id="sidebar-classes"><div>Classes</div><svg><use xlink:href="#down-icon"></use></svg></div><div class="sidebar-section-children-container"><div class="sidebar-section-children"><a href="Waibu.html">Waibu</a></div></div><div class="sidebar-section-title with-arrow" data-isopen="false" id="sidebar-global"><div>Global</div><svg><use xlink:href="#down-icon"></use></svg></div><div class="sidebar-section-children-container"><div class="sidebar-section-children"><a href="global.html#factory">factory</a></div></div></div><div class="mobile-navbar-actions"><div class="navbar-right-item"><button class="icon-button search-button" aria-label="open-search"><svg><use xlink:href="#search-icon"></use></svg></button></div><div class="navbar-right-item"><button class="icon-button theme-toggle" aria-label="toggle-theme"><svg><use class="theme-svg-use" xlink:href="#dark-theme-icon"></use></svg></button></div><div class="navbar-right-item"><button class="icon-button font-size" aria-label="change-font-size"><svg><use xlink:href="#font-size-icon"></use></svg></button></div></div></div></div><script type="text/javascript" src="scripts/core.min.js"></script><script src="scripts/search.min.js" defer="defer"></script><script src="scripts/third-party/fuse.js" defer="defer"></script><script type="text/javascript">var tocbotInstance=tocbot.init({tocSelector:"#eed4d2a0bfd64539bb9df78095dec881",contentSelector:".main-content",headingSelector:"h1, h2, h3",hasInnerContainers:!0,scrollContainer:".main-content",headingsOffset:130,onClick:bringLinkToView})</script></body></html>
|