@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,343 @@
1
+ // Combobox with autocomplete component using input, datalist and tags
2
+ // TODO fix multi select init in bwc-t4t-form.js
3
+ // TODO Initially if no data for the list, please fetch some
4
+ // TODO allow configurable classnames for tag and tag wrapper, clear icons (for bootstrap, muicss)
5
+ // TODO single select clear value if not found and custom tags not allowed
6
+ // TODO use ul/li instead of datalist (big change)
7
+ // OR https://stackoverflow.com/questions/30022728/perform-action-when-clicking-html5-datalist-option
8
+
9
+ /*
10
+ attributes:
11
+ - value (via v-model), at the text input
12
+ - required
13
+ - disabled
14
+ - listid (needed if using more than 2 components on the same page)
15
+ - input-class (style the input)
16
+ - multiple (v2)
17
+ - repeat (v2) for multiple selects allow same item to be selected many times
18
+ - allow-custom-tag (v2) - allow user defined tags
19
+ - object-key
20
+ - object-text
21
+ - tag-limit - maximum allowed tags
22
+
23
+ properties:
24
+ - items [string] or [{ key, text }]
25
+ - tags [string] or [{ key, text }]
26
+
27
+ methods:
28
+ - _setList(items) // should be private, called when items property changes
29
+ - _setTags(tags) // should be private, called with tags property changes
30
+
31
+ events emitted:
32
+ - @input (via v-model) - e.target.value
33
+ - @search - e.detail String
34
+ - @select - e.detail String or Object or null
35
+
36
+ if selected data is null (no match found, else match found)
37
+
38
+ Usage with (VueJS):
39
+
40
+ <bwc-combobox required :items="ac.items" v-model="ac.value" @search="(e) => autoComplete(e)" @select="(e) => selectItem"></bwc-combobox>
41
+
42
+ // string version
43
+ const ac = reactive({ value: 'a', items: ['aa9','aa5'] })
44
+
45
+ const autoComplete = (e) => {
46
+ const list = ['aa1', 'aa15', 'aa16', 'aa17', 'aa18', 'aa19', 'aa20', 'aa21', 'aa22', 'aa23']
47
+ const result = []
48
+ for (let i = 0; i < list.length; i++) {
49
+ if (list[i].includes(e.detail)) result.push(list[i])
50
+ }
51
+ ac.items = result
52
+ }
53
+
54
+ // object version
55
+ [
56
+ { key: 'unique', text: 'longer description' }
57
+ ]
58
+ */
59
+
60
+ const template = document.createElement('template')
61
+ template.innerHTML = /*html*/`
62
+ <input type="text" list="json-datalist" placeholder="search..." autocomplete="off">
63
+ <span class="icon is-small is-left clear-btn" style="pointer-events: all; cursor:pointer;">
64
+ <i class="fas fa-times"></i>
65
+ </span>
66
+ <datalist id="json-datalist"></datalist>
67
+ `
68
+
69
+ class BwcCombobox extends HTMLElement {
70
+ // local properties
71
+ #items = [] // list of items
72
+ #tags = [] // multi-select
73
+ #selected = null // single-select
74
+ #key = '' // must have both, other wise string is assumed?
75
+ #text = ''
76
+
77
+ #elTags = null // div.tags element
78
+ #elInput = null // input element
79
+ #elList = null // datalist element
80
+ #elClearBtn = null // clear button
81
+
82
+ #multiple = false // hold readonly attributes
83
+ #repeat = false // for multiselect, tag can be added multiple times
84
+ #allowCustomTag = false // can add new items
85
+ #tagLimit = 0 // unlimited tags
86
+
87
+ constructor() {
88
+ super()
89
+ this._onInput = this._onInput.bind(this)
90
+ }
91
+
92
+ connectedCallback() {
93
+ this.appendChild(template.content.cloneNode(true))
94
+
95
+ this.#elInput = this.querySelector('input')
96
+ this.#elList = this.querySelector('datalist')
97
+ this.#elClearBtn = this.querySelector('.clear-btn')
98
+
99
+ this.#elList.id = this.listid // console.log('listid', this.listid)
100
+ this.#elInput.setAttribute('list', this.listid)
101
+ this.#elInput.addEventListener('input', this._onInput)
102
+
103
+ this.#allowCustomTag = this.hasAttribute('allow-custom-tag')
104
+ this.#multiple = this.hasAttribute('multiple')
105
+ this.#repeat = this.hasAttribute('repeat')
106
+
107
+ if (this.hasAttribute('tag-limit')) this.#tagLimit = Number(this.getAttribute('tag-limit'))
108
+ if (this.hasAttribute('object-key')) this.#key = this.getAttribute('object-key')
109
+ if (this.hasAttribute('object-text')) this.#text = this.getAttribute('object-text')
110
+
111
+ if (this.#multiple) { // if multiple... use tags
112
+ this.#elTags = document.createElement('div')
113
+ this.#elTags.className = 'tags'
114
+ // this.prepend(this.#elTags)
115
+ this.append(this.#elTags)
116
+ }
117
+
118
+ this.#elClearBtn.onclick = (e) => {
119
+ this.#elInput.value = ''
120
+ if (!this.#multiple) {
121
+ // console.log('clear button click')
122
+ this.#selected = null
123
+ this.dispatchEvent(new CustomEvent('select', { detail: this.#selected }))
124
+ }
125
+ }
126
+ this.#elInput.onblur = (e) => {
127
+ // console.log('onblur', e)
128
+ const found = this.items.find(item => this._itemMatchInput(item))
129
+ if (this.#multiple) {
130
+ // multiple
131
+ if (!found) { // not found
132
+ if (this.#allowCustomTag) { // can add new
133
+ this._addTag(this._makeItemFromValue())
134
+ this.dispatchEvent(new CustomEvent('select', { detail: this.#tags }))
135
+ }
136
+ } else {
137
+ // if repeatable? set tags list if not there already
138
+ this._addTag(found)
139
+ this.dispatchEvent(new CustomEvent('select', { detail: this.#tags }))
140
+ }
141
+ this.value = ''
142
+ } else {
143
+ // single
144
+ if (!found) { // not found
145
+ if (this.#selected) {
146
+ console.log('onBlur - single select - not found and this.#selected truthy')
147
+ this.#selected = null
148
+ this.dispatchEvent(new CustomEvent('select', { detail: this.#selected }))
149
+ }
150
+ } else {
151
+ if (!this.#selected) {
152
+ console.log('onBlur - single select - found and this.#selected falsy')
153
+ this.#selected = found
154
+ this.dispatchEvent(new CustomEvent('select', { detail: this.#selected }))
155
+ }
156
+ }
157
+ }
158
+ }
159
+
160
+ // console.log('combo box connected', this.required, this.disabled, this.inputClass)
161
+ this.#elInput.value = this.value
162
+ this.#elInput.className = this.inputClass || 'input' // default to bulma - // if (this.hasAttribute('input-class')) el.setAttribute('class', this.getAttribute('input-class'))
163
+ this.required ? this.#elInput.setAttribute('required', '') : this.#elInput.removeAttribute('required')
164
+ this.disabled ? this.#elInput.setAttribute('disabled', '') : this.#elInput.removeAttribute('disabled')
165
+ this._setList(this.items)
166
+
167
+ this.dispatchEvent(new CustomEvent('load'))
168
+ }
169
+
170
+ disconnectedCallback() {
171
+ this.#elInput.removeEventListener('input', this._onInput)
172
+ }
173
+
174
+ attributeChangedCallback(name, oldVal, newVal) {
175
+ const el = this.#elInput
176
+ switch (name) {
177
+ case 'value': {
178
+ if (el) el.value = newVal // v-model affects this
179
+ this.dispatchEvent(new CustomEvent('input', { detail: newVal }))
180
+ break
181
+ }
182
+ case 'required': {
183
+ el && el.setAttribute('required', '')
184
+ break
185
+ }
186
+ case 'disabled': {
187
+ el && el.setAttribute('disabled', '')
188
+ break
189
+ }
190
+ case 'input-class': {
191
+ if (el) el.className = newVal
192
+ break
193
+ }
194
+ default:
195
+ break
196
+ }
197
+ }
198
+
199
+ static get observedAttributes() {
200
+ return ['value', 'required', 'listid', 'disabled', 'input-class']
201
+ }
202
+
203
+ get value() { return this.getAttribute('value') }
204
+ set value(val) { this.setAttribute('value', val) }
205
+
206
+ get required() { return this.hasAttribute('required') }
207
+ set required(val) { val ? this.setAttribute('required', '') : this.removeAttribute('required') }
208
+
209
+ get listid() { return this.getAttribute('listid') }
210
+ set listid(val) { this.setAttribute('listid', val) }
211
+
212
+ get disabled() { return this.hasAttribute('disabled') }
213
+ set disabled(val) { val ? this.setAttribute('disabled', '') : this.removeAttribute('disabled') }
214
+
215
+ get inputClass() { return this.getAttribute('input-class') }
216
+ set inputClass(val) { this.setAttribute('input-class', val) }
217
+
218
+ // properties
219
+ get items() { return this.#items }
220
+ set items(val) {
221
+ // console.log('set items', val.length)
222
+ this.#items = val
223
+ this._setList(val)
224
+ }
225
+
226
+ // multi-select
227
+ get tags() { return this.#tags }
228
+ set tags(val) { this._setTags(val) } // this.#tags will be set in _setTags()
229
+
230
+ // single-select
231
+ get selected() { return this.#selected }
232
+ set selected(val) { this.#selected = val } // TODO set it correctly
233
+
234
+ _isStringType() { // is list item and selected values string ?
235
+ return !(this.#key && this.#text) // console.log('_isStringType', !(this.#key && this.#text))
236
+ }
237
+ _itemMatchInput(item) { // item match to text input
238
+ return this._isStringType() ? item === this.value : item[this.#key] === this.value || item[this.#text] === this.value
239
+ }
240
+ _matchItems(item1, item2) { // item match to another item
241
+ if (item1 === null && item2 === null) return true
242
+ else if (item1 === null) return false
243
+ else if (item2 === null) return false
244
+ return this._isStringType() ? item1 === item2 : item1[this.#key] === item2[this.#key] || item1[this.#text] === item2[this.#text]
245
+ }
246
+ _makeItemFromValue () {
247
+ // TODO if all spaces only... return? trim white spaces?
248
+ return this._isStringType() ? this.value : { [this.#key]: this.value, [this.#text]: this.value }
249
+ }
250
+
251
+ _tagLimitReached() {
252
+ return this.#tagLimit && this.#elTags.children.length >= this.#tagLimit
253
+ }
254
+ _addTag(item) {
255
+ if (this._tagLimitReached()) return
256
+ const itemExists = this.#tags.find(tag => this._isStringType() ? tag === item : (tag[this.#key] === item[this.#key] && tag[this.#text] === item[this.#text]))
257
+ if (!this.#repeat && itemExists) return // duplicates not allowed
258
+ const span = document.createElement('span')
259
+ span.className = 'tag is-black'
260
+ span.innerText = this._isStringType() ? item : item[this.#text]
261
+ span.value = this._isStringType() ? item : item[this.#key]
262
+ span.onclick = (e) => { // e.target.innerText, e.target.value
263
+ this._removeTag(span)
264
+ this.dispatchEvent(new CustomEvent('select', { detail: this.#tags }))
265
+ }
266
+ this.#elTags.appendChild(span)
267
+ this._updateTags()
268
+ if (this._tagLimitReached()) this.#elInput.setAttribute('disabled', '')
269
+ }
270
+ _updateTags() {
271
+ let tags = [...this.#elTags.children]
272
+ this.#tags = tags.map(tag => this._isStringType() ? tag.innerText : ({ [this.#key]: tag.value, [this.#text]: tag.innerText }))
273
+ // console.log('_updateTags', this.#tags)
274
+ }
275
+ _removeTag(span) {
276
+ this.#elTags.removeChild(span)
277
+ this._updateTags()
278
+ if (!this._tagLimitReached() && !this.disabled) this.#elInput.removeAttribute('disabled')
279
+ }
280
+
281
+ _onInput(e) { // whether clicked or typed
282
+ // console.log('_onInput', e.target.value, this.items.length)
283
+ const prevItem = this.#selected
284
+ this.value = this.#elInput.value
285
+
286
+ const found = this.items.find(item => this._itemMatchInput(item))
287
+ if (!found) { // not found
288
+ this.#selected = null
289
+ this.dispatchEvent(new CustomEvent('search', { detail: this.value }))
290
+ } else {
291
+ this.#selected = found
292
+ }
293
+ if (!this._matchItems(prevItem, this.#selected) && !this.#multiple) {
294
+ console.log('_onInput - selected && provItem not match this.#selected')
295
+ if (!this.#selected && this.allowCustomTag) {
296
+ this.#selected = this._makeItemFromValue()
297
+ }
298
+ this.dispatchEvent(new CustomEvent('select', { detail: this.#selected }))
299
+ }
300
+ }
301
+
302
+ _setTags(_tags) {
303
+ // console.log('_setTags', _tags, this.#elTags)
304
+ if (!this.#elTags) return
305
+ this.#elTags.innerHTML = ''
306
+ this.#tags = []
307
+ _tags.forEach(tag => this._addTag(tag))
308
+ this.dispatchEvent(new CustomEvent('select', { detail: this.#tags }))
309
+ }
310
+
311
+ _setList(_items) { // set list items
312
+ // console.log('items', _items, this.value)
313
+ const dd = this.#elList
314
+ if (!dd) return
315
+ while(dd.firstChild) {
316
+ dd.removeChild(dd.lastChild)
317
+ }
318
+ if (typeof _items !== 'object') return
319
+
320
+ // EVENT TEST START
321
+ // dd.style.pointerEvents = 'all'
322
+ // dd.style.cursor = 'pointer'
323
+ // dd.onclick = (e) => console.log('whwhwhwh22a')
324
+ // dd.onmousedown = (e) => console.log('whwhwhwh22b')
325
+ // EVENT TEST END
326
+ _items.forEach((item) => {
327
+ const li = document.createElement('option')
328
+ li.innerHTML = typeof item === 'string' ? item : item[this.#key]
329
+ li.value = typeof item === 'string' ? item : item[this.#text]
330
+ // EVENT TEST START
331
+ // li.style.pointerEvents = 'all'
332
+ // li.style.cursor = 'pointer'
333
+ // li.onclick = (e) => console.log('whwhwhwha')
334
+ // li.onmousedown = (e) => console.log('whwhwhwhb')
335
+ // li.addEventListener('click', (e) => console.log('whwhwhwh'), true)
336
+ // li.onmousedown // useless on a datalist with listid...
337
+ // EVENT TEST END
338
+ dd.appendChild(li)
339
+ })
340
+ }
341
+ }
342
+
343
+ customElements.define('bwc-combobox', BwcCombobox)
@@ -0,0 +1,87 @@
1
+ // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file
2
+ // attributes: {
3
+ // accept: { type: String, default: '*/*' },
4
+ // value: { type: Object, required: true }
5
+ // },
6
+
7
+ // attributes
8
+ // accept: One or more unique file type specifiers describing file types to allow
9
+ // capture: What source to use for capturing image or video data
10
+ // files: A FileList listing the chosen files
11
+ // multiple: A Boolean which, if present, indicates that the user may choose more than one file
12
+ // required
13
+ // value
14
+
15
+ // Events change and input
16
+ const html = /*html*/`
17
+ <input type="text" readonly />
18
+ <input type="file" style="display: none" accept="text/csv" onclick="event.stopPropagation()" />
19
+ `
20
+
21
+ class BwcFileupload extends HTMLElement {
22
+ files = null
23
+
24
+ constructor() {
25
+ super()
26
+ this.click = this.click.bind(this)
27
+ this.change = this.change.bind(this)
28
+ // const shadowRoot = this.attachShadow({ mode: 'open' })
29
+ // const template = document.createElement('template')
30
+ // template.innerHTML = html
31
+ // // this.shadowRoot.appendChild(template.content.cloneNode(true))
32
+ // shadowRoot.innerHTML = html
33
+ }
34
+
35
+ connectedCallback() {
36
+ // this.appendChild(template.content.cloneNode(true))
37
+ this.innerHTML = html
38
+ this.files = null
39
+
40
+ // console.log('attrs', this.attributes)
41
+ if (!this.hasAttribute('value')) this.setAttribute('value', '')
42
+
43
+ if (this.hasAttribute('input-class')) this.querySelector('input[type=text]').setAttribute('class', this.getAttribute('input-class'))
44
+
45
+ if (this.hasAttribute('accept')) this.querySelector('input[type=file]').setAttribute('accept', this.getAttribute('accept'))
46
+ if (this.hasAttribute('capture')) this.querySelector('input[type=file]').setAttribute('capture', this.getAttribute('capture'))
47
+ if (this.hasAttribute('multiple')) this.querySelector('input[type=file]').setAttribute('multiple', '')
48
+ if (this.hasAttribute('required')) this.querySelector('input[type=file]').setAttribute('required', '')
49
+
50
+ this.querySelector('input[type=text]').addEventListener('click', this.click)
51
+ this.querySelector('input[type=file]').addEventListener('change', this.change)
52
+ }
53
+
54
+ disconnectedCallback() {
55
+ this.querySelector('input[type=text]').removeEventListener('click', this.click)
56
+ this.querySelector('input[type=file]').removeEventListener('change', this.change)
57
+ }
58
+
59
+ attributeChangedCallback(name, oldVal, newVal) {
60
+ const el = this.querySelector('input[type=text]')
61
+ switch (name) {
62
+ case 'value':
63
+ if (el) el.value = newVal
64
+ // console.log('bwc-fileupload', newVal)
65
+ // this.dispatchEvent(new CustomEvent('input', { detail: newVal }))
66
+ break
67
+ default: break
68
+ }
69
+ }
70
+
71
+ static get observedAttributes() { return ['value', 'class'] }
72
+ get value() { return this.getAttribute('value') }
73
+ set value(val) { this.setAttribute('value', val) }
74
+
75
+ click(e) {
76
+ this.querySelector('input[type=file]').click()
77
+ }
78
+
79
+ change(e) {
80
+ this.files = e.target.files
81
+ this.value = (this.files && this.files.length) ? Array.from(this.files).map(f => f.name).join(',') : ''
82
+ this.dispatchEvent(new CustomEvent('input', { detail: this.files }))
83
+ this.dispatchEvent(new CustomEvent('change', { detail: this.files }))
84
+ }
85
+ }
86
+
87
+ customElements.define('bwc-fileupload', BwcFileupload)
@@ -0,0 +1,54 @@
1
+ // FRONTEND ONLY
2
+ const template = document.createElement('template')
3
+ template.innerHTML = `
4
+ <style>
5
+ #overlay {
6
+ position: fixed;
7
+ top: 0; left: 0;
8
+ width: 100%; height: 100vh;
9
+ background: rgba(0,0,0,0.75);
10
+ z-index: 10;
11
+ opacity: 1;
12
+ pointer-events: all;
13
+ display: flex;
14
+ justify-content: center;
15
+ align-items: center;
16
+ }
17
+ .loader {
18
+ border: 16px solid #888;
19
+ border-top: 16px solid #fff;
20
+ border-radius: 50%;
21
+ width: 64px;
22
+ height: 64px;
23
+ animation: spin 1.5s linear infinite;
24
+ }
25
+ @-webkit-keyframes spin {
26
+ 0% { -webkit-transform: rotate(0deg); }
27
+ 100% { -webkit-transform: rotate(360deg); }
28
+ }
29
+ @keyframes spin {
30
+ 0% { transform: rotate(0deg); }
31
+ 100% { transform: rotate(360deg); }
32
+ }
33
+ </style>
34
+ <div id="overlay"><div class="loader"></div></div>
35
+ `
36
+ class LoadingOverlay extends HTMLElement {
37
+ constructor() {
38
+ super()
39
+ const shadowRoot = this.attachShadow({ mode: 'open' })
40
+ shadowRoot.appendChild(template.content.cloneNode(true))
41
+ }
42
+
43
+ static get observedAttributes() {
44
+ return ['show']
45
+ }
46
+ get show() {
47
+ return this.hasAttribute('show')
48
+ }
49
+ set show(value) {
50
+ value ? this.setAttribute('show', '') : this.removeAttribute('show')
51
+ }
52
+ }
53
+
54
+ customElements.define('bwc-loading-overlay', LoadingOverlay)