@es-labs/jslib 0.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.
Files changed (72) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/README.md +42 -0
  3. package/__test__/services.test.js +32 -0
  4. package/auth/index.js +226 -0
  5. package/auth/keyv.js +23 -0
  6. package/auth/knex.js +29 -0
  7. package/auth/redis.js +23 -0
  8. package/comms/email.js +123 -0
  9. package/comms/nexmo.js +44 -0
  10. package/comms/telegram.js +43 -0
  11. package/comms/telegram2/inbound.js +314 -0
  12. package/comms/telegram2/outbound.js +574 -0
  13. package/comms/webpush.js +60 -0
  14. package/config.js +37 -0
  15. package/express/controller/auth/oauth.js +39 -0
  16. package/express/controller/auth/oidc.js +87 -0
  17. package/express/controller/auth/own.js +100 -0
  18. package/express/controller/auth/saml.js +74 -0
  19. package/express/upload.js +48 -0
  20. package/index.js +1 -0
  21. package/iso/README.md +4 -0
  22. package/iso/__tests__/csv-utils.spec.js +128 -0
  23. package/iso/__tests__/datetime.spec.js +101 -0
  24. package/iso/__tests__/fetch.spec.js +270 -0
  25. package/iso/csv-utils.js +206 -0
  26. package/iso/datetime.js +103 -0
  27. package/iso/fetch.js +129 -0
  28. package/iso/fetch2.js +180 -0
  29. package/iso/log-filter.js +17 -0
  30. package/iso/sleep.js +6 -0
  31. package/iso/ws.js +63 -0
  32. package/node/oss-files/oss-uploader-client-fetch.js +258 -0
  33. package/node/oss-files/oss-uploader-client-fetch.md +31 -0
  34. package/node/oss-files/oss-uploader-client.js +219 -0
  35. package/node/oss-files/oss-uploader-server.js +199 -0
  36. package/node/oss-files/oss-uploader-usage.js +121 -0
  37. package/node/oss-files/oss-uploader-usage.md +34 -0
  38. package/node/oss-files/s3-uploader-client.js +217 -0
  39. package/node/oss-files/s3-uploader-server.js +123 -0
  40. package/node/oss-files/s3-uploader-usage.js +77 -0
  41. package/node/oss-files/s3-uploader-usage.md +34 -0
  42. package/package.json +53 -0
  43. package/packageInfo.js +9 -0
  44. package/services/ali.js +279 -0
  45. package/services/aws.js +194 -0
  46. package/services/db/__tests__/keyv.spec.js +31 -0
  47. package/services/db/keyv.js +14 -0
  48. package/services/db/knex.js +67 -0
  49. package/services/db/redis.js +51 -0
  50. package/services/index.js +57 -0
  51. package/services/mq/README.md +8 -0
  52. package/services/websocket.js +139 -0
  53. package/t4t/README.md +1 -0
  54. package/traps.js +20 -0
  55. package/utils/__tests__/aes.spec.js +52 -0
  56. package/utils/aes.js +23 -0
  57. package/web/UI.md +71 -0
  58. package/web/bwc-autocomplete.js +211 -0
  59. package/web/bwc-combobox.js +343 -0
  60. package/web/bwc-fileupload.js +87 -0
  61. package/web/bwc-loading-overlay.js +54 -0
  62. package/web/bwc-t4t-form.js +511 -0
  63. package/web/bwc-table.js +756 -0
  64. package/web/fetch.js +129 -0
  65. package/web/i18n.js +24 -0
  66. package/web/idle.js +49 -0
  67. package/web/parse-jwt.js +15 -0
  68. package/web/pwa.js +84 -0
  69. package/web/sign-pad.js +164 -0
  70. package/web/t4t-fe.js +164 -0
  71. package/web/util.js +126 -0
  72. package/web/web-cam.js +182 -0
@@ -0,0 +1,8 @@
1
+ Need to improve on this...
2
+ Need more research and study
3
+
4
+ For express it is only to push job
5
+
6
+ Also may need call to monitor and control jobs
7
+
8
+ Need to have a seperate listener app
@@ -0,0 +1,139 @@
1
+ // https://www.npmjs.com/package/ws
2
+ // NOTE: if --forcedExit --detectOpenHandles in JEST test, will cause error
3
+ // TODO: automated testing for websockets
4
+ import WebSocket, { WebSocketServer } from 'ws'
5
+ import https from 'https'
6
+
7
+ // NOSONAR
8
+ // function heartbeat() {
9
+ // clearTimeout(this.pingTimeout)
10
+ // // Use `WebSocket#terminate()` and not `WebSocket#close()`. Delay should be
11
+ // // equal to the interval at which your server sends out pings plus a
12
+ // // conservative assumption of the latency.
13
+ // this.pingTimeout = setTimeout(() => {
14
+ // this.terminate()
15
+ // }, 30000 + 1000)
16
+ // const client = new WebSocket('wss://echo.websocket.org/')
17
+ // client.on('open', heartbeat)
18
+ // client.on('ping', heartbeat)
19
+ // client.on('close', function clear() {
20
+ // clearTimeout(this.pingTimeout)
21
+ // })
22
+ // let WSServer = require('ws').Server
23
+ // // Create web socket server on top of a regular http server
24
+ // let wss = new WSServer({ server })
25
+ // server.on('request', app)
26
+
27
+ export default class Wss {
28
+ constructor(options = JSON.parse(process.env.WS_OPTIONS || null) || {}) {
29
+ if (!Wss._instance) {
30
+ Wss._instance = this
31
+ this._port = options.WS_PORT
32
+ this._keepAliveMs = options.WS_KEEEPALIVE_MS
33
+ this._wss = null
34
+ this._onClientConnect = (ws) => { }
35
+ this._onClientClose = (ws) => { }
36
+ this._onClientMessage = async (data, isBinary, ws, wss) => { // client incoming message
37
+ const message = isBinary ? data : data.toString()
38
+ // console.log('message', message)
39
+ try { // try-catch only detect immediate error, cannot detect if write failure
40
+ if (wss) { // send to other clients except self
41
+ wss.clients.forEach((client) => {
42
+ if (client !== ws && client.readyState === WebSocket.OPEN) {
43
+ client.send(message) // send message to others
44
+ }
45
+ })
46
+ // ws.send('something', function ack(error) { console.log }) // If error !defined, send has been completed, otherwise error object will indicate what failed.
47
+ ws.send(message) // echo back message...
48
+ }
49
+ } catch (e) {
50
+ console.log(e.toString())
51
+ }
52
+ }
53
+ }
54
+ return Wss._instance
55
+ }
56
+ static getInstance() {
57
+ return this._instance
58
+ }
59
+ setOnClientMessage(onClientMessageFn) {
60
+ this._onClientMessage = onClientMessageFn
61
+ }
62
+ setOnClientConnect(onClientConnectFn) { // what to do when client connects
63
+ this._onClientConnect = onClientConnectFn
64
+ }
65
+ setOnClientCLose(onClientCloseFn) { // what to do when client closes
66
+ this._onClientClose = onClientCloseFn
67
+ }
68
+
69
+ get() {
70
+ return this._instance
71
+ }
72
+
73
+ send(data) {
74
+ this._wss.clients.forEach((client) => {
75
+ if (client.readyState === WebSocket.OPEN) {
76
+ client.send(data)
77
+ }
78
+ })
79
+ }
80
+
81
+ open(server=null, app=null) {
82
+ const { HTTPS_PRIVATE_KEY, HTTPS_CERTIFICATE } = process.env
83
+ let err
84
+ try {
85
+ if (!this._wss && this._port) {
86
+ if (HTTPS_CERTIFICATE) {
87
+ if (!server) server = https.createServer({
88
+ key: HTTPS_PRIVATE_KEY, cert: HTTPS_CERTIFICATE
89
+ }).listen(this._port) // use same port, create server because of graphql subscriptions
90
+ this._wss = new WebSocketServer({ server })
91
+ } else {
92
+ if (!server) this._wss = new WebSocketServer({ port: this._port }) // use seperate port
93
+ else this._wss = new WebSocketServer({ server })
94
+ }
95
+ if (app) server.on('request', app)
96
+
97
+ console.log('WS API listening on port ' + this._port)
98
+ if (this._wss) {
99
+ this._wss.on('connection', (ws) => {
100
+ // console.log('ws client connected')
101
+ this._onClientConnect(ws) // what else to do when client connects
102
+ ws.isAlive = true
103
+ ws.on('pong', () => { ws.isAlive = true })
104
+ ws.on('close', () => this._onClientClose(ws))
105
+ ws.on('message', (data, isBinary) => this._onClientMessage(data, isBinary, ws, this._wss))
106
+ })
107
+ setInterval(() => { // set keep-alive
108
+ // console.log('WS Clients: ', this._wss.clients.size)
109
+ this._wss && this._wss.clients.forEach((ws) => {
110
+ if (!ws.isAlive) return ws.terminate() // force close
111
+ ws.isAlive = false
112
+ return ws.ping(() => {}) // NOSONAR
113
+ })
114
+ }, this._keepAliveMs)
115
+ }
116
+ } else {
117
+ console.log('NO WS Service To Open')
118
+ }
119
+ } catch (e) {
120
+ err = e.toString()
121
+ }
122
+ console.log('WS Open ' + (err ? err : 'Done'))
123
+ return this
124
+ }
125
+
126
+ close () {
127
+ try { // close all connections
128
+ if (this._wss) {
129
+ this._wss.close()
130
+ // this._wss.clients.forEach(client => client.close(0, 'wss close() called')) // close gracefully
131
+ for (const client of this._wss.clients) client.terminate() // https://github.com/websockets/ws/releases/tag/8.0.0
132
+ this._wss = null //delete wss
133
+ }
134
+ } catch (e) {
135
+ console.error(e.toString())
136
+ }
137
+ console.log('WS API CLOSE OK')
138
+ }
139
+ }
package/t4t/README.md ADDED
@@ -0,0 +1 @@
1
+ A simple table editor and form backend
package/traps.js ADDED
@@ -0,0 +1,20 @@
1
+ export default (signalFn, exceptionFn) => {
2
+ let defaultExceptionFn = async (e, type) => {
3
+ console.log(type, e.toString())
4
+ // process.emit("SIGTERM") // process.exit(0), process.kill(process.pid, type)
5
+ }
6
+ if (!exceptionFn) exceptionFn = defaultExceptionFn
7
+ const exceptions = ['unhandledRejection', 'uncaughtException']
8
+ exceptions.forEach(type => process.on(type, (e) => exceptionFn(e, type)))
9
+
10
+ let defaultSignalFn = async (type) => console.log(type)
11
+ if (!signalFn) signalFn = defaultSignalFn
12
+ const signals = ['SIGTERM', 'SIGINT', 'SIGUSR2'] // SIGINT now works on windows
13
+ // if (process.platform === 'win32') {
14
+ // import('readline').then(readline => readline.createInterface({ input: process.stdin, output: process.stdout }).on('SIGINT', () => process.emit('SIGINT')))
15
+ // }
16
+ signals.forEach(type => process.once(type, async () => {
17
+ const exitCode = await signalFn(type)
18
+ return Number.isInteger(exitCode) ? process.exit(parseInt(exitCode)) : process.exit(-10001) // should terminate the application
19
+ }))
20
+ }
@@ -0,0 +1,52 @@
1
+ import assert from 'node:assert/strict';
2
+ import { describe, it } from 'node:test';
3
+
4
+ import { genIv, genKey, encryptText, decryptText } from '../aes.js';
5
+
6
+ describe('utils/aes.js', () => {
7
+ const algorithm = 'aes256';
8
+ const password = 'test-password';
9
+ const plaintext = 'Hello, AES!';
10
+
11
+ it('should generate a valid IV (16 bytes)', () => {
12
+ const iv = genIv();
13
+ assert.ok(Buffer.isBuffer(iv));
14
+ assert.strictEqual(iv.length, 16);
15
+ });
16
+
17
+ it('should derive a 256 bit key from password', () => {
18
+ const key = genKey(algorithm, password);
19
+ assert.ok(Buffer.isBuffer(key));
20
+ assert.strictEqual(key.length, 32);
21
+ });
22
+
23
+ it('should derive a 256 bit key from password', () => {
24
+ const key = genKey('sha128', password);
25
+ assert.ok(Buffer.isBuffer(key));
26
+ assert.strictEqual(key.length, 32);
27
+ });
28
+
29
+ it('should encrypt and decrypt text correctly', () => {
30
+ const iv = genIv();
31
+ const key = genKey(algorithm, password);
32
+
33
+ const encrypted = encryptText(algorithm, key, iv, plaintext);
34
+ assert.ok(typeof encrypted === 'string');
35
+
36
+ const decrypted = decryptText(algorithm, key, iv, encrypted);
37
+ assert.strictEqual(decrypted, plaintext);
38
+ });
39
+ });
40
+
41
+
42
+ /*
43
+ const test_aes = (data = 'test data', password = 'pw', algorithm = 'aes256') => {
44
+ const [iv, key] = [genIv(), genKey(algorithm, password)]
45
+ const encText = encryptText(algorithm, key, iv, data)
46
+ const decText = decryptText(algorithm, key, iv, encText)
47
+ console.log('Password:', password)
48
+ console.log('Key: ', key.toString(DEFAULT_ENCODING))
49
+ console.log('Plaintext: ' + decText)
50
+ console.log('Encrypted (aes256): ' + encText)
51
+ }
52
+ */
package/utils/aes.js ADDED
@@ -0,0 +1,23 @@
1
+ import crypto from 'crypto'
2
+ const DEFAULT_ENCODING = 'base64' // 'binary'
3
+
4
+ export const encryptText = (alg, key, iv, text, encoding = DEFAULT_ENCODING) => {
5
+ const cipher = crypto.createCipheriv(alg, key, iv)
6
+ return cipher.update(text, 'utf8', encoding) + cipher.final(encoding)
7
+ }
8
+
9
+ export const decryptText = (alg, key, iv, text, encoding = DEFAULT_ENCODING) => {
10
+ const decipher = crypto.createDecipheriv(alg, key, iv)
11
+ return decipher.update(text, encoding) + decipher.final()
12
+ }
13
+
14
+ export const genIv = () => new Buffer.alloc(16, crypto.pseudoRandomBytes(16))
15
+
16
+ export const genKey = (algorithm, password) => {
17
+ const [size, algo] = algorithm.includes("256") ? [32, 'sha256'] :
18
+ // algorithm.includes("192") ? [32, 'sha192'] : // this is not support
19
+ algorithm.includes("128") ? [32, 'md5'] : []
20
+ const hash = crypto.createHash(algo)
21
+ hash.update(password)
22
+ return new Buffer.alloc(size, hash.digest('hex'),'hex')
23
+ }
package/web/UI.md ADDED
@@ -0,0 +1,71 @@
1
+ bulma
2
+ bootstrap
3
+ material UI - muicss
4
+
5
+ DO NOT USE
6
+ - material-components-web, semantic ui
7
+
8
+ input
9
+ - text (with regex)
10
+ - number
11
+ - date
12
+ - time
13
+ - datetime (local)
14
+ - file
15
+ - textarea
16
+ - replace <input> with <textarea>
17
+
18
+ ---
19
+
20
+ Extended input
21
+
22
+ - autocomplete / select (can use for selects also..., if multiple..., show tags list, delete from tags list?)
23
+ - single
24
+ - multiple
25
+ - allow new (always false if select)
26
+ bwc-autocomplete (if multiple, use tags? remove tags when not needed) - tag
27
+ bs-autocomplete - badge
28
+ mui-autocomplete - chips
29
+
30
+ ---
31
+
32
+ - link
33
+ - a[href]
34
+ - button
35
+
36
+
37
+ ## Input
38
+
39
+ label: <label> innerText, additional classes
40
+ input: <input> attrs: type, pattern, placeholder, additional classes
41
+ helper: <p>
42
+
43
+
44
+ {
45
+ tag:
46
+ attrs: []
47
+ // event:
48
+ children: [
49
+ ]
50
+ }
51
+
52
+ function formEl (node) {
53
+ const { tag, className, attrs, children } = node
54
+ const el = document.createElement(tag)
55
+ if (className) {
56
+ el.className = className
57
+ }
58
+ if (attrs) {
59
+ for (let key in attrs) {
60
+ el.setAttirbutes(key, attrs[key])
61
+ }
62
+ }
63
+ if (children) {
64
+ children.forEach(child => {
65
+ childEl = formEl(child)
66
+ el.appendChild(childEl)
67
+ })
68
+ }
69
+ return el
70
+ }
71
+
@@ -0,0 +1,211 @@
1
+ /*
2
+ Autocomplete component using input and datalist.
3
+ Only able to handle single selection due to nature of datalist not able to have click event
4
+
5
+ attributes:
6
+ - value (via v-model)
7
+ - required
8
+ - disabled
9
+ - listid (needed if using more than 2 components on the same page)
10
+ - input-class (style the input)
11
+
12
+ properties:
13
+ - items Array or String or Object
14
+
15
+ methods:
16
+ - setList(items) // should be private, called when items property changes
17
+
18
+ events emitted:
19
+ - @input (via v-model) - e.target.value
20
+ - @search - e.detail String
21
+ - @selected - e.detail String or Object or null
22
+
23
+ if selected data is null (no match found, else match found)
24
+
25
+ Usage with (VueJS):
26
+
27
+ <bwc-autocomplete required :items="ac.items" v-model="ac.value" @search="(e) => autoComplete(e)" @selected=></bwc-autocomplete>
28
+
29
+ const ac = reactive({
30
+ value: 'a',
31
+ items: ['aa9','aa5']
32
+ })
33
+
34
+ const autoComplete = (e) => {
35
+ const list = ['aa1', 'aa15', 'aa16', 'aa17', 'aa18', 'aa19', 'aa20', 'aa21', 'aa22', 'aa23']
36
+ const result = []
37
+ for (let i = 0; i < list.length; i++) {
38
+ if (list[i].includes(e.detail)) result.push(list[i])
39
+ }
40
+ ac.items = result
41
+ }
42
+
43
+ */
44
+ const template = document.createElement('template')
45
+ template.innerHTML = /*html*/`
46
+ <input type="text" list="json-datalist" placeholder="search..." autocomplete="off">
47
+ <datalist id="json-datalist"></datalist>
48
+ `
49
+
50
+ class AutoComplete extends HTMLElement {
51
+ // local properties
52
+ #items = [] // private
53
+ selectedItem = null // public
54
+
55
+ constructor() {
56
+ super()
57
+ this.inputFn = this.inputFn.bind(this)
58
+ }
59
+
60
+ connectedCallback() {
61
+ this.appendChild(template.content.cloneNode(true))
62
+
63
+ // console.log('listid', this.listid)
64
+ this.querySelector('datalist').id = this.listid
65
+ this.querySelector('input').setAttribute('list', this.listid)
66
+
67
+ const el = this.querySelector('input')
68
+ el.addEventListener('input', this.inputFn)
69
+
70
+ el.onblur = (e) => {
71
+ const found = this.items.find(item => {
72
+ return typeof item === 'string' ? item === this.value : item.key === this.value || item.text === this.value
73
+ })
74
+ if (!found) { // not found
75
+ if (this.selectedItem) {
76
+ // console.log('not found but is selected')
77
+ this.selectedItem = null
78
+ this.dispatchEvent(new CustomEvent('selected', { detail: this.selectedItem }))
79
+ }
80
+ } else {
81
+ if (!this.selectedItem) {
82
+ // console.log('found but not selected')
83
+ this.selectedItem = found
84
+ this.dispatchEvent(new CustomEvent('selected', { detail: this.selectedItem }))
85
+ }
86
+ }
87
+ }
88
+ // console.log('setup stuff', this.required, this.disabled, this.inputClass)
89
+ el.value = this.value
90
+ el.className = this.inputClass || 'input' // default to bulma?
91
+
92
+ if (this.required) el.setAttribute('required', '')
93
+ else el.removeAttribute('required')
94
+ if (this.disabled) el.setAttribute('disabled', '')
95
+ else el.removeAttribute('disabled')
96
+
97
+ this.setList(this.items)
98
+ }
99
+
100
+ disconnectedCallback() {
101
+ const el = this.querySelector('input')
102
+ el.removeEventListener('input', this.inputFn)
103
+ }
104
+
105
+ attributeChangedCallback(name, oldVal, newVal) {
106
+ const el = this.querySelector('input')
107
+ switch (name) {
108
+ case 'value': {
109
+ if (el) el.value = newVal
110
+ this.dispatchEvent(new CustomEvent('input', { detail: newVal }))
111
+ break
112
+ }
113
+ case 'required': {
114
+ if (el) el.setAttribute('required', '')
115
+ break
116
+ }
117
+ case 'disabled': {
118
+ if (el) el.setAttribute('disabled', '')
119
+ break
120
+ }
121
+ case 'input-class': {
122
+ if (el) el.className = newVal
123
+ break
124
+ }
125
+ default:
126
+ break
127
+ }
128
+ }
129
+
130
+ static get observedAttributes() {
131
+ return ['value', 'required', 'listid', 'disabled', 'input-class']
132
+ }
133
+
134
+ get value() { return this.getAttribute('value') }
135
+ set value(val) { this.setAttribute('value', val) }
136
+
137
+ get required() { return this.hasAttribute('required') }
138
+ set required(val) {
139
+ if (val) {
140
+ this.setAttribute('required', '')
141
+ } else {
142
+ this.removeAttribute('required')
143
+ }
144
+ }
145
+
146
+ get listid() { return this.getAttribute('listid') }
147
+ set listid(val) { this.setAttribute('listid', val) }
148
+
149
+ get disabled() { return this.hasAttribute('disabled') }
150
+ set disabled(val) {
151
+ if (val) {
152
+ this.setAttribute('disabled', '')
153
+ } else {
154
+ this.removeAttribute('disabled')
155
+ }
156
+ }
157
+
158
+ get inputClass() { return this.getAttribute('input-class') }
159
+ set inputClass(val) { this.setAttribute('input-class', val) }
160
+
161
+ // properties
162
+ get items() {
163
+ return this.#items
164
+ }
165
+ set items(val) {
166
+ // console.log('set items', val.length)
167
+ this.#items = val
168
+ this.setList(val)
169
+ }
170
+
171
+ inputFn(e) { // whether clicked or typed
172
+ // console.log('inputFn', e.target.value, this.items.length)
173
+ const el = this.querySelector('input')
174
+ const prevItem = this.selectedItem
175
+ this.value = el.value
176
+
177
+ const found = this.items.find(item => {
178
+ return typeof item === 'string' ? item === this.value : item.key === this.value || item.text === this.value
179
+ })
180
+ if (!found) { // not found
181
+ // console.log('emit search')
182
+ this.selectedItem = null
183
+ this.dispatchEvent(new CustomEvent('search', { detail: this.value }))
184
+ } else {
185
+ this.selectedItem = found
186
+ }
187
+ // console.log('emit selected?', prevItem !== this.selectedItem, this.selectedItem)
188
+ if (prevItem !== this.selectedItem) {
189
+ this.dispatchEvent(new CustomEvent('selected', { detail: this.selectedItem }))
190
+ }
191
+ }
192
+
193
+ setList(_items) {
194
+ // console.log('items', _items, this.value)
195
+ const dd = this.querySelector('datalist')
196
+ if (!dd) return
197
+ while(dd.firstChild) {
198
+ dd.removeChild(dd.lastChild)
199
+ }
200
+ if (typeof _items !== 'object') return
201
+
202
+ _items.forEach((item) => {
203
+ const li = document.createElement('option')
204
+ li.innerHTML = typeof item === 'string' ? item : item.key
205
+ li.value = typeof item === 'string' ? item : item.text
206
+ dd.appendChild(li)
207
+ })
208
+ }
209
+ }
210
+
211
+ customElements.define('bwc-autocomplete', AutoComplete)