Browse Source

:sparkles: sse push updates!

tags/0.0.1
J 4 years ago
parent
commit
d142531acb

+ 5
- 0
backend/lib/index.js View File

@@ -2,6 +2,7 @@ const UserPlugin = require('./plugins/user')
2 2
 const MembershipPlugin = require('./plugins/membership')
3 3
 const SurveyPlugin = require('./plugins/survey')
4 4
 const ProfilePlugin = require('./plugins/profile')
5
+const NotificationPlugin = require('./plugins/notification')
5 6
 
6 7
 /**
7 8
  * A Hapi server instance
@@ -40,5 +41,9 @@ exports.plugin = {
40 41
         await server.register(ProfilePlugin, {
41 42
             routes: { prefix: '/profile' },
42 43
         })
44
+        
45
+        await server.register(NotificationPlugin, {
46
+            routes: { prefix: '/notification' },
47
+        })
43 48
     },
44 49
 }

+ 75
- 0
backend/lib/plugins/notification.js View File

@@ -0,0 +1,75 @@
1
+const Stream = require('stream')
2
+const PassThrough = Stream.PassThrough
3
+const Transform = Stream.Transform
4
+const NotificationRoute = require('../routes/notification')
5
+
6
+const stringifyEvent = function (event) {
7
+    let str = ''
8
+    const endl = '\r\n'
9
+    for (const i in event) {
10
+        let val = event[i]
11
+        if (val instanceof Buffer) { val = val.toString() }
12
+        if (typeof val === 'object') { val = JSON.stringify(val) }
13
+        str += i + ': ' + val + endl
14
+    }
15
+    str += endl
16
+    return str
17
+}
18
+
19
+class Transformer extends Transform {
20
+    constructor(options, objectMode) {
21
+        super({ objectMode })
22
+        options = options || {}
23
+        this.counter = 1
24
+        this.event = options.event || null
25
+        this.generateId = options.generateId ? options.generateId : () => {
26
+            return this.counter++
27
+        }
28
+    }
29
+    _transform (chunk, encoding, callback) {
30
+        const event = {
31
+            id: this.generateId(chunk),
32
+            data: chunk
33
+        }
34
+        if (this.event) { event.event = this.event }
35
+        this.push(stringifyEvent(event))
36
+        callback()
37
+    }
38
+    _flush(callback) {
39
+        this.push(stringifyEvent({ event: 'end', data: '' }))
40
+        callback()
41
+    }
42
+} 
43
+
44
+const writeEvent = function (event, stream) {
45
+    if (event) {
46
+        stream.write(stringifyEvent(event))
47
+    } else {
48
+        // closing time
49
+        stream.write(stringifyEvent({ event: 'end', data: '' }))
50
+        stream.end()
51
+    }
52
+}
53
+
54
+const onEvent = (event, h, streamOptions) => {
55
+    // We only support ObjectMode streams
56
+    if (!event._readableState.objectMode) return
57
+
58
+    const through = new Transformer(streamOptions, true)
59
+    const active = new PassThrough()
60
+    through.pipe(active)
61
+    event.pipe(through)
62
+    console.log(event)
63
+    return h.response(active)
64
+        .header('content-type', 'text/event-stream')
65
+        .header('content-encoding', 'identity')
66
+}
67
+
68
+module.exports = {
69
+    name: 'notification-plugin',
70
+    version: '1.0.0',
71
+    register: async (server, options) => {
72
+        await server.route(NotificationRoute)
73
+        server.decorate('toolkit', 'event', onEvent)
74
+    }
75
+}

+ 1
- 1
backend/lib/routes/membership/join.js View File

@@ -71,7 +71,7 @@ module.exports = {
71 71
                     groupingToWrite,
72 72
                     role,
73 73
                 )
74
-                console.log(memberships)
74
+                // console.log(memberships)
75 75
                 return h
76 76
                     .response({
77 77
                         ok: true,

+ 53
- 0
backend/lib/routes/notification/index.js View File

@@ -0,0 +1,53 @@
1
+const Joi = require('joi')
2
+const apiSchema = require('../../schemas/api')
3
+const errorSchema = require('../../schemas/errors')
4
+const Stream = require('stream')
5
+const PassThrough = require('stream').PassThrough
6
+
7
+const pluginConfig = {
8
+    handlerType: 'notifictaion',
9
+    docs: {
10
+        description: 'subscribe',
11
+        notes: 'Subscribe to notifications based on profile_id',
12
+    },
13
+}
14
+
15
+const validators = {
16
+    params: Joi.object({
17
+        profile_id: Joi.number(),
18
+    })
19
+}
20
+
21
+module.exports = {
22
+    method: 'GET',
23
+    path: '/{profile_id}/subscribe',
24
+    options: {
25
+        ...pluginConfig.docs,
26
+        tags: ['api'],
27
+        auth: false,
28
+        cors: true,
29
+        handler: async (request, h) => {
30
+            const input = new PassThrough({ objectMode: true })
31
+            const eventType = 'stonk'
32
+
33
+            const msg = { name: 'BDGRS', price: (500 + Math.floor(Math.random() * 100)).toString(), order: null }
34
+
35
+            // Write to the input stream
36
+            setInterval(() => {
37
+                msg.order = Math.floor(Math.random() * 2) === 1 ? 'BUY' : 'SELL'
38
+                input.write(msg)
39
+            }, 5000)
40
+
41
+            // h.event() Added at plugin registration
42
+            // h is the toolkit
43
+            return h.event(input, h, { event: eventType })
44
+        },
45
+
46
+        /** Validate based on validators object */
47
+         validate: {
48
+            ...validators,
49
+            failAction: 'log'
50
+        },
51
+
52
+    },
53
+}

