Browse Source

Added API route `dataCollect`. Rewordked uuid system

rigwild 1 year ago
parent
commit
f8ae7494f6

+ 4 - 1
config.messagesId.js

@@ -1,5 +1,8 @@
 // List of IDs for messages sent using WebSockets
 
+// Message ID for data collection
+export const COLLECT_DATA = 'COLLECT_DATA'
+
 // Message IDs for experiments events
 export const EXPERIMENT = {
   // An experiment was started
@@ -12,4 +15,4 @@ export const EXPERIMENT = {
   VALIDATED: 'EXPERIMENT_VALIDATED'
 }
 
-export default { EXPERIMENT }
+export default { COLLECT_DATA, EXPERIMENT }

+ 2 - 0
package.json

@@ -13,6 +13,7 @@
   },
   "dependencies": {
     "@hapi/boom": "^7.4.2",
+    "body-parser": "^1.19.0",
     "compression": "^1.7.4",
     "core-js": "^2.6.5",
     "cors": "^2.8.5",
@@ -23,6 +24,7 @@
     "morgan": "^1.9.1",
     "serve-static": "^1.13.2",
     "sharp": "^0.22.1",
+    "ua-parser-js": "^0.7.19",
     "vue": "^2.6.10",
     "vue-native-websocket": "^2.0.13",
     "vue-router": "^3.0.6",

+ 63 - 0
server/routes/dataCollect.js

@@ -0,0 +1,63 @@
+'use strict'
+
+import express from 'express'
+import boom from '@hapi/boom'
+import userAgentParser from 'ua-parser-js'
+
+import { COLLECT_DATA } from '../../config.messagesId'
+import DataController from '../database/controllers/Data'
+import { asyncMiddleware, checkRequiredParameters } from '../functions'
+
+const router = express.Router()
+
+/**
+ * @api {post} /dataCollect /dataCollect
+ * @apiVersion 0.1.11
+ * @apiName dataCollect
+ * @apiGroup API
+ *
+ * @apiDescription Collect user's data
+ *
+ * @apiExample Usage example
+ * curl -i -L -H "Content-Type: application/json" -X POST "http://diran.univ-littoral.fr/api/dataCollect" -d '{"uuid":"test","viewport":{"x":1920,"y":1024}}'
+ *
+ * @apiSuccessExample {string} Success response example
+ * HTTP/1.1 200 OK /api/dataCollect
+ * OK
+ */
+
+router.post('/', asyncMiddleware(async (req, res) => {
+  // Check the request contains all the required body parameters
+  const b = req.body
+  checkRequiredParameters(['uuid', 'viewport'], b)
+
+  let errorList = []
+
+  if (typeof b.uuid !== 'string')
+    errorList.push('"uuid" must be a string.')
+
+  if (!Number.isInteger(b.viewport.x) || !Number.isInteger(b.viewport.y))
+    errorList.push('"viewport.x" and "viewport.y" must be integers.')
+
+  // Check there is no errors with parameters
+  if (errorList.length > 0)
+    throw boom.badRequest('Invalid body parameter(s).', errorList)
+
+  const userAgent = userAgentParser(req.headers['user-agent'])
+
+  // Collected data object
+  const data = {
+    uuid: b.uuid,
+    msgId: COLLECT_DATA,
+    msg: {
+      viewport: b.viewport,
+      userAgent,
+      ip: req.ip
+    }
+  }
+
+  await DataController.add(data)
+  res.send('OK')
+}))
+
+export default router

+ 3 - 1
server/routes/index.js

@@ -6,6 +6,7 @@ import listSceneQualities from './listSceneQualities'
 import getImage from './getImage'
 import getImageExtracts from './getImageExtracts'
 import ping from './ping'
+import dataCollect from './dataCollect'
 
 const router = express.Router()
 
@@ -13,6 +14,7 @@ router.use('/listScenes', listScenes)
 router.use('/listSceneQualities', listSceneQualities)
 router.use('/getImage', getImage)
 router.use('/getImageExtracts', getImageExtracts)
-router.get('/ping', ping)
+router.use('/dataCollect', dataCollect)
+router.use('/ping', ping)
 
 export default router

+ 1 - 1
server/routes/ping.js

@@ -20,6 +20,6 @@ const router = express.Router()
  * pong
  */
 
-router.get('/ping', (req, res) => res.send('pong'))
+router.get('/', (req, res) => res.send('pong'))
 
 export default router

+ 1 - 4
server/webSocket/index.js

@@ -33,10 +33,7 @@ const createWsServer = httpServer => {
   wss.on('listening', () => wsLogger.info(formatLog('The WebSocket server was started')))
   wss.on('error', err => wsLogger.error(formatError(err)))
 
-  wss.on('connection', (ws, req) => {
-    // Unique identifier passed with the request url
-    ws.uuid = req.url.replace('/?uuid=', '')
-
+  wss.on('connection', ws => {
     wsLogger.info(formatLog('New client connected.'))
 
     ws.on('message', data => messageHandler(ws)(data).catch(err => errorHandler(ws)(err)))

+ 2 - 1
server/webSocket/messageHandler.js

@@ -21,8 +21,9 @@ const messageHandler = ws => async data => {
   catch (err) {
     throw new Error('Invalid JSON data.')
   }
+  if (!json.uuid)
+    throw new Error('"uuid" was not provided.')
 
-  json.WS_UNIQUE_UUID = ws.uuid
   await DataController.add(json)
   if (!TEST_MODE) wsLogger.info(formatLog(json, 'message'))
   ws.send('{"message":"ok"}')

+ 2 - 0
src/functions.js

@@ -2,6 +2,8 @@ export const API_PREFIX = '/api'
 export const API_ROUTES = {
   ping: () => `${API_PREFIX}/ping`,
 
+  dataCollect: () => `${API_PREFIX}/dataCollect`,
+
   listScenes: () => `${API_PREFIX}/listScenes`,
 
   listSceneQualities: sceneName => `${API_PREFIX}/listSceneQualities?${new URLSearchParams({ sceneName })}`,

+ 21 - 3
src/store/actions.js

@@ -18,7 +18,7 @@ export default {
     commit('resetApp', { gdprConsent, hostConfig, progression })
   },
 
-  async setHostConfig({ state, commit }, { ssl, host, port }) {
+  async setHostConfig({ state, commit, dispatch }, { ssl, host, port }) {
     // Timeout after 1s
     const controller = new AbortController()
     const signal = controller.signal
@@ -39,9 +39,11 @@ export default {
         if (!state.socket.isConnected)
           throw new Error('Could not connect to remote WebSocket server.')
 
+
         // Configuration is valid
         commit('setHostConfig', { ssl, host, port })
         router.push('/experiments')
+        dispatch('collectUserData')
       })
       .catch(err => {
         // Host not reachable or invalid HTTP status code
@@ -61,8 +63,24 @@ export default {
     else throw new Error('Could not connect to WebSocket server. Host is not configured.')
   },
 
-  sendMessage(_, { msgId, msg = undefined }) {
-    Vue.prototype.$socket.send(JSON.stringify({ msgId, msg }))
+  async collectUserData({ state, getters }) {
+    return fetch(getters.getHostURI + API_ROUTES.dataCollect(), {
+      method: 'POST',
+      headers: {
+        'Content-Type': 'application/json'
+      },
+      body: JSON.stringify({
+        uuid: state.uuid,
+        viewport: {
+          x: window.innerWidth,
+          y: window.innerHeight
+        }
+      })
+    })
+  },
+
+  sendMessage({ state }, { msgId, msg = undefined }) {
+    Vue.prototype.$socket.send(JSON.stringify({ uuid: state.uuid, msgId, msg }))
   },
 
   async loadScenesList({ getters: { isHostConfigured, getHostURI }, commit }) {

+ 61 - 4
yarn.lock

@@ -1828,6 +1828,22 @@ body-parser@1.18.3:
     raw-body "2.3.3"
     type-is "~1.6.16"
 
+body-parser@^1.19.0:
+  version "1.19.0"
+  resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a"
+  integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==
+  dependencies:
+    bytes "3.1.0"
+    content-type "~1.0.4"
+    debug "2.6.9"
+    depd "~1.1.2"
+    http-errors "1.7.2"
+    iconv-lite "0.4.24"
+    on-finished "~2.3.0"
+    qs "6.7.0"
+    raw-body "2.4.0"
+    type-is "~1.6.17"
+
 bonjour@^3.5.0:
   version "3.5.0"
   resolved "https://registry.yarnpkg.com/bonjour/-/bonjour-3.5.0.tgz#8e890a183d8ee9a2393b3844c691a42bcf7bc9f5"
@@ -2012,6 +2028,11 @@ bytes@3.0.0:
   resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048"
   integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=
 
+bytes@3.1.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
+  integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==
+
 cacache@^10.0.4:
   version "10.0.4"
   resolved "https://registry.yarnpkg.com/cacache/-/cacache-10.0.4.tgz#6452367999eff9d4188aefd9a14e9d7c6a263460"
@@ -4840,6 +4861,17 @@ http-errors@1.6.3, http-errors@~1.6.2, http-errors@~1.6.3:
     setprototypeof "1.1.0"
     statuses ">= 1.4.0 < 2"
 
+http-errors@1.7.2:
+  version "1.7.2"
+  resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f"
+  integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==
+  dependencies:
+    depd "~1.1.2"
+    inherits "2.0.3"
+    setprototypeof "1.1.1"
+    statuses ">= 1.5.0 < 2"
+    toidentifier "1.0.0"
+
 http-parser-js@>=0.4.0:
   version "0.5.0"
   resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.0.tgz#d65edbede84349d0dc30320815a15d39cc3cbbd8"
@@ -4885,7 +4917,7 @@ iconv-lite@0.4.23:
   dependencies:
     safer-buffer ">= 2.1.2 < 3"
 
-iconv-lite@^0.4.17, iconv-lite@^0.4.24, iconv-lite@^0.4.4:
+iconv-lite@0.4.24, iconv-lite@^0.4.17, iconv-lite@^0.4.24, iconv-lite@^0.4.4:
   version "0.4.24"
   resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
   integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
@@ -7695,7 +7727,7 @@ qs@6.5.2, qs@~6.5.2:
   resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
   integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==
 
-qs@^6.5.1:
+qs@6.7.0, qs@^6.5.1:
   version "6.7.0"
   resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
   integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==
@@ -7759,6 +7791,16 @@ raw-body@2.3.3:
     iconv-lite "0.4.23"
     unpipe "1.0.0"
 
+raw-body@2.4.0:
+  version "2.4.0"
+  resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332"
+  integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==
+  dependencies:
+    bytes "3.1.0"
+    http-errors "1.7.2"
+    iconv-lite "0.4.24"
+    unpipe "1.0.0"
+
 rc@^1.0.1, rc@^1.1.6, rc@^1.2.7:
   version "1.2.8"
   resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
@@ -8377,6 +8419,11 @@ setprototypeof@1.1.0:
   resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656"
   integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==
 
+setprototypeof@1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683"
+  integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==
+
 sha.js@^2.4.0, sha.js@^2.4.8:
   version "2.4.11"
   resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7"
@@ -8727,7 +8774,7 @@ static-extend@^0.1.1:
     define-property "^0.2.5"
     object-copy "^0.1.0"
 
-"statuses@>= 1.4.0 < 2":
+"statuses@>= 1.4.0 < 2", "statuses@>= 1.5.0 < 2":
   version "1.5.0"
   resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
   integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=
@@ -9217,6 +9264,11 @@ to-regex@^3.0.1, to-regex@^3.0.2:
     regex-not "^1.0.2"
     safe-regex "^1.1.0"
 
+toidentifier@1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
+  integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==
+
 topo@3.x.x:
   version "3.0.3"
   resolved "https://registry.yarnpkg.com/topo/-/topo-3.0.3.tgz#d5a67fb2e69307ebeeb08402ec2a2a6f5f7ad95c"
@@ -9309,7 +9361,7 @@ type-fest@^0.4.1:
   resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.4.1.tgz#8bdf77743385d8a4f13ba95f610f5ccd68c728f8"
   integrity sha512-IwzA/LSfD2vC1/YDYMv/zHP4rDF1usCwllsDpbolT3D4fUepIO7f9K70jjmUewU/LmGUKJcwcVtDCpnKk4BPMw==
 
-type-is@~1.6.16:
+type-is@~1.6.16, type-is@~1.6.17:
   version "1.6.18"
   resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
   integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==
@@ -9322,6 +9374,11 @@ typedarray@^0.0.6:
   resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
   integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
 
+ua-parser-js@^0.7.19:
+  version "0.7.19"
+  resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.19.tgz#94151be4c0a7fb1d001af7022fdaca4642659e4b"
+  integrity sha512-T3PVJ6uz8i0HzPxOF9SWzWAlfN/DavlpQqepn22xgve/5QecC+XMCAtmUNnY7C9StehaV6exjUCI801lOI7QlQ==
+
 uc.micro@^1.0.1, uc.micro@^1.0.5:
   version "1.0.6"
   resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac"