@mattduffy/banner 1.1.0 → 1.2.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/Readme.md +72 -2
- package/package.json +1 -1
- package/src/index.js +38 -15
- package/test/test.js +58 -24
package/Readme.md
CHANGED
|
@@ -3,6 +3,76 @@ This package exports a middleware function for Koajs app servers that displays u
|
|
|
3
3
|
information.
|
|
4
4
|
|
|
5
5
|
```javascript
|
|
6
|
-
import
|
|
7
|
-
|
|
6
|
+
import Banner from '@mattduffy/banner'
|
|
7
|
+
|
|
8
|
+
// to emit a startup banner in your app logs
|
|
9
|
+
const banner = new Banner({
|
|
10
|
+
name: <your_app_name>,
|
|
11
|
+
local: <your_local_dev_host>,
|
|
12
|
+
localPort: <your_local_dev_port>, // optional
|
|
13
|
+
public: <your_public_domain_name>,
|
|
14
|
+
})
|
|
15
|
+
banner.print()
|
|
16
|
+
/*
|
|
17
|
+
Emits to stdout (or where ever your logging goes).
|
|
18
|
+
#####################################################
|
|
19
|
+
# #
|
|
20
|
+
# Starting up: <your_app_name> #
|
|
21
|
+
# local: http://192.168.1.252:9876 #
|
|
22
|
+
# public: https://example.com #
|
|
23
|
+
# process: node v20.19.4 (Iron) #
|
|
24
|
+
# arch: x64 linux #
|
|
25
|
+
# #
|
|
26
|
+
#####################################################
|
|
27
|
+
*/
|
|
28
|
+
// To emit a banner at the start of each request
|
|
29
|
+
app.use(banner.use())
|
|
30
|
+
/*
|
|
31
|
+
Emits at the beginning of each client request.
|
|
32
|
+
#################################################################
|
|
33
|
+
# GET: https://dev.example.com/map/getToken?debug=verbose
|
|
34
|
+
# Referer: https://dev.exaple.com/?debug=verbose
|
|
35
|
+
# From IP: 192.168.1.254
|
|
36
|
+
#################################################################
|
|
37
|
+
*/
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The request banner uses a different ASCII text character depending on the value of
|
|
41
|
+
the ```ctx.request.method``` property. The supported request methods are ```GET```, ```PUT```,
|
|
42
|
+
```POST```, and ```DELETE```.
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
// GET
|
|
46
|
+
######################################################################
|
|
47
|
+
# GET: https://banner.test/a/really/long/url/to/a/special/page
|
|
48
|
+
# Referer: https://googoogle.com
|
|
49
|
+
# From IP: 192.168.1.250
|
|
50
|
+
######################################################################
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
// PUT
|
|
55
|
+
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
|
|
56
|
+
& PUT: https://banner.test/a/really/long/url/to/a/special/page
|
|
57
|
+
& Referer: https://googoogle.com
|
|
58
|
+
& From IP: 192.168.1.250
|
|
59
|
+
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
// POST
|
|
64
|
+
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
|
65
|
+
@ POST: https://banner.test/a/really/long/url/to/a/special/page
|
|
66
|
+
@ Referer: https://googoogle.com
|
|
67
|
+
@ From IP: 192.168.1.250
|
|
68
|
+
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
// DELETE
|
|
73
|
+
**********************************************************************
|
|
74
|
+
* DELETE: https://banner.test/a/really/long/url/to/a/special/page
|
|
75
|
+
* Referer: https://googoogle.com
|
|
76
|
+
* From IP: 192.168.1.250
|
|
77
|
+
**********************************************************************
|
|
8
78
|
```
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -24,6 +24,7 @@ export class Banner {
|
|
|
24
24
|
#borderGlyphPUT = '&'
|
|
25
25
|
#borderGlyphPOST = '@'
|
|
26
26
|
#borderGlyphDELETE = '*'
|
|
27
|
+
#ipAddress
|
|
27
28
|
#lineStarts = []
|
|
28
29
|
#lines = []
|
|
29
30
|
#local
|
|
@@ -38,10 +39,11 @@ export class Banner {
|
|
|
38
39
|
/**
|
|
39
40
|
* Create an instance of the Banner class.
|
|
40
41
|
* @param { object } [strings] - An object literal of strings to display in the banner.
|
|
41
|
-
* @param { string } strings.
|
|
42
|
-
* @param { string } strings.public - The public web address the app is accesssible at.
|
|
42
|
+
* @param { string } [strings.ip] - The ip address of the request.
|
|
43
43
|
* @param { string } strings.local - The local address the app is accessible at.
|
|
44
44
|
* @param { Number } [strings.localPort] - The local address port, if provided.
|
|
45
|
+
* @param { string } strings.name - The name of the app starting up.
|
|
46
|
+
* @param { string } strings.public - The public web address the app is accesssible at.
|
|
45
47
|
* @returns { Banner}
|
|
46
48
|
*/
|
|
47
49
|
constructor(strings=null) {
|
|
@@ -61,6 +63,7 @@ export class Banner {
|
|
|
61
63
|
this.#appName = strings.name
|
|
62
64
|
this.#borderGlyphGET = strings?.borderGlyph ?? this.#borderGlyphGET
|
|
63
65
|
this.#localPort = strings?.localPort ?? null
|
|
66
|
+
this.#ipAddress = strings?.ip ?? null
|
|
64
67
|
this.#local = `${strings.local}${(this.#localPort) ? ':' + this.#localPort : ''}`
|
|
65
68
|
this.#public = strings.public
|
|
66
69
|
this.#startingup = strings.name
|
|
@@ -264,9 +267,9 @@ export class Banner {
|
|
|
264
267
|
const gPOST = this.#borderGlyphPOST
|
|
265
268
|
const gDEL = this.#borderGlyphDELETE
|
|
266
269
|
const n = this.#appName
|
|
267
|
-
return async function banner(ctx, next){
|
|
270
|
+
return async function banner(ctx, next = null){
|
|
271
|
+
let _requestBanner
|
|
268
272
|
try {
|
|
269
|
-
// const _g = (/post/i.test(ctx.request.method)) ? '@' : g
|
|
270
273
|
let _g
|
|
271
274
|
switch(ctx.request.method.toLowerCase()) {
|
|
272
275
|
case 'get':
|
|
@@ -284,14 +287,28 @@ export class Banner {
|
|
|
284
287
|
default:
|
|
285
288
|
_g = gGET
|
|
286
289
|
}
|
|
287
|
-
|
|
290
|
+
if (!ctx) {
|
|
291
|
+
throw new Error('Missing required ctx object.')
|
|
292
|
+
}
|
|
293
|
+
if (!ctx.request.header.host) {
|
|
294
|
+
throw new Error('Missing required request header.host value.')
|
|
295
|
+
}
|
|
296
|
+
if (!ctx.request.method) {
|
|
297
|
+
throw new Error('Missing required request method value.')
|
|
298
|
+
}
|
|
299
|
+
if (!ctx.request.url) {
|
|
300
|
+
throw new Error('Missing required request url value.')
|
|
301
|
+
}
|
|
288
302
|
const _urlLabel = `${ctx.request.method}:`
|
|
289
303
|
const _url = `${ctx.request.protocol}://${ctx.request.header.host}${ctx.request.url}`
|
|
290
304
|
let _urlLine = `${_urlLabel} ${_url}`
|
|
291
305
|
const _refLabel = 'Referer:'
|
|
292
306
|
const _ref = ctx.request.header.referer ?? '<emtpy header field>'
|
|
293
307
|
let _refLine = `${_refLabel} ${_ref}`
|
|
294
|
-
const
|
|
308
|
+
const _ipLabel = 'From IP:'
|
|
309
|
+
const _ip = ctx.request.ip
|
|
310
|
+
let _ipLine = `${_ipLabel} ${_ip}`
|
|
311
|
+
const _longestLabel = [_urlLabel, _refLabel, _ipLabel].reduce((a, c) => {
|
|
295
312
|
if (a.length > (c.indexOf(':') + 1)) {
|
|
296
313
|
return a
|
|
297
314
|
}
|
|
@@ -303,24 +320,30 @@ export class Banner {
|
|
|
303
320
|
_urlLine = _urlLine.padStart(
|
|
304
321
|
(_longestLabel - _urlLine.indexOf(':')) + _urlLine.length, ' '
|
|
305
322
|
)
|
|
306
|
-
|
|
323
|
+
_ipLine = _ipLine.padStart(
|
|
324
|
+
(_longestLabel - _ipLine.indexOf(':')) + _ipLine.length, ' '
|
|
325
|
+
)
|
|
326
|
+
const _longestLine = [_urlLine, _refLine, _ipLine].reduce((a, c) => {
|
|
307
327
|
if (a > c.length) return a
|
|
308
328
|
return c.length
|
|
309
329
|
}, '')
|
|
310
330
|
// _log('request banner _longestLine', _longestLine)
|
|
311
|
-
|
|
331
|
+
_requestBanner =
|
|
312
332
|
`${_g.padEnd(_longestLine + 5, _g)}\n`
|
|
313
333
|
+ `${_g} ${_urlLine}\n`
|
|
314
334
|
+ `${_g} ${_refLine}\n`
|
|
335
|
+
+ `${_g} ${_ipLine}\n`
|
|
315
336
|
+ `${_g.padEnd(_longestLine + 5, _g)}`
|
|
316
337
|
_log(_requestBanner)
|
|
317
|
-
|
|
318
|
-
|
|
338
|
+
if (next) {
|
|
339
|
+
await next(ctx.request.method)
|
|
340
|
+
}
|
|
319
341
|
} catch (e) {
|
|
320
|
-
_error('Failed after adding
|
|
321
|
-
_error(e)
|
|
322
|
-
ctx.throw(500,
|
|
342
|
+
// _error('Failed after adding the request banner.')
|
|
343
|
+
// _error(e)
|
|
344
|
+
ctx.throw(500, e)
|
|
323
345
|
}
|
|
324
|
-
|
|
325
|
-
|
|
346
|
+
return _requestBanner
|
|
347
|
+
} // end async closure function, Banner.use.banner()
|
|
348
|
+
} // end Banner.use()
|
|
326
349
|
}
|
package/test/test.js
CHANGED
|
@@ -8,8 +8,10 @@ import {
|
|
|
8
8
|
} from 'node:test'
|
|
9
9
|
import assert from 'node:assert/strict'
|
|
10
10
|
import fs from 'node:fs/promises'
|
|
11
|
+
import os from 'node:os'
|
|
11
12
|
import { Banner } from '../src/index.js'
|
|
12
13
|
const skip = { skip: true }
|
|
14
|
+
console.log(skip)
|
|
13
15
|
let cfg
|
|
14
16
|
let ctx
|
|
15
17
|
let ctx_POST
|
|
@@ -17,11 +19,30 @@ let ctx_GET
|
|
|
17
19
|
let ctx_PUT
|
|
18
20
|
let ctx_DEL
|
|
19
21
|
let next
|
|
20
|
-
console.log(skip)
|
|
21
22
|
describe('First test suite for banner package', async () => {
|
|
22
23
|
before(() => {
|
|
24
|
+
function getLocalIpAddress() {
|
|
25
|
+
const networkInterfaces = os.networkInterfaces();
|
|
26
|
+
let localIpAddress = null;
|
|
27
|
+
|
|
28
|
+
for (const interfaceName in networkInterfaces) {
|
|
29
|
+
const networkInterface = networkInterfaces[interfaceName];
|
|
30
|
+
for (const details of networkInterface) {
|
|
31
|
+
// Check for IPv4, not internal (loopback), and a valid address
|
|
32
|
+
if (details.family === 'IPv4' && !details.internal) {
|
|
33
|
+
localIpAddress = details.address;
|
|
34
|
+
break; // Found a suitable IPv4 address, exit inner loop
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (localIpAddress) {
|
|
38
|
+
break; // Found a suitable IPv4 address, exit outer loop
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return localIpAddress;
|
|
42
|
+
}
|
|
23
43
|
ctx = {
|
|
24
44
|
request: {
|
|
45
|
+
ip: getLocalIpAddress(),
|
|
25
46
|
protocol: 'https',
|
|
26
47
|
method: '',
|
|
27
48
|
url: '/a/really/long/url/to/a/special/page',
|
|
@@ -30,17 +51,10 @@ describe('First test suite for banner package', async () => {
|
|
|
30
51
|
referer: 'https://googoogle.com',
|
|
31
52
|
},
|
|
32
53
|
},
|
|
33
|
-
throw: (code, msg) => {
|
|
34
|
-
throw new Error(`Error code ${code}: ${msg}`)
|
|
35
|
-
}
|
|
36
54
|
}
|
|
37
|
-
|
|
38
|
-
ctx_PUT = { ...ctx }
|
|
39
|
-
ctx_POST = { ...ctx }
|
|
40
|
-
ctx_GET = { ...ctx }
|
|
41
|
-
next = async () => {
|
|
55
|
+
next = async (m) => {
|
|
42
56
|
setTimeout(() => {
|
|
43
|
-
console.log(
|
|
57
|
+
console.log(`the next(${m}) function`)
|
|
44
58
|
}, 1000)
|
|
45
59
|
}
|
|
46
60
|
cfg = {
|
|
@@ -68,44 +82,64 @@ describe('First test suite for banner package', async () => {
|
|
|
68
82
|
})
|
|
69
83
|
|
|
70
84
|
it('Should work as a koajs middleware function - POST method.', async () => {
|
|
71
|
-
ctx_POST = Object.assign(
|
|
85
|
+
ctx_POST = Object.assign({}, ctx)
|
|
72
86
|
ctx_POST.request.method = 'POST'
|
|
87
|
+
ctx_POST.throw = (code, err, {} = null) => {
|
|
88
|
+
throw new Error(`${code}, ${msg}`)
|
|
89
|
+
}
|
|
73
90
|
console.log(ctx_POST)
|
|
74
91
|
const post = new Banner(ctx_POST)
|
|
75
|
-
assert(await post.use()(
|
|
92
|
+
assert(await post.use()(ctx_POST, next))
|
|
76
93
|
})
|
|
77
94
|
|
|
78
95
|
it('Should work as a koajs middleware function - GET method.', async () => {
|
|
79
|
-
ctx_GET = Object.assign(
|
|
96
|
+
ctx_GET = Object.assign({}, ctx)
|
|
80
97
|
ctx_GET.request.method = 'GET'
|
|
98
|
+
ctx_GET.throw = (code, err, {} = null) => {
|
|
99
|
+
throw new Error(`${code}, ${msg}`)
|
|
100
|
+
}
|
|
81
101
|
console.log(ctx_GET)
|
|
82
102
|
const get = new Banner(ctx_GET)
|
|
83
|
-
assert(await get.use()(
|
|
103
|
+
assert(await get.use()(ctx_GET, next))
|
|
84
104
|
})
|
|
85
105
|
|
|
86
106
|
it('Should work as a koajs middleware function - PUT method.', async () => {
|
|
87
|
-
ctx_PUT = Object.assign(
|
|
107
|
+
ctx_PUT = Object.assign({}, ctx)
|
|
88
108
|
ctx_PUT.request.method = 'PUT'
|
|
109
|
+
ctx_PUT.throw = (code, err, {} = null) => {
|
|
110
|
+
throw new Error(`${code}, ${msg}`)
|
|
111
|
+
}
|
|
89
112
|
console.log(ctx_PUT)
|
|
90
113
|
const put = new Banner(ctx_PUT)
|
|
91
|
-
assert(await put.use()(
|
|
114
|
+
assert(await put.use()(ctx_PUT, next))
|
|
92
115
|
})
|
|
93
116
|
|
|
94
117
|
it('Should work as a koajs middleware function - DELETE method.', async () => {
|
|
95
|
-
ctx_DEL = Object.assign(
|
|
118
|
+
ctx_DEL = Object.assign({}, ctx)
|
|
96
119
|
ctx_DEL.request.method = 'DELETE'
|
|
120
|
+
ctx_DEL.throw = (code, err, {} = null) => {
|
|
121
|
+
throw new Error(`${code}, ${msg}`)
|
|
122
|
+
}
|
|
97
123
|
console.log(ctx_DEL)
|
|
98
124
|
const del = new Banner(ctx_DEL)
|
|
99
|
-
assert(await del.use()(
|
|
125
|
+
assert(await del.use()(ctx_DEL, next))
|
|
100
126
|
})
|
|
101
127
|
|
|
102
128
|
it('Should fail as a koajs middleware function, missing input parameters.', async () => {
|
|
103
|
-
ctx.
|
|
104
|
-
ctx.request.
|
|
105
|
-
ctx.
|
|
129
|
+
ctx.request.header.host = null
|
|
130
|
+
ctx.request.url = null
|
|
131
|
+
ctx.throw = (code, err) => {
|
|
132
|
+
// console.log(err.message)
|
|
133
|
+
throw err
|
|
134
|
+
}
|
|
106
135
|
console.log(ctx)
|
|
107
|
-
const
|
|
108
|
-
await
|
|
109
|
-
|
|
136
|
+
const req = new Banner()
|
|
137
|
+
const use = await req.use()
|
|
138
|
+
assert.rejects(
|
|
139
|
+
async () => {
|
|
140
|
+
await use(ctx)
|
|
141
|
+
},
|
|
142
|
+
Error,
|
|
143
|
+
)
|
|
110
144
|
})
|
|
111
145
|
})
|