+ 5
- 5
backend/lib/routes/profile/queue.js View File

@@ -52,7 +52,7 @@ module.exports = {
52 52
 
53 53
             const queue = await matchQueueService.getQueue(profile_id)
54 54
             const queueIds = queue.map(entry => entry.target_id)
55
-            console.log('queueIds', queueIds)
55
+            // console.log('queueIds', queueIds)
56 56
             const res =  {
57 57
                 ok:true,
58 58
                 handler: pluginConfig.handlerType,
@@ -63,10 +63,10 @@ module.exports = {
63 63
             // queueIds spits out the queue profiles in the correct order
64 64
             // ~However~ when it goes through getProfilesFor
65 65
             // it comes back in literal database order regardless of is_deleted status
66
-            console.log(
67
-                'include_profile results',
68
-                await profileService.getProfilesFor(queueIds),
69
-            )
66
+            // console.log(
67
+            //     'include_profile results',
68
+            //     await profileService.getProfilesFor(queueIds),
69
+            // )
70 70
             if(include_profile) {
71 71
                 res.data = await profileService.getProfilesFor(queueIds)
72 72
             }

+ 27
- 27
backend/package-lock.json View File

@@ -1052,9 +1052,9 @@
1052 1052
       "dev": true
1053 1053
     },
1054 1054
     "ansi-regex": {
1055
-      "version": "5.0.0",
1056
-      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
1057
-      "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
1055
+      "version": "5.0.1",
1056
+      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
1057
+      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
1058 1058
       "dev": true
1059 1059
     },
1060 1060
     "ansi-styles": {
@@ -1240,25 +1240,25 @@
1240 1240
           "dev": true
1241 1241
         },
1242 1242
         "boxen": {
1243
-          "version": "5.0.1",
1244
-          "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.0.1.tgz",
1245
-          "integrity": "sha512-49VBlw+PrWEF51aCmy7QIteYPIFZxSpvqBdP/2itCPPlJ49kj9zg/XPRFrdkne2W+CfwXUls8exMvu1RysZpKA==",
1243
+          "version": "5.1.2",
1244
+          "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz",
1245
+          "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==",
1246 1246
           "dev": true,
1247 1247
           "requires": {
1248 1248
             "ansi-align": "^3.0.0",
1249 1249
             "camelcase": "^6.2.0",
1250 1250
             "chalk": "^4.1.0",
1251 1251
             "cli-boxes": "^2.2.1",
1252
-            "string-width": "^4.2.0",
1252
+            "string-width": "^4.2.2",
1253 1253
             "type-fest": "^0.20.2",
1254 1254
             "widest-line": "^3.1.0",
1255 1255
             "wrap-ansi": "^7.0.0"
1256 1256
           }
1257 1257
         },
1258 1258
         "camelcase": {
1259
-          "version": "6.2.0",
1260
-          "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz",
1261
-          "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==",
1259
+          "version": "6.3.0",
1260
+          "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
1261
+          "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
1262 1262
           "dev": true
1263 1263
         },
1264 1264
         "chalk": {
@@ -3063,17 +3063,17 @@
3063 3063
       }
3064 3064
     },
3065 3065
     "hapi-swagger": {
3066
-      "version": "14.1.3",
3067
-      "resolved": "https://registry.npmjs.org/hapi-swagger/-/hapi-swagger-14.1.3.tgz",
3068
-      "integrity": "sha512-Lb4LuJ4VCg/D4xeat/c9aMCpifuTKMgVC8taazB+Dn8evRRTyoymccGouk1H9VI35maV7F1dxzyZJ5ur+C2b/A==",
3066
+      "version": "14.2.5",
3067
+      "resolved": "https://registry.npmjs.org/hapi-swagger/-/hapi-swagger-14.2.5.tgz",
3068
+      "integrity": "sha512-rIxwCT9i+R9E9Z5m9BT15rwYI58IOKTKu7NEx9+pHO5aVeJK703qW3PWk72D7x9MSAnhmlJoEyUiFAU+6zQJ9A==",
3069 3069
       "requires": {
3070 3070
         "@hapi/boom": "^9.1.0",
3071 3071
         "@hapi/hoek": "^9.0.2",
3072
-        "handlebars": "^4.5.3",
3072
+        "handlebars": "^4.7.7",
3073 3073
         "http-status": "^1.0.1",
3074 3074
         "json-schema-ref-parser": "^6.1.0",
3075 3075
         "swagger-parser": "4.0.2",
3076
-        "swagger-ui-dist": "^3.47.1"
3076
+        "swagger-ui-dist": "^4.5.0"
3077 3077
       }
3078 3078
     },
3079 3079
     "has": {
@@ -4538,9 +4538,9 @@
4538 4538
       }
4539 4539
     },
