0.1.8 refactored to match .3.9 uci-websocket

supports promise connect and auto reconnect with authentification (default is token
emits 'status' events so client can monitor socket status
the example client now uses quasar 1+ and is designed to work with example server in the uci-websocket repo
also uses npm-watch dev dependency for restarting example client with changes in the consumer socket
emit consumer-connection events
This commit is contained in:
David Kebler 2019-09-08 14:26:07 -07:00
parent cb2f4a613c
commit a21aa3bf88
13 changed files with 1118 additions and 330 deletions

View file

@ -1,27 +1,31 @@
{
"name": "test-client-new",
"name": "example-client",
"version": "0.0.1",
"description": "a test websocket client for uci websocket server",
"productName": "Quasar App",
"description": "a websocket client example for use with @uci/websocket-client",
"productName": "Websocket Client",
"cordovaId": "org.cordova.quasar.app",
"author": "David Kebler <d@kebler.net>",
"private": true,
"scripts": {
"lint": "eslint --ext .js,.vue src",
"tc": "./node_modules/.bin/quasar dev",
"tc:nas": "WSS='ws://ws.kebler.net' npm run tc"
"client": "quasar dev"
},
"dependencies": {
"@quasar/extras": "^1.2.0",
"quasar": "^1.0.0"
"@quasar/extras": "^1.3.1",
"@uci/websocket-client": "file:../..",
"auto-bind": "^2.1.0",
"better-try-catch": "^0.6.2",
"delay": "^4.3.0",
"quasar": "^1.1.0",
"rfs": "^9.0.0"
},
"devDependencies": {
"@quasar/app": "^1.0.0",
"@quasar/app": "^1.0.6",
"@vue/eslint-config-standard": "^4.0.0",
"babel-eslint": "^10.0.1",
"eslint": "^5.10.0",
"eslint-loader": "^2.1.1",
"eslint-plugin-vue": "^5.0.0"
"babel-eslint": "^10.0.3",
"eslint": "^6.3.0",
"eslint-loader": "^3.0.0",
"eslint-plugin-vue": "^5.2.3"
},
"engines": {
"node": ">= 8.9.0",

View file

@ -46,7 +46,9 @@ module.exports = function (ctx) {
'QSelect',
'QField',
'QFooter',
'QTooltip'
'QTooltip',
'QBadge',
'QScrollArea'
],
directives: [

View file

@ -1,5 +1,4 @@
// import WebSocket from '@uci/websocket-client'
import WebSocket from '../../../../src'
import WebSocket from '@uci/websocket-client'
const ws = new WebSocket(process.env.WSS || 'ws://0.0.0.0:8090')

View file

@ -1,4 +1,37 @@
// app global css
// from https://github.com/quasarframework/quasar/tree/dev/ui/src/css
@import "../../node_modules/rfs/stylus"
$rfs-base-font-size = 1.25rem
// $rfs-font-size-unit = rem
$rfs-breakpoint = 1400px
// $rfs-breakpoint-unit = px
// $rfs-factor = 10
// $rfs-rem-value = 16
$rfs-two-dimensional = true
// $rfs-class = false
$field-font-factor = 1.3
$field-label-font-factor = 1
.q-field__native
.q-item__label
rfs($rfs-base-font-size * $field-font-factor)
.q-field__label
&.no-pointer-events
rfs($rfs-base-font-size * $field-font-factor)
.q-field__label
rfs($rfs-base-font-size * $field-label-font-factor)
padding-bottom .3em // needs to be dynamic based on font size
.q-field__control
// height +??px // needs to be computed based on font size.
.q-page-container
background $accent
display: flex;
@ -20,3 +53,7 @@
.q-item
padding 0
margin 0
.greyedout
opacity 0.5
input
fontsize-12

View file

@ -0,0 +1,639 @@
$space-base ?= 16px
$space-x-base ?= $space-base
$space-y-base ?= $space-base
$spaces ?= {
none: {
x: 0,
y: 0
},
xs: {
x: ($space-x-base * .25),
y: ($space-y-base * .25)
},
sm: {
x: ($space-x-base * .5),
y: ($space-y-base * .5)
},
md: {
x: $space-x-base,
y: $space-y-base
},
lg: {
x: ($space-x-base * 1.5),
y: ($space-y-base * 1.5)
},
xl: {
x: ($space-x-base * 3),
y: ($space-y-base * 3)
}
}
// Max width at which point
// current size ends
$breakpoint-xs ?= 599px
$breakpoint-sm ?= 1023px
$breakpoint-md ?= 1439px
$breakpoint-lg ?= 1919px
$flex-cols ?= 12
$flex-gutter-xs ?= ($space-base * .25)
$flex-gutter-sm ?= ($space-base * .5)
$flex-gutter-md ?= $space-base
$flex-gutter-lg ?= ($space-base * 1.5)
$flex-gutter-xl ?= ($space-base * 3)
$body-font-size ?= 14px
$body-line-height ?= 1.5
$flex-gutter ?= {
none: 0,
xs: $flex-gutter-xs,
sm: $flex-gutter-sm,
md: $flex-gutter-md,
lg: $flex-gutter-lg,
xl: $flex-gutter-xl
}
$sizes ?= {
xs: 0, // Extra small screen
sm: $breakpoint-xs + 1, // Small screen
md: $breakpoint-sm + 1, // Medium screen
lg: $breakpoint-md + 1, // Large screen
xl: $breakpoint-lg + 1 // Extra large screen
}
$breakpoint-xs-min ?= 0
$breakpoint-xs-max ?= $breakpoint-xs
$breakpoint-sm-min ?= $sizes.sm
$breakpoint-sm-max ?= $breakpoint-sm
$breakpoint-md-min ?= $sizes.md
$breakpoint-md-max ?= $breakpoint-md
$breakpoint-lg-min ?= $sizes.lg
$breakpoint-lg-max ?= $breakpoint-lg
$breakpoint-xl-min ?= $sizes.xl
$breakpoint-xl-max ?= 9999px
$headings ?= {
h1: {
size: 6rem,
line-height: 6rem,
weight: 300,
letter-spacing: -.01562em
},
h2: {
size: 3.75rem,
line-height: 3.75rem,
letter-spacing: -.00833em,
weight: 300
},
h3: {
size: 3rem,
line-height: 3.125rem,
letter-spacing: normal,
weight: 400
},
h4: {
size: 2.125rem,
line-height: 2.5rem,
letter-spacing: .00735em,
weight: 400
},
h5: {
size: 1.5rem,
line-height: 2rem,
letter-spacing: normal,
weight: 400
},
h6: {
size: 1.25rem,
line-height: 2rem,
letter-spacing: .0125em,
weight: 500
},
subtitle1: {
size: 1rem,
line-height: 1.75rem,
letter-spacing: .00937em,
weight: 400
},
subtitle2: {
size: .875rem,
line-height: 1.375rem,
letter-spacing: .00714em,
weight: 500
},
body1: {
size: 1rem,
line-height: 1.5rem,
letter-spacing: .03125em,
weight: 400
},
body2: {
size: .875rem,
line-height: 1.25rem,
letter-spacing: .01786em,
weight: 400
},
overline: {
size: .75rem,
line-height: 2rem,
letter-spacing: .16667em,
weight: 500
},
caption: {
size: .75rem,
line-height: 1.25rem,
letter-spacing: .03333em,
weight: 400
}
}
$h-tags ?= {
h1: $headings.h1,
h2: $headings.h2,
h3: $headings.h3,
h4: $headings.h4,
h5: $headings.h5,
h6: $headings.h6
}
$text-weights ?= {
thin: 100,
light: 300,
regular: 400,
medium: 500,
bold: 700,
bolder: 900
}
$primary ?= #027BE3
$secondary ?= #26A69A
$accent ?= #9C27B0
$positive ?= #21BA45
$negative ?= #C10015
$info ?= #31CCEC
$warning ?= #F2C037
$light ?= #bdbdbd
$dark ?= #424242
$faded ?= #777
$dimmed-background ?= rgba(0, 0, 0, .4)
$light-dimmed-background ?= rgba(255, 255, 255, .6)
$separator-color ?= rgba(0, 0, 0, .12)
$separator-dark-color ?= rgba(255, 255, 255, .48)
$red ?= #f44336
$red-1 ?= #ffebee
$red-2 ?= #ffcdd2
$red-3 ?= #ef9a9a
$red-4 ?= #e57373
$red-5 ?= #ef5350
$red-6 ?= #f44336
$red-7 ?= #e53935
$red-8 ?= #d32f2f
$red-9 ?= #c62828
$red-10 ?= #b71c1c
$red-11 ?= #ff8a80
$red-12 ?= #ff5252
$red-13 ?= #ff1744
$red-14 ?= #d50000
$pink ?= #e91e63
$pink-1 ?= #fce4ec
$pink-2 ?= #f8bbd0
$pink-3 ?= #f48fb1
$pink-4 ?= #f06292
$pink-5 ?= #ec407a
$pink-6 ?= #e91e63
$pink-7 ?= #d81b60
$pink-8 ?= #c2185b
$pink-9 ?= #ad1457
$pink-10 ?= #880e4f
$pink-11 ?= #ff80ab
$pink-12 ?= #ff4081
$pink-13 ?= #f50057
$pink-14 ?= #c51162
$purple ?= #9c27b0
$purple-1 ?= #f3e5f5
$purple-2 ?= #e1bee7
$purple-3 ?= #ce93d8
$purple-4 ?= #ba68c8
$purple-5 ?= #ab47bc
$purple-6 ?= #9c27b0
$purple-7 ?= #8e24aa
$purple-8 ?= #7b1fa2
$purple-9 ?= #6a1b9a
$purple-10 ?= #4a148c
$purple-11 ?= #ea80fc
$purple-12 ?= #e040fb
$purple-13 ?= #d500f9
$purple-14 ?= #aa00ff
$deep-purple ?= #673ab7
$deep-purple-1 ?= #ede7f6
$deep-purple-2 ?= #d1c4e9
$deep-purple-3 ?= #b39ddb
$deep-purple-4 ?= #9575cd
$deep-purple-5 ?= #7e57c2
$deep-purple-6 ?= #673ab7
$deep-purple-7 ?= #5e35b1
$deep-purple-8 ?= #512da8
$deep-purple-9 ?= #4527a0
$deep-purple-10 ?= #311b92
$deep-purple-11 ?= #b388ff
$deep-purple-12 ?= #7c4dff
$deep-purple-13 ?= #651fff
$deep-purple-14 ?= #6200ea
$indigo ?= #3f51b5
$indigo-1 ?= #e8eaf6
$indigo-2 ?= #c5cae9
$indigo-3 ?= #9fa8da
$indigo-4 ?= #7986cb
$indigo-5 ?= #5c6bc0
$indigo-6 ?= #3f51b5
$indigo-7 ?= #3949ab
$indigo-8 ?= #303f9f
$indigo-9 ?= #283593
$indigo-10 ?= #1a237e
$indigo-11 ?= #8c9eff
$indigo-12 ?= #536dfe
$indigo-13 ?= #3d5afe
$indigo-14 ?= #304ffe
$blue ?= #2196f3
$blue-1 ?= #e3f2fd
$blue-2 ?= #bbdefb
$blue-3 ?= #90caf9
$blue-4 ?= #64b5f6
$blue-5 ?= #42a5f5
$blue-6 ?= #2196f3
$blue-7 ?= #1e88e5
$blue-8 ?= #1976d2
$blue-9 ?= #1565c0
$blue-10 ?= #0d47a1
$blue-11 ?= #82b1ff
$blue-12 ?= #448aff
$blue-13 ?= #2979ff
$blue-14 ?= #2962ff
$light-blue ?= #03a9f4
$light-blue-1 ?= #e1f5fe
$light-blue-2 ?= #b3e5fc
$light-blue-3 ?= #81d4fa
$light-blue-4 ?= #4fc3f7
$light-blue-5 ?= #29b6f6
$light-blue-6 ?= #03a9f4
$light-blue-7 ?= #039be5
$light-blue-8 ?= #0288d1
$light-blue-9 ?= #0277bd
$light-blue-10 ?= #01579b
$light-blue-11 ?= #80d8ff
$light-blue-12 ?= #40c4ff
$light-blue-13 ?= #00b0ff
$light-blue-14 ?= #0091ea
$cyan ?= #00bcd4
$cyan-1 ?= #e0f7fa
$cyan-2 ?= #b2ebf2
$cyan-3 ?= #80deea
$cyan-4 ?= #4dd0e1
$cyan-5 ?= #26c6da
$cyan-6 ?= #00bcd4
$cyan-7 ?= #00acc1
$cyan-8 ?= #0097a7
$cyan-9 ?= #00838f
$cyan-10 ?= #006064
$cyan-11 ?= #84ffff
$cyan-12 ?= #18ffff
$cyan-13 ?= #00e5ff
$cyan-14 ?= #00b8d4
$teal ?= #009688
$teal-1 ?= #e0f2f1
$teal-2 ?= #b2dfdb
$teal-3 ?= #80cbc4
$teal-4 ?= #4db6ac
$teal-5 ?= #26a69a
$teal-6 ?= #009688
$teal-7 ?= #00897b
$teal-8 ?= #00796b
$teal-9 ?= #00695c
$teal-10 ?= #004d40
$teal-11 ?= #a7ffeb
$teal-12 ?= #64ffda
$teal-13 ?= #1de9b6
$teal-14 ?= #00bfa5
$green ?= #4caf50
$green-1 ?= #e8f5e9
$green-2 ?= #c8e6c9
$green-3 ?= #a5d6a7
$green-4 ?= #81c784
$green-5 ?= #66bb6a
$green-6 ?= #4caf50
$green-7 ?= #43a047
$green-8 ?= #388e3c
$green-9 ?= #2e7d32
$green-10 ?= #1b5e20
$green-11 ?= #b9f6ca
$green-12 ?= #69f0ae
$green-13 ?= #00e676
$green-14 ?= #00c853
$light-green ?= #8bc34a
$light-green-1 ?= #f1f8e9
$light-green-2 ?= #dcedc8
$light-green-3 ?= #c5e1a5
$light-green-4 ?= #aed581
$light-green-5 ?= #9ccc65
$light-green-6 ?= #8bc34a
$light-green-7 ?= #7cb342
$light-green-8 ?= #689f38
$light-green-9 ?= #558b2f
$light-green-10 ?= #33691e
$light-green-11 ?= #ccff90
$light-green-12 ?= #b2ff59
$light-green-13 ?= #76ff03
$light-green-14 ?= #64dd17
$lime ?= #cddc39
$lime-1 ?= #f9fbe7
$lime-2 ?= #f0f4c3
$lime-3 ?= #e6ee9c
$lime-4 ?= #dce775
$lime-5 ?= #d4e157
$lime-6 ?= #cddc39
$lime-7 ?= #c0ca33
$lime-8 ?= #afb42b
$lime-9 ?= #9e9d24
$lime-10 ?= #827717
$lime-11 ?= #f4ff81
$lime-12 ?= #eeff41
$lime-13 ?= #c6ff00
$lime-14 ?= #aeea00
$yellow ?= #ffeb3b
$yellow-1 ?= #fffde7
$yellow-2 ?= #fff9c4
$yellow-3 ?= #fff59d
$yellow-4 ?= #fff176
$yellow-5 ?= #ffee58
$yellow-6 ?= #ffeb3b
$yellow-7 ?= #fdd835
$yellow-8 ?= #fbc02d
$yellow-9 ?= #f9a825
$yellow-10 ?= #f57f17
$yellow-11 ?= #ffff8d
$yellow-12 ?= #ffff00
$yellow-13 ?= #ffea00
$yellow-14 ?= #ffd600
$amber ?= #ffc107
$amber-1 ?= #fff8e1
$amber-2 ?= #ffecb3
$amber-3 ?= #ffe082
$amber-4 ?= #ffd54f
$amber-5 ?= #ffca28
$amber-6 ?= #ffc107
$amber-7 ?= #ffb300
$amber-8 ?= #ffa000
$amber-9 ?= #ff8f00
$amber-10 ?= #ff6f00
$amber-11 ?= #ffe57f
$amber-12 ?= #ffd740
$amber-13 ?= #ffc400
$amber-14 ?= #ffab00
$orange ?= #ff9800
$orange-1 ?= #fff3e0
$orange-2 ?= #ffe0b2
$orange-3 ?= #ffcc80
$orange-4 ?= #ffb74d
$orange-5 ?= #ffa726
$orange-6 ?= #ff9800
$orange-7 ?= #fb8c00
$orange-8 ?= #f57c00
$orange-9 ?= #ef6c00
$orange-10 ?= #e65100
$orange-11 ?= #ffd180
$orange-12 ?= #ffab40
$orange-13 ?= #ff9100
$orange-14 ?= #ff6d00
$deep-orange ?= #ff5722
$deep-orange-1 ?= #fbe9e7
$deep-orange-2 ?= #ffccbc
$deep-orange-3 ?= #ffab91
$deep-orange-4 ?= #ff8a65
$deep-orange-5 ?= #ff7043
$deep-orange-6 ?= #ff5722
$deep-orange-7 ?= #f4511e
$deep-orange-8 ?= #e64a19
$deep-orange-9 ?= #d84315
$deep-orange-10 ?= #bf360c
$deep-orange-11 ?= #ff9e80
$deep-orange-12 ?= #ff6e40
$deep-orange-13 ?= #ff3d00
$deep-orange-14 ?= #dd2c00
$brown ?= #795548
$brown-1 ?= #efebe9
$brown-2 ?= #d7ccc8
$brown-3 ?= #bcaaa4
$brown-4 ?= #a1887f
$brown-5 ?= #8d6e63
$brown-6 ?= #795548
$brown-7 ?= #6d4c41
$brown-8 ?= #5d4037
$brown-9 ?= #4e342e
$brown-10 ?= #3e2723
$brown-11 ?= #d7ccc8
$brown-12 ?= #bcaaa4
$brown-13 ?= #8d6e63
$brown-14 ?= #5d4037
$grey ?= #9e9e9e
$grey-1 ?= #fafafa
$grey-2 ?= #f5f5f5
$grey-3 ?= #eeeeee
$grey-4 ?= #e0e0e0
$grey-5 ?= #bdbdbd
$grey-6 ?= #9e9e9e
$grey-7 ?= #757575
$grey-8 ?= #616161
$grey-9 ?= #424242
$grey-10 ?= #212121
$grey-11 ?= #f5f5f5
$grey-12 ?= #eeeeee
$grey-13 ?= #bdbdbd
$grey-14 ?= #616161
$blue-grey ?= #607d8b
$blue-grey-1 ?= #eceff1
$blue-grey-2 ?= #cfd8dc
$blue-grey-3 ?= #b0bec5
$blue-grey-4 ?= #90a4ae
$blue-grey-5 ?= #78909c
$blue-grey-6 ?= #607d8b
$blue-grey-7 ?= #546e7a
$blue-grey-8 ?= #455a64
$blue-grey-9 ?= #37474f
$blue-grey-10 ?= #263238
$blue-grey-11 ?= #cfd8dc
$blue-grey-12 ?= #b0bec5
$blue-grey-13 ?= #78909c
$blue-grey-14 ?= #455a64
$ios-statusbar-height ?= 20px
$z-fab ?= 990
$z-side ?= 1000
$z-marginals ?= 2000
$z-fixed-drawer ?= 3000
$z-fullscreen ?= 6000
$z-menu ?= 6000
$z-top ?= 7000
$z-tooltip ?= 9000
$z-notify ?= 9500
$z-max ?= 9998
$shadow-color ?= black
$shadow-transition ?= box-shadow .28s cubic-bezier(.4, 0, .2, 1)
$inset-shadow ?= 0 7px 9px -7px rgba($shadow-color, .7) inset
$elevation-umbra ?= rgba($shadow-color, .2)
$elevation-penumbra ?= rgba($shadow-color, .14)
$elevation-ambient ?= rgba($shadow-color, .12)
$shadow-0 ?= 0 0 0 $elevation-umbra, 0 0 0 $elevation-penumbra, 0 0 0 $elevation-ambient
$shadow-1 ?= 0 1px 3px $elevation-umbra, 0 1px 1px $elevation-penumbra, 0 2px 1px -1px $elevation-ambient
$shadow-2 ?= 0 1px 5px $elevation-umbra, 0 2px 2px $elevation-penumbra, 0 3px 1px -2px $elevation-ambient
$shadow-3 ?= 0 1px 8px $elevation-umbra, 0 3px 4px $elevation-penumbra, 0 3px 3px -2px $elevation-ambient
$shadow-4 ?= 0 2px 4px -1px $elevation-umbra, 0 4px 5px $elevation-penumbra, 0 1px 10px $elevation-ambient
$shadow-5 ?= 0 3px 5px -1px $elevation-umbra, 0 5px 8px $elevation-penumbra, 0 1px 14px $elevation-ambient
$shadow-6 ?= 0 3px 5px -1px $elevation-umbra, 0 6px 10px $elevation-penumbra, 0 1px 18px $elevation-ambient
$shadow-7 ?= 0 4px 5px -2px $elevation-umbra, 0 7px 10px 1px $elevation-penumbra, 0 2px 16px 1px $elevation-ambient
$shadow-8 ?= 0 5px 5px -3px $elevation-umbra, 0 8px 10px 1px $elevation-penumbra, 0 3px 14px 2px $elevation-ambient
$shadow-9 ?= 0 5px 6px -3px $elevation-umbra, 0 9px 12px 1px $elevation-penumbra, 0 3px 16px 2px $elevation-ambient
$shadow-10 ?= 0 6px 6px -3px $elevation-umbra, 0 10px 14px 1px $elevation-penumbra, 0 4px 18px 3px $elevation-ambient
$shadow-11 ?= 0 6px 7px -4px $elevation-umbra, 0 11px 15px 1px $elevation-penumbra, 0 4px 20px 3px $elevation-ambient
$shadow-12 ?= 0 7px 8px -4px $elevation-umbra, 0 12px 17px 2px $elevation-penumbra, 0 5px 22px 4px $elevation-ambient
$shadow-13 ?= 0 7px 8px -4px $elevation-umbra, 0 13px 19px 2px $elevation-penumbra, 0 5px 24px 4px $elevation-ambient
$shadow-14 ?= 0 7px 9px -4px $elevation-umbra, 0 14px 21px 2px $elevation-penumbra, 0 5px 26px 4px $elevation-ambient
$shadow-15 ?= 0 8px 9px -5px $elevation-umbra, 0 15px 22px 2px $elevation-penumbra, 0 6px 28px 5px $elevation-ambient
$shadow-16 ?= 0 8px 10px -5px $elevation-umbra, 0 16px 24px 2px $elevation-penumbra, 0 6px 30px 5px $elevation-ambient
$shadow-17 ?= 0 8px 11px -5px $elevation-umbra, 0 17px 26px 2px $elevation-penumbra, 0 6px 32px 5px $elevation-ambient
$shadow-18 ?= 0 9px 11px -5px $elevation-umbra, 0 18px 28px 2px $elevation-penumbra, 0 7px 34px 6px $elevation-ambient
$shadow-19 ?= 0 9px 12px -6px $elevation-umbra, 0 19px 29px 2px $elevation-penumbra, 0 7px 36px 6px $elevation-ambient
$shadow-20 ?= 0 10px 13px -6px $elevation-umbra, 0 20px 31px 3px $elevation-penumbra, 0 8px 38px 7px $elevation-ambient
$shadow-21 ?= 0 10px 13px -6px $elevation-umbra, 0 21px 33px 3px $elevation-penumbra, 0 8px 40px 7px $elevation-ambient
$shadow-22 ?= 0 10px 14px -6px $elevation-umbra, 0 22px 35px 3px $elevation-penumbra, 0 8px 42px 7px $elevation-ambient
$shadow-23 ?= 0 11px 14px -7px $elevation-umbra, 0 23px 36px 3px $elevation-penumbra, 0 9px 44px 8px $elevation-ambient
$shadow-24 ?= 0 11px 15px -7px $elevation-umbra, 0 24px 38px 3px $elevation-penumbra, 0 9px 46px 8px $elevation-ambient
$shadow-up-0 ?= 0 0 0 $elevation-umbra, 0 0 0 $elevation-penumbra, 0 0 0 $elevation-ambient
$shadow-up-1 ?= 0 -1px 3px $elevation-umbra, 0 -1px 1px $elevation-penumbra, 0 -2px 1px -1px $elevation-ambient
$shadow-up-2 ?= 0 -1px 5px $elevation-umbra, 0 -2px 2px $elevation-penumbra, 0 -3px 1px -2px $elevation-ambient
$shadow-up-3 ?= 0 -1px 8px $elevation-umbra, 0 -3px 4px $elevation-penumbra, 0 -3px 3px -2px $elevation-ambient
$shadow-up-4 ?= 0 -2px 4px -1px $elevation-umbra, 0 -4px 5px $elevation-penumbra, 0 -1px 10px $elevation-ambient
$shadow-up-5 ?= 0 -3px 5px -1px $elevation-umbra, 0 -5px 8px $elevation-penumbra, 0 -1px 14px $elevation-ambient
$shadow-up-6 ?= 0 -3px 5px -1px $elevation-umbra, 0 -6px 10px $elevation-penumbra, 0 -1px 18px $elevation-ambient
$shadow-up-7 ?= 0 -4px 5px -2px $elevation-umbra, 0 -7px 10px 1px $elevation-penumbra, 0 -2px 16px 1px $elevation-ambient
$shadow-up-8 ?= 0 -5px 5px -3px $elevation-umbra, 0 -8px 10px 1px $elevation-penumbra, 0 -3px 14px 2px $elevation-ambient
$shadow-up-9 ?= 0 -5px 6px -3px $elevation-umbra, 0 -9px 12px 1px $elevation-penumbra, 0 -3px 16px 2px $elevation-ambient
$shadow-up-10 ?= 0 -6px 6px -3px $elevation-umbra, 0 -10px 14px 1px $elevation-penumbra, 0 -4px 18px 3px $elevation-ambient
$shadow-up-11 ?= 0 -6px 7px -4px $elevation-umbra, 0 -11px 15px 1px $elevation-penumbra, 0 -4px 20px 3px $elevation-ambient
$shadow-up-12 ?= 0 -7px 8px -4px $elevation-umbra, 0 -12px 17px 2px $elevation-penumbra, 0 -5px 22px 4px $elevation-ambient
$shadow-up-13 ?= 0 -7px 8px -4px $elevation-umbra, 0 -13px 19px 2px $elevation-penumbra, 0 -5px 24px 4px $elevation-ambient
$shadow-up-14 ?= 0 -7px 9px -4px $elevation-umbra, 0 -14px 21px 2px $elevation-penumbra, 0 -5px 26px 4px $elevation-ambient
$shadow-up-15 ?= 0 -8px 9px -5px $elevation-umbra, 0 -15px 22px 2px $elevation-penumbra, 0 -6px 28px 5px $elevation-ambient
$shadow-up-16 ?= 0 -8px 10px -5px $elevation-umbra, 0 -16px 24px 2px $elevation-penumbra, 0 -6px 30px 5px $elevation-ambient
$shadow-up-17 ?= 0 -8px 11px -5px $elevation-umbra, 0 -17px 26px 2px $elevation-penumbra, 0 -6px 32px 5px $elevation-ambient
$shadow-up-18 ?= 0 -9px 11px -5px $elevation-umbra, 0 -18px 28px 2px $elevation-penumbra, 0 -7px 34px 6px $elevation-ambient
$shadow-up-19 ?= 0 -9px 12px -6px $elevation-umbra, 0 -19px 29px 2px $elevation-penumbra, 0 -7px 36px 6px $elevation-ambient
$shadow-up-20 ?= 0 -10px 13px -6px $elevation-umbra, 0 -20px 31px 3px $elevation-penumbra, 0 -8px 38px 7px $elevation-ambient
$shadow-up-21 ?= 0 -10px 13px -6px $elevation-umbra, 0 -21px 33px 3px $elevation-penumbra, 0 -8px 40px 7px $elevation-ambient
$shadow-up-22 ?= 0 -10px 14px -6px $elevation-umbra, 0 -22px 35px 3px $elevation-penumbra, 0 -8px 42px 7px $elevation-ambient
$shadow-up-23 ?= 0 -11px 14px -7px $elevation-umbra, 0 -23px 36px 3px $elevation-penumbra, 0 -9px 44px 8px $elevation-ambient
$shadow-up-24 ?= 0 -11px 15px -7px $elevation-umbra, 0 -24px 38px 3px $elevation-penumbra, 0 -9px 46px 8px $elevation-ambient
$generic-border-radius ?= 4px
$generic-hover-transition ?= .3s cubic-bezier(.25, .8, .5, 1)
$typography-font-family ?= 'Roboto', '-apple-system', 'Helvetica Neue', Helvetica, Arial, sans-serif
$min-line-height ?= 1.12
$button-border-radius ?= 3px
$button-padding ?= 4px 16px
$button-dense-padding ?= .285em
$button-transition ?= $generic-hover-transition
$button-font-size ?= 14px
$button-line-height ?= 1.718em
$button-font-weight ?= 500
$button-shadow ?= $shadow-2
$button-shadow-active ?= $shadow-5
$button-rounded-border-radius ?= 28px
$button-push-border-radius ?= 7px
$chat-message-received-color ?= black
$chat-message-received-bg ?= $green-4
$chat-message-sent-color ?= black
$chat-message-sent-bg ?= $grey-4
$chat-message-avatar-size ?= 48px
$chat-message-border-radius ?= $generic-border-radius
$chat-message-distance ?= 8px
$chat-message-text-padding ?= 8px
$item-base-color ?= $grey-5
$editor-border-color ?= $separator-color
$editor-content-padding ?= 10px
$editor-content-min-height ?= 10em
$editor-toolbar-padding ?= 4px
$editor-hr-color ?= $editor-border-color
$editor-button-gutter ?= 4px
$fab-margin ?= 5px
$table-transition ?= $generic-hover-transition
$table-border-radius ?= $generic-border-radius
$table-box-shadow ?= $shadow-2
$table-border-color ?= $separator-color
$table-hover-background ?= rgba(0, 0, 0, .03)
$table-selected-background ?= rgba(0, 0, 0, .06)
$table-dark-border-color ?= $separator-dark-color
$table-dark-hover-background ?= rgba(255, 255, 255, .07)
$table-dark-selected-background ?= rgba(255, 255, 255, .1)
$toolbar-min-height ?= 50px
$toolbar-padding ?= 0 12px
$toolbar-inset-size ?= 58px
$toolbar-title-font-size ?= 21px
$toolbar-title-font-weight ?= normal
$toolbar-title-letter-spacing ?= .01em
$toolbar-title-padding ?= 0 12px
$layout-border ?= 1px solid $separator-color
$layout-shadow ?= 0 0 10px 2px rgba(0,0,0,0.2), 0 0px 10px rgba(0,0,0,0.24)
$menu-background ?= white
$menu-box-shadow ?= $shadow-2
$menu-max-width ?= 95vw
$rating-grade-color ?= $yellow
$rating-shadow ?= 0 1px 3px rgba(0, 0, 0, .12), 0 1px 2px rgba(0, 0, 0, .24)
$tooltip-color ?= #fafafa
$tooltip-background ?= $grey-7
$tooltip-padding ?= 6px 10px
$tooltip-border-radius ?= $generic-border-radius
$tooltip-fontsize ?= 10px
$tooltip-mobile-padding ?= 8px 16px
$tooltip-mobile-fontsize ?= 14px
$option-focus-transition ?= .22s cubic-bezier(0,0,.2,1)
$input-font-size ?= 14px
$input-text-color ?= rgba(0,0,0,.87)
$input-label-color ?= rgba(0,0,0,.6)
$input-autofill-color ?= inherit
$img-width ?= 100%
$img-background-repeat ?= no-repeat
$img-loading-font-size ?= 50px
$img-content-position ?= absolute
$img-content-padding ?= 16px
$img-content-color ?= white
$img-content-background ?= rgba(0, 0, 0, .47)

View file

@ -1,110 +0,0 @@
<template>
<q-layout view="lHh Lpr lFf">
<q-header elevated>
<q-toolbar>
<q-btn
flat
dense
round
@click="leftDrawerOpen = !leftDrawerOpen"
aria-label="Menu"
>
<q-icon name="menu" />
</q-btn>
<q-toolbar-title>
Quasar App
</q-toolbar-title>
<div>Quasar v{{ $q.version }}</div>
</q-toolbar>
</q-header>
<q-drawer
v-model="leftDrawerOpen"
bordered
content-class="bg-grey-2"
>
<q-list>
<q-item-label header>Essential Links</q-item-label>
<q-item clickable tag="a" target="_blank" href="https://quasar.dev">
<q-item-section avatar>
<q-icon name="school" />
</q-item-section>
<q-item-section>
<q-item-label>Docs</q-item-label>
<q-item-label caption>quasar.dev</q-item-label>
</q-item-section>
</q-item>
<q-item clickable tag="a" target="_blank" href="https://github.quasar.dev">
<q-item-section avatar>
<q-icon name="code" />
</q-item-section>
<q-item-section>
<q-item-label>Github</q-item-label>
<q-item-label caption>github.com/quasarframework</q-item-label>
</q-item-section>
</q-item>
<q-item clickable tag="a" target="_blank" href="https://chat.quasar.dev">
<q-item-section avatar>
<q-icon name="chat" />
</q-item-section>
<q-item-section>
<q-item-label>Discord Chat Channel</q-item-label>
<q-item-label caption>chat.quasar.dev</q-item-label>
</q-item-section>
</q-item>
<q-item clickable tag="a" target="_blank" href="https://forum.quasar.dev">
<q-item-section avatar>
<q-icon name="record_voice_over" />
</q-item-section>
<q-item-section>
<q-item-label>Forum</q-item-label>
<q-item-label caption>forum.quasar.dev</q-item-label>
</q-item-section>
</q-item>
<q-item clickable tag="a" target="_blank" href="https://twitter.quasar.dev">
<q-item-section avatar>
<q-icon name="rss_feed" />
</q-item-section>
<q-item-section>
<q-item-label>Twitter</q-item-label>
<q-item-label caption>@quasarframework</q-item-label>
</q-item-section>
</q-item>
<q-item clickable tag="a" target="_blank" href="https://facebook.quasar.dev">
<q-item-section avatar>
<q-icon name="public" />
</q-item-section>
<q-item-section>
<q-item-label>Facebook</q-item-label>
<q-item-label caption>@QuasarFramework</q-item-label>
</q-item-section>
</q-item>
</q-list>
</q-drawer>
<q-page-container>
<router-view />
</q-page-container>
</q-layout>
</template>
<script>
import { openURL } from 'quasar'
export default {
name: 'MyLayout',
data () {
return {
leftDrawerOpen: this.$q.platform.is.desktop
}
},
methods: {
openURL
}
}
</script>
<style>
</style>

View file

@ -4,7 +4,7 @@
<q-header elevated class="bg-primary text-white">
<q-toolbar>
<q-toolbar-title>
Title
Websocket Client for Four In One UCI Base Example
</q-toolbar-title>
</q-toolbar>
</q-header>
@ -14,11 +14,7 @@
</q-page-container>
<q-footer elevated class="bg-grey-8 text-white">
<q-toolbar>
<q-toolbar-title>
Title
</q-toolbar-title>
</q-toolbar>
</q-footer>
</q-layout>
@ -28,6 +24,7 @@
export default {
data () {
return {
status: []
}
}
}

View file

@ -1,10 +1,10 @@
<template>
<q-page padding class="" >
<!-- <q-page padding > :class="{greyedout: !connected}" > -->
<q-page padding >
<q-list class="">
<q-item class="">
<q-item-section side>
<q-btn color="secondary" round @click="send" icon="send">
<q-btn :disabled="!connected" color="secondary" round @click="send" icon="send">
<q-tooltip>
Click to send this command and payload to the sever
</q-tooltip>
@ -12,8 +12,6 @@
</q-item-section>
<q-item-section >
<q-item-label>Command</q-item-label>
<!-- <q-input label="Enter a . delimited command to send to server" v-model="cmd" />
-->
<q-select
v-model="cmd"
:options="commands"
@ -25,114 +23,234 @@
<q-item><div class="col">Property</div>:<div class="col">Value</div></q-item>
<q-item><q-input class="col" readonly v-model="key[0]" />:<q-input dense class="col" v-model="value[0]" /></q-item>
<q-item><q-input class="col" readonly v-model="key[1]" />:<q-input class="col" v-model="value[1]" /></q-item>
<q-item><q-input class="col" label="a custom property in payload" v-model="key[2]" />:<q-input class="col" v-model="value[2]" /></q-item>
<q-item><q-input class="col" label="a custom property in payload" v-model="key[1]" />:<q-input class="col" v-model="value[2]" /></q-item>
</q-list>
<!-- <q-btn :class="switches[0]" @click="toggle(1)">Switch 1</q-btn>
<q-btn :class="switches[1]" @click="toggle(2)">Switch 2</q-btn>
<q-btn :class="switches[2]" @click="toggle(3)">Switch 3</q-btn>
<q-btn :class="switches[3]" @click="toggle(4)">Switch 4</q-btn> -->
<q-btn :class="switches[0]" @click="toggle(1)" label="Switch 1"><q-tooltip>Click to Toggle Switch</q-tooltip></q-btn>
<q-btn :class="switches[1]" @click="toggle(2)" label="Switch 2"><q-tooltip>Click to Toggle Switch</q-tooltip></q-btn>
<q-btn :class="switches[2]" @click="toggle(3)" label="Switch 3"><q-tooltip>Click to Toggle Switch</q-tooltip></q-btn>
<q-btn :class="switches[3]" @click="toggle(4)" label="Switch 4"><q-tooltip>Click to Toggle Switch</q-tooltip></q-btn>
<q-list class="response">
<q-input v-model="packet" readonly label="Sending Packet:" />
<q-input v-model="resMsg" readonly label="Socket/Server Response Message:" />
<q-input v-model="resPayload" readonly label="Socket/Server Response Payload:" />
<q-input v-model="pushed" readonly type="textarea" label="Pushed from Socket/Server:" />
<q-input v-model="res" readonly label="Socket/Server Response Packet" />
<q-input v-model="pushed" readonly type="textarea" label="A Packet Pushed from Socket/Server:" />
</q-list>
<div class="row">
<q-btn size="40px" class="col q-px-xl q-py-xs" @click.left.exact="connect" @click.shift.left.exact="disconnect" :color="online">
<q-tooltip>click to (re)connect, shift click to disconnect</q-tooltip>
Socket: {{ connection }}
</q-btn>
<div class="col">
<q-input v-model="initTimeout" readonly label="Initial Connect Timeout" />
<q-select
v-model="statusLevel"
:options="statusOptions"
label="Choose Minimum Level to Dislay"
emit-value
map-options
></q-select>
</div>
<div class="col">
Status Log: (filter level {{ statusLevel }})
<q-scroll-area style="height: 200px">
<div v-for="(item, index) in filteredStatusLines" :key="index" class="caption q-py-sm">
<q-badge class="shadow-1">
{{ item._count }}
</q-badge>
<q-badge :color="status(item, 'color') || 'brown'" class="shadow-1">
{{ status(item,'name') || 'status' }}
</q-badge>
<q-badge v-if="item.success" color="positive" class="shadow-1">
{{ 'success' }}
</q-badge>
{{ item.msg }}
</div>
</q-scroll-area>
</div>
<div class="col">
</div>
</div>
</q-page>
</template>
<script>
import btc from 'better-try-catch'
// import socket from '../socket.js'
// import {status} from './utils'
const statusLevels = {
10: { color: 'black', name: 'trace' },
20: { color: 'indigo', name: 'debug' },
30: { color: 'info', name: 'info' },
40: { color: 'warning', name: 'warning' },
50: { color: 'red-3', name: 'error' },
60: { color: 'negative', name: 'fatal' }
}
export default {
data () {
return {
cmd: 'on',
cmd: 'switch/toggle',
connected: false,
commands: [
{
label: 'echo',
value: 'echo',
description: 'echo',
icon: 'no'
},
{
label: 'on',
value: 'on',
label: 'turn a switch on',
value: 'switch/on',
description: 'turn on something',
icon: 'mdi:lightbulb'
},
{
label: 'off',
value: 'off',
label: 'turn a switch off',
value: 'switch/off',
description: 'turn off something',
icon: 'lightbulb-off'
},
{
label: 'bogus',
value: 'bogus',
label: 'toggle a switch',
value: 'switch/toggle',
description: 'turn off something',
icon: 'mdi:lightbulb'
},
{
label: 'ack',
value: 'ack',
description: 'send ack packet',
icon: 'no'
}
],
packet: '',
resMsg: '',
resPayload: '',
key: ['id', 'brightness'],
value: ['not set', 0],
res: '',
key: ['id', 'sender'],
value: [1, 'websocket client'],
pushed: '',
switches: ['off', 'off', 'off', 'off']
switches: ['off', 'off', 'off', 'off'],
statusLines: [],
statusCount: 0,
statusLevel: 30,
statusOptions: [],
name: 'test-client',
initTimeout: 30
}
},
computed: {
online () {
if (this.connected) return 'positive'
else return 'warning'
},
connection () {
if (this.connected) return 'Connected'
else return 'Offline'
},
filteredStatusLines () {
return this.statusLines.filter(item => { return item.level >= this.statusLevel })
}
},
methods: {
async send () {
async connect () {
this.statusClear()
let [err, res] = await btc(this.$socket.connect)({ initTimeout: this.initTimeout * 1000, name: this.name })
if (err) {
this.$q.notify({
color: 'negative',
message: err.msg
})
this.connected = false
} else {
this.$q.notify({ color: 'positive', message: res })
// this.$socket.listen()
this.connected = true
}
},
async disconnect () {
await this.$socket.disconnect()
this.clear()
let packet = {
cmd: this.cmd,
[this.key[0]]: this.value[0],
[this.key[1]]: this.value[1],
[this.key[2]]: this.value[2]
this.connected = false
},
async send (packet) {
this.clear()
if (!packet.cmd) {
packet = {
cmd: this.cmd,
[this.key[0]]: this.value[0],
[this.key[1]]: this.value[1]
}
}
this.packet = JSON.stringify(packet)
let [err, res] = await btc(this.$socket.send)(packet)
if (err) {
console.log('error ', err)
this.$q.notify({
color: 'negative',
message: 'Error in repsonse of packet send'
message: `Error packet send, ${err}`
})
} else {
delete res._header
this.resMsg = res.msg
this.resPayload = JSON.stringify(res.payload)
}
// display on UI
this.res = JSON.stringify(res)
},
clear () {
this.pushed = ''
this.resMsg = ''
this.resPayload = ''
this.res = ''
this.packet = ''
},
async toggle (id) {
let packet = { cmd: 'switch/toggle', id: id, state: this.switches[id - 1] }
await this.send(packet)
},
status (item, prop) {
return statusLevels[item.level] ? statusLevels[item.level][prop] : item[prop] || ''
},
statusClear () {
this.statusLines = []
this.statusCount = 0
},
_statusHandler (packet) {
// converts level name to number, unknown level converted to 'info' 30
// console.log(packet.level, packet.msg)
if (typeof packet.level === 'string') {
packet.level = Number(Object.keys(statusLevels).find(key => { return statusLevels[key].name === packet.level })) || 30
}
if (packet._clear) this.statusClear()
// if (this.statusLines.length) {
// if (packet.level === 10 && this.statusLines[0].level === 10) this.statusLines.shift()
// }
// if (this.statusLines.length) {
// if (packet.level === this.statusLines[0].level && packet.msg === this.statusLines[0].msg) this.statusLines.shift()
// }
let previous = this.statusLines.findIndex(line => { return (line.level === packet.level && line.msg === packet.msg) })
if (previous > -1) this.statusLines.splice(previous, 1)
packet._count = ++this.statusCount
this.statusLines.unshift(packet)
if (this.statusLines > 20) this.statusLines.pop()
if (packet.connected != null) this.connected = packet.connected
if (packet.success === 'success') this.$q.notify({ color: 'positive', message: packet.msg })
},
_pushedHandler (packet) {
// don't mutate the packet just in case
let pushed = Object.assign({}, packet)
delete pushed._header
this.pushed = JSON.stringify(pushed) // display on UI
if (packet.cmd === 'switch/status') {
this.switches[packet.id - 1] = packet.state
}
}
},
async mounted () {
this.$q.notify({
color: 'info',
message: `Client connecting to', ${this.$socket.url}`
})
let [err] = await btc(this.$socket.connect)()
if (err) {
this.$q.notify({
color: 'negative',
message: 'Websocket Server Not Available'
})
} else {
this.$q.notify({ color: 'positive', message: 'Ready' })
this.$socket.listen()
this.clear()
}
this.$socket.on('pushed', packet => {
delete packet._header
this.pushed = JSON.stringify(packet)
Object.keys(statusLevels).forEach(level => {
this.statusOptions.push(
{
label: statusLevels[level].name,
value: Number(level)
}
)
})
window.addEventListener('unload', this.disconnect)
this.clear()
this.$socket.on('status', this._statusHandler.bind(this))
this.$socket.on('pushed', this._pushedHandler.bind(this))
await this.connect()
},
beforeDestroy () {
window.removeEventListener('unload', this.disconnect)
}
}
</script>

View file

@ -1,14 +0,0 @@
<template>
<q-page class="flex flex-center">
<img alt="Quasar logo" src="~assets/quasar-logo-full.svg">
</q-page>
</template>
<style>
</style>
<script>
export default {
name: 'PageIndex'
}
</script>

View file

@ -1,36 +0,0 @@
import Socket from '@uci/websocket'
// ws server to example ws client
async function packetProcess (packet) {
return new Promise(resolve => {
let res = {}
switch (packet.cmd) {
case 'echo':
res.msg = 'Echoing Back any payload propery'
res.payload = packet.payload
this.push({msg:'pushing echo to to any clients also', res:res})
break
case 'on':
case 'off':
res.msg = `Command turn ${packet.cmd} was sent for light id: ${packet.id ||'none!'} of brightness: ${packet.brightness || 0}`
res.payload = {switch:packet.cmd}
this.push(res)
break
default:
res.msg = `command ${packet.cmd} was unknown at server echo payload back`
res.payload = {}
}
resolve(res)
})
}
// let test = new Test()
let test = new Socket({ port: 8090, clientTracking: true })
test.registerPacketProcessor(packetProcess)
;(async () => {
console.log(await test.create())
})().catch(err => {
console.error('FATAL: UNABLE TO START SYSTEM!\n', err)
})

4
nodemon.json.off Normal file
View file

@ -0,0 +1,4 @@
{
"ignoreRoot": [".git","examples/ws-fio-client"],
"watch": ["node_modules/@uci/","node_modules/@uci-utils/","src/","index.js","examples/","test/"]
}

View file

@ -1,11 +1,14 @@
{
"name": "@uci/websocket-client",
"version": "0.1.7",
"version": "0.1.8",
"description": "JSON packet browser client over web socket",
"main": "src",
"watch": {
"client": "src/*.js"
},
"scripts": {
"example": "cd ./example/client && npm run tc",
"example:server": "node -r esm ./example/server.js"
"client": "cd ./example/client && npm run client",
"client:watch": "npm-watch"
},
"author": "David Kebler",
"license": "MIT",
@ -28,13 +31,13 @@
},
"homepage": "https://github.com/uCOMmandIt/websocket-client#readme",
"devDependencies": {
"@uci/websocket": "^0.3.7",
"esm": "^3.2.25"
"npm-watch": "^0.6.0"
},
"dependencies": {
"auto-bind": "^2.1.0",
"await-to-js": "^2.1.1",
"better-try-catch": "^0.6.2",
"eventemitter3": "^4.0.0"
"delay": "^4.3.0",
"eventemitter3": "^4.0.0",
"ws": "^7.1.2"
}
}

View file

@ -4,6 +4,8 @@
import btc from 'better-try-catch'
import EventEmitter from 'eventemitter3'
import autoBind from 'auto-bind'
import pause from 'delay'
/**
* Web Socket Consumer - An in browser consumer/client that can communicate via UCI packets
@ -12,8 +14,6 @@ import autoBind from 'auto-bind'
* @extends EventEmitter
*/
let count = 1
class WSConsumer extends EventEmitter {
/**
* constructor - Description
@ -26,11 +26,15 @@ class WSConsumer extends EventEmitter {
this.name = opts.name || 'browser'
this.instanceID = new Date().getTime()
this.url = url
this.rsLimit = opts.rsLimit || 5
this.initTimeout = opts.initTimeout || 30000
this.rsDelay = opts.rsDelay || 5000 // in large production you'd want a more robust delay calculation
this.initTimeout = opts.initTimeout * 1000 || 60000
this.retryWait = opts.retryWait * 1000 || 5000
this.protocol = opts.protocol // available if needed but not documented
this.started = false
this.opts = opts
this._connected = false
this._authenticated = false
this._conAttempt = 1
this._aborted = false
this._reconnect = false
autoBind(this)
}
@ -42,59 +46,123 @@ class WSConsumer extends EventEmitter {
* but opted to use https://www.npmjs.com/package/reconnecting-websocket
*/
async connect () {
// console.log('--- initial connect to websocket at', this.url)
async connect (opts={}) {
this._connected = false
this._authenticated = false
this._conAttempt = 1
this.url = opts.url || this.url
this.name = opts.name || this.name
this.initTimeout = opts.initTimeout || this.initTimeout
this.retryWait = opts.retryWait || this.retryWait
return new Promise((resolve, reject) => {
if(!this.url) reject('no url provided!')
let timeout
let connect = con.bind(this)
if(this._authenticated) resolve('socket already online')
this.emit('status',{level:30, msg:'attempting an initial connection', id:this.id, opts:this.opts, ready:false})
let initTimeout = {}
if (this.initTimeout > 499) { // if not set above 500 ms then infinite tries
initTimeout = setTimeout(() => {
this._aborted = true
this.socket.onopen = null // same as 'connect' for regular socket
this.socket.onerror = null
this.emit('status',{level:30, msg:'initial connection timed out', id:this.id, timeout:this.initTimeout, wait:this.retryWait, opts:this.opts, ready:false})
reject({ opts: this.opts, msg: `unable to connect initially to socket server in ${this.initTimeout/1000} secs, giving up no more attempts`})
}
, this.initTimeout)
}
function con () {
// console.log(this.started, this.socket)
if (this.socket) delete this.socket // make sure previous socket is garabage collected
this.socket = new WebSocket(this.url, this.protocol)
// console.log('ready after create', this.socket.readyState, this.socket.onopen, this.socket.onclose)
this.socket.onopen = open.bind(this)
const initialConnectHandler = async () => {
this.emit('status',{level:30, msg:'initial connect handler', _clear:true})
this.socket.onmessage =initialHandshake.bind(this)
}
timeout = setTimeout(function () {
if (!this.started && count===1) console.log('original connection connect failed - retrying')
console.log(`socket has not ${this.started?'re':''}connected in ${this.rsDelay*count/1000} seconds`)
count += 1
if (!this.started && this.rsDelay*count > this.initTimeout) {
let err = `unable to make a connection to websocket server at ${this.url} within ${this.initTimeout/1000}s`
console.log(err)
reject({url:this.url, msg:err})
const initialHandshake = async (event) => {
this.emit('status',{level:30, msg:'initial handshake'})
let packet = JSON.parse(event.data)
if (packet._handshake) {
clearTimeout(initTimeout)
this._connected = true
this.emit('status',{level:30, msg:'connected to server, sending authentification', id:this.id, opts:this.opts, connected:this._authenticated})
let authPacket = this._authenticate() || {}
authPacket._authenticate = true
authPacket.consumerName = this.name
let [err,res] = await btc(this._authenticateSend)(authPacket)
if (err) reject(err)
if (!res.authenticated) {
this.emit('status',{level:60, msg:`authentication failed: ${res.reason}`, id:this.id, opts:this.opts, connected:this._authenticated })
reject('unable to authenticate')
}
else connect()
}.bind(this), this.rsDelay)
else {
this._authenticated = res.authenticated
this.emit('status',{level:30, success:true, msg:'authentication succesful', id:this.id, opts:this.opts, connected:this._authenticated })
this._listen()
this.emit('consumer-connection', {state:'connected', id:this.id})
if (this.opts.conPacket) (this.send(this.conPacket))
resolve('initial connection successful')
}
}
}
function open () {
this.listen() // this handles messages
// console.log(`socket open to server at : ${this.url}`)
if (!this.started) this.emit('connected')
else this.emit('reconnected')
clearTimeout(timeout)
// this.socket.onerror = error.bind(this)
this.socket.onclose = close.bind(this)
count = 0
this.started = true
resolve({url:this.url, msg:`socket open to server at : ${this.url}`})
const initialErrorHandler = async (err) => {
this.socket.onopen = null
this.socket.onerror = null
if (!this._aborted) {
this.emit('status',{level:'error', msg:`error during initial connect, trying again in ${this.retryWait/1000} secs`, err:err, id:this.id, connected:this._authenticated })
await pause(this.retryWait)
connect() // reconnect on error
}
}
const connect = async () => {
let [err] = await btc(this.disconnect)()
if (!err) {
this.socket = new WebSocket(this.url) //, this.protocol)
this.socket.onopen = initialConnectHandler.bind(this) // same as 'connect' for regular socket
this.socket.onerror = initialErrorHandler.bind(this)
}
} // end connect
connect() // get the ball rolling
function close () {
this.socket.onclose = null
console.error('Socket has closed, attempting reconnect')
this.removeAllListeners('push')
this.socket.onmessage = null
this.emit('disconnected')
connect()
}
}) // end promise
}
async disconnect () {
return new Promise((resolve, reject) => {
clearTimeout(this.pingTimeout)
this.removeAllListeners('ping')
if (!this.socket) { resolve ('no socket, nothing to close');return }
if (this.socket.readyState === WebSocket.CLOSED || this.socket.readyState === WebSocket.CLOSING) {
this._connected = false
this._authenticated = false
this.emit('status', {level:'trace', msg:'disconnecting - socket is already closed/closing'})
resolve('socket already closed')
return
}
this.socket.close()
const timeout = setTimeout(() => {
clearInterval(wait)
this.emit('status', {level:40, msg:'Unable to disconnect in 5 seconds!'})
reject('unable to close socket in 5 seconds')
},5000)
const wait = setInterval(() => {
if (this.socket.readyState == WebSocket.CLOSED) {
clearInterval(wait)
clearTimeout(timeout)
this._connected = false
this._authenticated = false
this.emit('status', {level:40, msg:'Socket been closed/disconnected', connected:this._authenticated})
resolve('socket is now closed')
}
},500)
})
}
/**
* listen - Description
*
@ -102,37 +170,100 @@ class WSConsumer extends EventEmitter {
*
* @returns {type} Description
*/
listen (func) {
this.socket.onmessage = packetHandler.bind(this)
_listen () {
// process 'pushed' packets
this.on('pushed', async function (packet) {
const reconnect = async (reason) => {
let [err] = await btc(this.disconnect)()
if (err) {
this.emit('status',{level:'fatal', msg:'unable to close current connection - reconnection attempts aborted'})
} else {
this.emit('status',{level:'error', msg:`connection failed because ${reason}. attempting reconnect in ${this.retryWait/1000} secs`})
this.emit('consumer-connection', {state:'disconnected', id:this.id})
await pause(this.retryWait)
this.removeListener('pushed',pushedHandler)
this.removeListener('ping',pingHandler)
this.socket = new WebSocket(this.url) //, this.protocol)
this.socket.onopen = connectHandler.bind(this) // same as 'connect' for regular socket
this.socket.onerror = errorHandler.bind(this)
}
} // end reconnect
const connectHandler = async () => {
this.emit('status',{level:30, msg:'starting reconnect', _clear:true})
this.socket.onmessage =handshake.bind(this)
}
const handshake = async (event) => {
this.emit('status',{level:30, msg:'handshake/authenticate'})
let packet = JSON.parse(event.data)
if (packet._handshake) {
this._connected = true
this.emit('status',{level:30, msg:'connected to server, sending authentification', id:this.id, opts:this.opts, connected:this._authenticated })
let authPacket = this._authenticate() || {}
authPacket._authenticate = true
authPacket.consumerName = this.name
let [err,res] = await btc(this._authenticateSend)(authPacket)
if (err) this.socket.emit('error','authentication send failed')
if (!res.authenticated) {
this.emit('status',{level:60, msg:`authentication failed: ${res.reason}`, id:this.id, opts:this.opts, connected:this._authenticated})
reconnect.call(this, 'authentication failed')
}
else {
this._authenticated = res.authenticated
this.emit('status',{level:30, success:true, msg:'authentication succesful - reconnected', id:this.id, opts:this.opts, connected:this._authenticated})
this._listen()
this.emit('consumer-connection', {state:'reconnected', id:this.id})
if (this.opts.conPacket) (this.send(this.conPacket))
}
}
}
const errorHandler = async () => { // all reconnects go through here
this.emit('status',{level:50, msg:'error with socket connection', _clear:true, connected: false})
reconnect('emitted connection error') // reconnect on error
}
function monitorPing () {
this.pingTimeout = setTimeout( () => {
this.removeAllListeners('ping')
this.emit('status',{level:40, msg:'failed to receive ping - websocket offline', connected:false })
reconnect('ping not received in time')
},this._pingTimeout)
}
const pingHandler = async (packet) => {
clearTimeout(this.pingTimeout)
this.emit('status',{level:'trace', msg:'received ping - resetting timeout' })
this._pingTimeout= packet.pingInterval + 1000
monitorPing.call(this)
}
const packetHandler = (event) => {
let [err, packet] = btc(JSON.parse)(event.data)
if (err) this.emit('status', {'level':'error', msg: `Incoming message - could not parse JSON: ${event.data}` })
else {
if (packet._header) {
if (packet._header.id !=='ping') this.emit('status', {'level':'debug', msg: `Incoming message - ${packet._header.id}` })
this.emit(packet._header.id,packet)
}
}
}
const pushedHandler = async (packet) => {
// TODO do some extra security here for 'evil' pushed packets
let res = await this._packetProcess(packet)
if (!res) {
// if process was not promise returning like just logged to console
// console.log('warning: consumer process function was not promise returning')
this.emit('status',{level:40, msg:`consumer process function ${packet.cmd} was not promise returning`})
}
})
function packetHandler (event) {
let packet = {}
if (this.socket.readyState === 1) {
let [err, parsed] = btc(JSON.parse)(event.data)
if (err) packet = { error: `Could not parse JSON: ${event.data}` }
else packet = parsed
} else {
packet = {
error: `Connection not Ready, CODE:${this.socket.readyState}`
}
}
// console.log('in the handler', event.data)
if (func) func(packet) // extra processing if enabled
// this is response to a packet send command listener and is processed below
// will also emit 'pushed' via id which can be listened for in app
this.emit(packet._header.id, packet)
}
}
// set the main message handler and ping handler
this.on('pushed',pushedHandler)
this.socket.onmessage = packetHandler
this.on('ping',pingHandler)
} // end listen
/**
* send - Description
@ -143,10 +274,7 @@ class WSConsumer extends EventEmitter {
*/
async send (packet) {
return new Promise((resolve, reject) => {
if (this.socket.readyState !== 1)
reject(
new Error(`Connection not Ready, CODE:${this.socket.readyState}`)
)
if (!this._connected) reject('Unable to send not connected')
packet._header = {
id: Math.random()
.toString()
@ -155,16 +283,13 @@ class WSConsumer extends EventEmitter {
url: this.url
}
let [err, message] = btc(JSON.stringify)(packet)
if (err) reject(new Error(`Could not JSON stringify: ${packet}`))
// console.log('message to send', message)
if (err) reject(`Could not JSON stringify: ${packet}`)
this.socket.send(message)
// listen for when packet comes back with unique header id
this.once(packet._header.id, async function (reply) {
let res = await this._packetProcess(reply)
if (!res) {
// if process was not promise returning like just logged to console
res = reply
// console.log('consumer function was not promise returning - resolving unprocessed')
this.emit('status',{level:40, msg:`consumer process function ${packet.cmd} was not promise returning`})
}
resolve(res)
}) // end reply listener
@ -186,6 +311,26 @@ class WSConsumer extends EventEmitter {
async _packetProcess (packet) {
return Promise.resolve(packet)
}
// default authentication using a simple token
_authenticate () {
return { token: process.env.UCI_CLIENT_TOKEN || this.token || 'default' }
}
async _authenticateSend (authPacket={}) {
return new Promise(async (resolve, reject) => {
setTimeout(() => {reject({ error: 'no response from socket in 10sec' })}, 10000)
let [err, data] = await btc(JSON.stringify)(authPacket)
if (err) reject('unable to stringify authorization packet')
this.socket.onmessage = event => {
let [err,packet] = btc(JSON.parse)(event.data)
if (err) reject('unable to parse authorization return from server')
resolve (packet)
}
this.socket.send(data)
})
}
} // end Consumer Class
export default WSConsumer