@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 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 banner from '@mattduffy/banner'
7
- app.use(banner)
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mattduffy/banner",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Displays server start-up information.",
5
5
  "author": "Matthew Duffy",
6
6
  "license": "ISC",
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.name - The name of the app starting up.
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
- _log('ctx.request.header.host', ctx.request.header.host)
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 _longestLabel = [_urlLabel, _refLabel].reduce((a, c) => {
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
- const _longestLine = [_urlLine, _refLine].reduce((a, c) => {
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
- const _requestBanner =
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
- await next()
318
- return true
338
+ if (next) {
339
+ await next(ctx.request.method)
340
+ }
319
341
  } catch (e) {
320
- _error('Failed after adding start-up banner.')
321
- _error(e)
322
- ctx.throw(500, 'Error after adding start-up banner.', e)
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
- ctx_DEL = { ...ctx }
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('the next() function')
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(ctx_POST, ctx)
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()(ctx, next))
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(ctx_GET, ctx)
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()(ctx, next))
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(ctx_PUT, ctx)
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()(ctx, next))
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(ctx_DEL, ctx)
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()(ctx, next))
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.url= ''
104
- ctx.request.header.host = undefined
105
- ctx.request.url = undefined
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 post = new Banner()
108
- await post.use()(ctx, next)
109
- // assert.throws(await post.use()(ctx, next))
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
  })