4540 4540
     "objection": {
4541
-      "version": "2.2.15",
4542
-      "resolved": "https://registry.npmjs.org/objection/-/objection-2.2.15.tgz",
4543
-      "integrity": "sha512-ZOLJDigE9Z2ppk3C//S2fcWL6ph2jUe6Cwl0CEpslTZegnnOeG6RPIV80nhPAaghWjl/8F2kox/w89VVBN4ccg==",
4541
+      "version": "2.2.18",
4542
+      "resolved": "https://registry.npmjs.org/objection/-/objection-2.2.18.tgz",
4543
+      "integrity": "sha512-la7dWwKzpw+Sh4ROWWrZycq2k2z0IyYv3kb8rTPJPIRKuyV59DlSSctALPs5Feoi8I3RNxo3vnCuVoNcKgxthw==",
4544 4544
       "requires": {
4545 4545
         "ajv": "^6.12.6",
4546 4546
         "db-errors": "^0.2.3"
@@ -5848,9 +5848,9 @@
5848 5848
       "integrity": "sha1-cAcEaNbSl3ylI3suUZyn0Gouo/0="
5849 5849
     },
5850 5850
     "swagger-ui-dist": {
5851
-      "version": "3.49.0",
5852
-      "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.49.0.tgz",
5853
-      "integrity": "sha512-R1+eT16XNP1bBLfacISifZAkFJlpwvWsS2vVurF5pbIFZnmCasD/hj+9r/q7urYdQyb0B6v11mDnuYU7rUpfQg=="
5851
+      "version": "4.5.2",
5852
+      "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-4.5.2.tgz",
5853
+      "integrity": "sha512-wV4w54eW9z+VKbYJBJfULfqO05otCbM9jwgRIkwRl9CrfTVKelDzyhhEvdUQkGUzro+Ir8TOZPiZgKIdIdolWQ=="
5854 5854
     },
5855 5855
     "table": {
5856 5856
       "version": "6.7.1",
@@ -6002,9 +6002,9 @@
6002 6002
       }
6003 6003
     },
6004 6004
     "trim-off-newlines": {
6005
-      "version": "1.0.1",
6006
-      "resolved": "https://registry.npmjs.org/trim-off-newlines/-/trim-off-newlines-1.0.1.tgz",
6007
-      "integrity": "sha1-n5up2e+odkw4dpi8v+sshI8RrbM=",
6005
+      "version": "1.0.3",
6006
+      "resolved": "https://registry.npmjs.org/trim-off-newlines/-/trim-off-newlines-1.0.3.tgz",
6007
+      "integrity": "sha512-kh6Tu6GbeSNMGfrrZh6Bb/4ZEHV1QlB4xNDBeog8Y9/QwFlKTRyWvY3Fs9tRDAMZliVUwieMgEdIeL/FtqjkJg==",
6008 6008
       "dev": true
6009 6009
     },
6010 6010
     "type-check": {
@@ -6038,9 +6038,9 @@
6038 6038
       }
