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 eventsmaster
parent
cb2f4a613c
commit
a21aa3bf88
|
@ -1,27 +1,31 @@
|
||||||
{
|
{
|
||||||
"name": "test-client-new",
|
"name": "example-client",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"description": "a test websocket client for uci websocket server",
|
"description": "a websocket client example for use with @uci/websocket-client",
|
||||||
"productName": "Quasar App",
|
"productName": "Websocket Client",
|
||||||
"cordovaId": "org.cordova.quasar.app",
|
"cordovaId": "org.cordova.quasar.app",
|
||||||
"author": "David Kebler <d@kebler.net>",
|
"author": "David Kebler <d@kebler.net>",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint": "eslint --ext .js,.vue src",
|
"lint": "eslint --ext .js,.vue src",
|
||||||
"tc": "./node_modules/.bin/quasar dev",
|
"client": "quasar dev"
|
||||||
"tc:nas": "WSS='ws://ws.kebler.net' npm run tc"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@quasar/extras": "^1.2.0",
|
"@quasar/extras": "^1.3.1",
|
||||||
"quasar": "^1.0.0"
|
"@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": {
|
"devDependencies": {
|
||||||
"@quasar/app": "^1.0.0",
|
"@quasar/app": "^1.0.6",
|
||||||
"@vue/eslint-config-standard": "^4.0.0",
|
"@vue/eslint-config-standard": "^4.0.0",
|
||||||
"babel-eslint": "^10.0.1",
|
"babel-eslint": "^10.0.3",
|
||||||
"eslint": "^5.10.0",
|
"eslint": "^6.3.0",
|
||||||
"eslint-loader": "^2.1.1",
|
"eslint-loader": "^3.0.0",
|
||||||
"eslint-plugin-vue": "^5.0.0"
|
"eslint-plugin-vue": "^5.2.3"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 8.9.0",
|
"node": ">= 8.9.0",
|
||||||
|
|
|
@ -46,7 +46,9 @@ module.exports = function (ctx) {
|
||||||
'QSelect',
|
'QSelect',
|
||||||
'QField',
|
'QField',
|
||||||
'QFooter',
|
'QFooter',
|
||||||
'QTooltip'
|
'QTooltip',
|
||||||
|
'QBadge',
|
||||||
|
'QScrollArea'
|
||||||
],
|
],
|
||||||
|
|
||||||
directives: [
|
directives: [
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
// import WebSocket from '@uci/websocket-client'
|
import WebSocket from '@uci/websocket-client'
|
||||||
import WebSocket from '../../../../src'
|
|
||||||
|
|
||||||
const ws = new WebSocket(process.env.WSS || 'ws://0.0.0.0:8090')
|
const ws = new WebSocket(process.env.WSS || 'ws://0.0.0.0:8090')
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,37 @@
|
||||||
// app global css
|
// 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
|
.q-page-container
|
||||||
background $accent
|
background $accent
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -20,3 +53,7 @@
|
||||||
.q-item
|
.q-item
|
||||||
padding 0
|
padding 0
|
||||||
margin 0
|
margin 0
|
||||||
|
.greyedout
|
||||||
|
opacity 0.5
|
||||||
|
input
|
||||||
|
fontsize-12
|
||||||
|
|
|
@ -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)
|
|
@ -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>
|
|
|
@ -4,7 +4,7 @@
|
||||||
<q-header elevated class="bg-primary text-white">
|
<q-header elevated class="bg-primary text-white">
|
||||||
<q-toolbar>
|
<q-toolbar>
|
||||||
<q-toolbar-title>
|
<q-toolbar-title>
|
||||||
Title
|
Websocket Client for Four In One UCI Base Example
|
||||||
</q-toolbar-title>
|
</q-toolbar-title>
|
||||||
</q-toolbar>
|
</q-toolbar>
|
||||||
</q-header>
|
</q-header>
|
||||||
|
@ -14,11 +14,7 @@
|
||||||
</q-page-container>
|
</q-page-container>
|
||||||
|
|
||||||
<q-footer elevated class="bg-grey-8 text-white">
|
<q-footer elevated class="bg-grey-8 text-white">
|
||||||
<q-toolbar>
|
|
||||||
<q-toolbar-title>
|
|
||||||
Title
|
|
||||||
</q-toolbar-title>
|
|
||||||
</q-toolbar>
|
|
||||||
</q-footer>
|
</q-footer>
|
||||||
|
|
||||||
</q-layout>
|
</q-layout>
|
||||||
|
@ -28,6 +24,7 @@
|
||||||
export default {
|
export default {
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
|
status: []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
<template>
|
<template>
|
||||||
|
<!-- <q-page padding > :class="{greyedout: !connected}" > -->
|
||||||
<q-page padding class="" >
|
<q-page padding >
|
||||||
<q-list class="">
|
<q-list class="">
|
||||||
<q-item class="">
|
<q-item class="">
|
||||||
<q-item-section side>
|
<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>
|
<q-tooltip>
|
||||||
Click to send this command and payload to the sever
|
Click to send this command and payload to the sever
|
||||||
</q-tooltip>
|
</q-tooltip>
|
||||||
|
@ -12,8 +12,6 @@
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
<q-item-section >
|
<q-item-section >
|
||||||
<q-item-label>Command</q-item-label>
|
<q-item-label>Command</q-item-label>
|
||||||
<!-- <q-input label="Enter a . delimited command to send to server" v-model="cmd" />
|
|
||||||
-->
|
|
||||||
<q-select
|
<q-select
|
||||||
v-model="cmd"
|
v-model="cmd"
|
||||||
:options="commands"
|
:options="commands"
|
||||||
|
@ -25,114 +23,234 @@
|
||||||
|
|
||||||
<q-item><div class="col">Property</div>:<div class="col">Value</div></q-item>
|
<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[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[1]" />:<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[2]" />:<q-input class="col" v-model="value[2]" /></q-item>
|
|
||||||
</q-list>
|
</q-list>
|
||||||
<!-- <q-btn :class="switches[0]" @click="toggle(1)">Switch 1</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)">Switch 2</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)">Switch 3</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)">Switch 4</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-list class="response">
|
||||||
<q-input v-model="packet" readonly label="Sending Packet:" />
|
<q-input v-model="packet" readonly label="Sending Packet:" />
|
||||||
<q-input v-model="resMsg" readonly label="Socket/Server Response Message:" />
|
<q-input v-model="res" readonly label="Socket/Server Response Packet" />
|
||||||
<q-input v-model="resPayload" readonly label="Socket/Server Response Payload:" />
|
<q-input v-model="pushed" readonly type="textarea" label="A Packet Pushed from Socket/Server:" />
|
||||||
<q-input v-model="pushed" readonly type="textarea" label="Pushed from Socket/Server:" />
|
|
||||||
</q-list>
|
</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>
|
</q-page>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import btc from 'better-try-catch'
|
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 {
|
export default {
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
cmd: 'on',
|
cmd: 'switch/toggle',
|
||||||
|
connected: false,
|
||||||
commands: [
|
commands: [
|
||||||
{
|
{
|
||||||
label: 'echo',
|
label: 'turn a switch on',
|
||||||
value: 'echo',
|
value: 'switch/on',
|
||||||
description: 'echo',
|
|
||||||
icon: 'no'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'on',
|
|
||||||
value: 'on',
|
|
||||||
description: 'turn on something',
|
description: 'turn on something',
|
||||||
icon: 'mdi:lightbulb'
|
icon: 'mdi:lightbulb'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'off',
|
label: 'turn a switch off',
|
||||||
value: 'off',
|
value: 'switch/off',
|
||||||
description: 'turn off something',
|
description: 'turn off something',
|
||||||
icon: 'lightbulb-off'
|
icon: 'lightbulb-off'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'bogus',
|
label: 'toggle a switch',
|
||||||
value: 'bogus',
|
value: 'switch/toggle',
|
||||||
description: 'turn off something',
|
description: 'turn off something',
|
||||||
|
icon: 'mdi:lightbulb'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'ack',
|
||||||
|
value: 'ack',
|
||||||
|
description: 'send ack packet',
|
||||||
icon: 'no'
|
icon: 'no'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
packet: '',
|
packet: '',
|
||||||
resMsg: '',
|
res: '',
|
||||||
resPayload: '',
|
key: ['id', 'sender'],
|
||||||
key: ['id', 'brightness'],
|
value: [1, 'websocket client'],
|
||||||
value: ['not set', 0],
|
|
||||||
pushed: '',
|
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: {
|
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()
|
this.clear()
|
||||||
let packet = {
|
this.connected = false
|
||||||
|
},
|
||||||
|
async send (packet) {
|
||||||
|
this.clear()
|
||||||
|
if (!packet.cmd) {
|
||||||
|
packet = {
|
||||||
cmd: this.cmd,
|
cmd: this.cmd,
|
||||||
[this.key[0]]: this.value[0],
|
[this.key[0]]: this.value[0],
|
||||||
[this.key[1]]: this.value[1],
|
[this.key[1]]: this.value[1]
|
||||||
[this.key[2]]: this.value[2]
|
}
|
||||||
}
|
}
|
||||||
this.packet = JSON.stringify(packet)
|
this.packet = JSON.stringify(packet)
|
||||||
let [err, res] = await btc(this.$socket.send)(packet)
|
let [err, res] = await btc(this.$socket.send)(packet)
|
||||||
if (err) {
|
if (err) {
|
||||||
console.log('error ', err)
|
|
||||||
this.$q.notify({
|
this.$q.notify({
|
||||||
color: 'negative',
|
color: 'negative',
|
||||||
message: 'Error in repsonse of packet send'
|
message: `Error packet send, ${err}`
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
delete res._header
|
delete res._header
|
||||||
this.resMsg = res.msg
|
|
||||||
this.resPayload = JSON.stringify(res.payload)
|
|
||||||
}
|
}
|
||||||
|
// display on UI
|
||||||
|
this.res = JSON.stringify(res)
|
||||||
},
|
},
|
||||||
clear () {
|
clear () {
|
||||||
this.pushed = ''
|
this.pushed = ''
|
||||||
this.resMsg = ''
|
this.res = ''
|
||||||
this.resPayload = ''
|
|
||||||
this.packet = ''
|
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 () {
|
async mounted () {
|
||||||
this.$q.notify({
|
Object.keys(statusLevels).forEach(level => {
|
||||||
color: 'info',
|
this.statusOptions.push(
|
||||||
message: `Client connecting to', ${this.$socket.url}`
|
{
|
||||||
})
|
label: statusLevels[level].name,
|
||||||
let [err] = await btc(this.$socket.connect)()
|
value: Number(level)
|
||||||
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)
|
|
||||||
})
|
})
|
||||||
|
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>
|
</script>
|
||||||
|
|
|
@ -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>
|
|
|
@ -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)
|
|
||||||
})
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"ignoreRoot": [".git","examples/ws-fio-client"],
|
||||||
|
"watch": ["node_modules/@uci/","node_modules/@uci-utils/","src/","index.js","examples/","test/"]
|
||||||
|
}
|
17
package.json
17
package.json
|
@ -1,11 +1,14 @@
|
||||||
{
|
{
|
||||||
"name": "@uci/websocket-client",
|
"name": "@uci/websocket-client",
|
||||||
"version": "0.1.7",
|
"version": "0.1.8",
|
||||||
"description": "JSON packet browser client over web socket",
|
"description": "JSON packet browser client over web socket",
|
||||||
"main": "src",
|
"main": "src",
|
||||||
|
"watch": {
|
||||||
|
"client": "src/*.js"
|
||||||
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"example": "cd ./example/client && npm run tc",
|
"client": "cd ./example/client && npm run client",
|
||||||
"example:server": "node -r esm ./example/server.js"
|
"client:watch": "npm-watch"
|
||||||
},
|
},
|
||||||
"author": "David Kebler",
|
"author": "David Kebler",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
@ -28,13 +31,13 @@
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/uCOMmandIt/websocket-client#readme",
|
"homepage": "https://github.com/uCOMmandIt/websocket-client#readme",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@uci/websocket": "^0.3.7",
|
"npm-watch": "^0.6.0"
|
||||||
"esm": "^3.2.25"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"auto-bind": "^2.1.0",
|
"auto-bind": "^2.1.0",
|
||||||
"await-to-js": "^2.1.1",
|
|
||||||
"better-try-catch": "^0.6.2",
|
"better-try-catch": "^0.6.2",
|
||||||
"eventemitter3": "^4.0.0"
|
"delay": "^4.3.0",
|
||||||
|
"eventemitter3": "^4.0.0",
|
||||||
|
"ws": "^7.1.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
import btc from 'better-try-catch'
|
import btc from 'better-try-catch'
|
||||||
import EventEmitter from 'eventemitter3'
|
import EventEmitter from 'eventemitter3'
|
||||||
import autoBind from 'auto-bind'
|
import autoBind from 'auto-bind'
|
||||||
|
import pause from 'delay'
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Web Socket Consumer - An in browser consumer/client that can communicate via UCI packets
|
* 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
|
* @extends EventEmitter
|
||||||
*/
|
*/
|
||||||
|
|
||||||
let count = 1
|
|
||||||
|
|
||||||
class WSConsumer extends EventEmitter {
|
class WSConsumer extends EventEmitter {
|
||||||
/**
|
/**
|
||||||
* constructor - Description
|
* constructor - Description
|
||||||
|
@ -26,11 +26,15 @@ class WSConsumer extends EventEmitter {
|
||||||
this.name = opts.name || 'browser'
|
this.name = opts.name || 'browser'
|
||||||
this.instanceID = new Date().getTime()
|
this.instanceID = new Date().getTime()
|
||||||
this.url = url
|
this.url = url
|
||||||
this.rsLimit = opts.rsLimit || 5
|
this.initTimeout = opts.initTimeout * 1000 || 60000
|
||||||
this.initTimeout = opts.initTimeout || 30000
|
this.retryWait = opts.retryWait * 1000 || 5000
|
||||||
this.rsDelay = opts.rsDelay || 5000 // in large production you'd want a more robust delay calculation
|
|
||||||
this.protocol = opts.protocol // available if needed but not documented
|
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)
|
autoBind(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,59 +46,123 @@ class WSConsumer extends EventEmitter {
|
||||||
* but opted to use https://www.npmjs.com/package/reconnecting-websocket
|
* but opted to use https://www.npmjs.com/package/reconnecting-websocket
|
||||||
*/
|
*/
|
||||||
|
|
||||||
async connect () {
|
async connect (opts={}) {
|
||||||
// console.log('--- initial connect to websocket at', this.url)
|
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) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
|
||||||
if(!this.url) reject('no url provided!')
|
if(!this.url) reject('no url provided!')
|
||||||
let timeout
|
if(this._authenticated) resolve('socket already online')
|
||||||
let connect = con.bind(this)
|
this.emit('status',{level:30, msg:'attempting an initial connection', id:this.id, opts:this.opts, ready:false})
|
||||||
|
let initTimeout = {}
|
||||||
function con () {
|
if (this.initTimeout > 499) { // if not set above 500 ms then infinite tries
|
||||||
// console.log(this.started, this.socket)
|
initTimeout = setTimeout(() => {
|
||||||
if (this.socket) delete this.socket // make sure previous socket is garabage collected
|
this._aborted = true
|
||||||
this.socket = new WebSocket(this.url, this.protocol)
|
this.socket.onopen = null // same as 'connect' for regular socket
|
||||||
// console.log('ready after create', this.socket.readyState, this.socket.onopen, this.socket.onclose)
|
this.socket.onerror = null
|
||||||
this.socket.onopen = open.bind(this)
|
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`})
|
||||||
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})
|
|
||||||
}
|
}
|
||||||
else connect()
|
, this.initTimeout)
|
||||||
}.bind(this), this.rsDelay)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function open () {
|
const initialConnectHandler = async () => {
|
||||||
this.listen() // this handles messages
|
this.emit('status',{level:30, msg:'initial connect handler', _clear:true})
|
||||||
// console.log(`socket open to server at : ${this.url}`)
|
this.socket.onmessage =initialHandshake.bind(this)
|
||||||
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 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 {
|
||||||
|
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')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
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
|
}) // 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
|
* listen - Description
|
||||||
*
|
*
|
||||||
|
@ -102,37 +170,100 @@ class WSConsumer extends EventEmitter {
|
||||||
*
|
*
|
||||||
* @returns {type} Description
|
* @returns {type} Description
|
||||||
*/
|
*/
|
||||||
listen (func) {
|
_listen () {
|
||||||
this.socket.onmessage = packetHandler.bind(this)
|
|
||||||
|
|
||||||
// process 'pushed' packets
|
const reconnect = async (reason) => {
|
||||||
this.on('pushed', async function (packet) {
|
let [err] = await btc(this.disconnect)()
|
||||||
// TODO do some extra security here for 'evil' pushed packets
|
if (err) {
|
||||||
let res = await this._packetProcess(packet)
|
this.emit('status',{level:'fatal', msg:'unable to close current connection - reconnection attempts aborted'})
|
||||||
if (!res) {
|
|
||||||
// if process was not promise returning like just logged to console
|
|
||||||
// console.log('warning: consumer process function 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 {
|
} else {
|
||||||
packet = {
|
this.emit('status',{level:'error', msg:`connection failed because ${reason}. attempting reconnect in ${this.retryWait/1000} secs`})
|
||||||
error: `Connection not Ready, CODE:${this.socket.readyState}`
|
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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 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
|
const errorHandler = async () => { // all reconnects go through here
|
||||||
// will also emit 'pushed' via id which can be listened for in app
|
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)
|
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) {
|
||||||
|
this.emit('status',{level:40, msg:`consumer process function ${packet.cmd} was not promise returning`})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the main message handler and ping handler
|
||||||
|
|
||||||
|
this.on('pushed',pushedHandler)
|
||||||
|
this.socket.onmessage = packetHandler
|
||||||
|
this.on('ping',pingHandler)
|
||||||
|
|
||||||
|
} // end listen
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* send - Description
|
* send - Description
|
||||||
|
@ -143,10 +274,7 @@ class WSConsumer extends EventEmitter {
|
||||||
*/
|
*/
|
||||||
async send (packet) {
|
async send (packet) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (this.socket.readyState !== 1)
|
if (!this._connected) reject('Unable to send not connected')
|
||||||
reject(
|
|
||||||
new Error(`Connection not Ready, CODE:${this.socket.readyState}`)
|
|
||||||
)
|
|
||||||
packet._header = {
|
packet._header = {
|
||||||
id: Math.random()
|
id: Math.random()
|
||||||
.toString()
|
.toString()
|
||||||
|
@ -155,16 +283,13 @@ class WSConsumer extends EventEmitter {
|
||||||
url: this.url
|
url: this.url
|
||||||
}
|
}
|
||||||
let [err, message] = btc(JSON.stringify)(packet)
|
let [err, message] = btc(JSON.stringify)(packet)
|
||||||
if (err) reject(new Error(`Could not JSON stringify: ${packet}`))
|
if (err) reject(`Could not JSON stringify: ${packet}`)
|
||||||
// console.log('message to send', message)
|
|
||||||
this.socket.send(message)
|
this.socket.send(message)
|
||||||
// listen for when packet comes back with unique header id
|
|
||||||
this.once(packet._header.id, async function (reply) {
|
this.once(packet._header.id, async function (reply) {
|
||||||
let res = await this._packetProcess(reply)
|
let res = await this._packetProcess(reply)
|
||||||
if (!res) {
|
if (!res) {
|
||||||
// if process was not promise returning like just logged to console
|
|
||||||
res = reply
|
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)
|
resolve(res)
|
||||||
}) // end reply listener
|
}) // end reply listener
|
||||||
|
@ -186,6 +311,26 @@ class WSConsumer extends EventEmitter {
|
||||||
async _packetProcess (packet) {
|
async _packetProcess (packet) {
|
||||||
return Promise.resolve(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
|
} // end Consumer Class
|
||||||
|
|
||||||
export default WSConsumer
|
export default WSConsumer
|
||||||
|
|
Loading…
Reference in New Issue