6039 6039
     },
6040 6040
     "uglify-js": {
6041
-      "version": "3.13.7",
6042
-      "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.13.7.tgz",
6043
-      "integrity": "sha512-1Psi2MmnZJbnEsgJJIlfnd7tFlJfitusmR7zDI8lXlFI0ACD4/Rm/xdrU8bh6zF0i74aiVoBtkRiFulkrmh3AA==",
6041
+      "version": "3.15.1",
6042
+      "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.15.1.tgz",
6043
+      "integrity": "sha512-FAGKF12fWdkpvNJZENacOH0e/83eG6JyVQyanIJaBXCN1J11TUQv1T1/z8S+Z0CG0ZPk1nPcreF/c7lrTd0TEQ==",
6044 6044
       "optional": true
6045 6045
     },
6046 6046
     "unc-path-regex": {

+ 2
- 2
backend/package.json View File

@@ -26,12 +26,12 @@
26 26
     "compute-cosine-similarity": "^1.0.0",
27 27
     "dotenv": "^10.0.0",
28 28
     "exiting": "^6.0.1",
29
-    "hapi-swagger": "^14.1.3",
29
+    "hapi-swagger": "^14.2.5",
30 30
     "haversine": "^1.1.1",
31 31
     "joi": "^17.4.0",
32 32
     "knex": "^0.21.19",
33 33
     "mysql": "^2.18.1",
34
-    "objection": "^2.2.15",
34
+    "objection": "^2.2.18",
35 35
     "secure-password": "^4.0.0"
36 36
   },
37 37
   "devDependencies": {

+ 16
- 5135
frontend/package-lock.json
File diff suppressed because it is too large
View File


+ 21
- 1
frontend/src/utils/db.js View File

@@ -72,4 +72,24 @@ class Connector {
72 72
     // async removeAll() { }
73 73
 }
74 74
 const db = new Connector()
75
-export { Connector, db }
75
+
76
+class Toaster {
77
+    constructor({ profileId }) {
78
+        this.url = `${remote}/notification/${profileId}/subscribe`
79
+        
80
+    }
81
+    connect() {
82
+        this.source = new EventSource(`${remote}/notification/${profileId}/subscribe`)
83
+        this.source.onmessage = e => {
84
+            console.log("Data", + e.data)
85
+        }
86
+        this.source.onerror = e => {
87
+            console.error("EventSource failed.", e)
88
+        }
89
+        this.source.addEventListener('end', e => {
90
+            this.close()
91
+        })
92
+    }
93
+}
94
+
95
+export { Connector, db, Toaster }

+ 17
- 1
frontend/src/views/home.vue View File

@@ -12,6 +12,7 @@ import sidebar from '../components/Sidebar.vue'
12 12
 import mainNav from '../components/MainNav.vue'
13 13
 import profileCardList from '../components/ProfileCardList.vue'
14 14
 import { fetchQueueByProfileId } from '../services'
15
+import { Toaster } from '../utils/db'
15 16
 
16 17
 import batch_10 from '../../../backend/db/generated/_batch_10.js.ref'
17 18
 import batch_20 from '../../../backend/db/generated/_batch_20.js.ref'
@@ -25,6 +26,9 @@ export default {
25 26
         user: null,
26 27
         mypid: null,
27 28
     }),
29
+    mounted() {
30
+        this.setupToaster()
31
+    },
28 32
     async created() {
29 33
         // this.mypid = auth.currentUser?.mypid || "99999";
30 34
         this.mypid = 38
@@ -33,10 +37,22 @@ export default {
33 37
 
34 38
         // Uncomment below to use API
35 39
         const queueList = await fetchQueueByProfileId(this.mypid)
36
-        console.log('queueList', queueList)
40
+        // console.log('queueList', queueList)
37 41
         this.processQueue(queueList)
38 42
     },
39 43
     methods: {
44
+        setupToaster() {
45
+            const stocks = {}
46
+            const source = new EventSource(`http://localhost:3001/api/notification/${this.mypid}/subscribe`)
47
+            source.addEventListener('stonk', function (message) {
48
+                console.log('updated:', message)
49
+                const data = JSON.parse(message.data)
50
+                stocks[data.name] = data
51
+            })
52
+            source.addEventListener('end', function (message) {
53
+                this.close()
54
+            })
55
+        },
40 56
         processQueue(queueList) {
41 57
             const formattedList = []
42 58
             queueList.forEach(profile => {

Loading…
Cancel
Save