Procházet zdrojové kódy

Zoom Plugin - Fix meeting timestamp for user timezones and explicitly include moment-timezone dependency (#2525)

* Explicitly add moment timezone as a dependency and fix timezones for queries and displaying data to be based on user zoom timezone setting

* wip

* Remove async/await to match other providers, add comment to explain to/from conversion from local time to UTC

* Update comment, rename meetingInfo to plural to indicate multiple are retrieved
mokutsu-coursera před 4 roky
rodič
revize
46aad2ab94

+ 6 - 4
package-lock.json

@@ -8057,6 +8057,8 @@
           "integrity": "sha512-cRQ5Qw87SNS4b2v+UfjUW5uAVx4nv6pj9I0cu6ys8x+t7NjVo3cOjaT4Bns1/xxy6xzWv/4WT0LbG5h+NNGNMA==",
           "requires": {
             "@types/tus-js-client": "^1.8.0",
+            "@uppy/companion-client": "^1.5.0",
+            "@uppy/utils": "^3.1.0",
             "tus-js-client": "^1.8.0"
           }
         },
@@ -8121,7 +8123,7 @@
         "browserify": "^16.5.2",
         "drag-drop": "^4.2.0",
         "ecstatic": "^3.0.0",
-        "marked": "^0.6.0"
+        "marked": "^0.7.0"
       }
     },
     "@uppy-example/uppy-with-companion": {
@@ -8189,6 +8191,7 @@
         "jsonwebtoken": "8.5.1",
         "lodash.merge": "4.6.2",
         "mime-types": "2.1.25",
+        "moment-timezone": "^0.5.31",
         "morgan": "1.10.0",
         "ms": "2.1.2",
         "node-redis-pubsub": "4.0.0",
@@ -22034,7 +22037,6 @@
       "version": "1.18.1",
       "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz",
       "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==",
-      "dev": true,
       "requires": {
         "eventemitter3": "^4.0.0",
         "follow-redirects": "^1.0.0",
@@ -22044,8 +22046,7 @@
         "eventemitter3": {
           "version": "4.0.7",
           "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
-          "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
-          "dev": true
+          "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="
         }
       }
     },
@@ -26996,6 +26997,7 @@
         "connect-history-api-fallback": "^1.6.0",
         "connect-injector": "^0.4.4",
         "gaze": "^1.1.3",
+        "http-proxy": "^1.18.1",
         "morgan": "~1.10.0",
         "opener": "^1.5.1",
         "parseurl": "^1.3.3",

+ 1 - 0
packages/@uppy/companion/package.json

@@ -49,6 +49,7 @@
     "isobject": "3.0.1",
     "jsonwebtoken": "8.5.1",
     "lodash.merge": "4.6.2",
+    "moment-timezone": "^0.5.31",
     "mime-types": "2.1.25",
     "morgan": "1.10.0",
     "ms": "2.1.2",

+ 5 - 6
packages/@uppy/companion/src/server/provider/zoom/adapter.js

@@ -1,4 +1,4 @@
-const moment = require('moment')
+const moment = require('moment-timezone')
 
 const MIMETYPES = {
   MP4: 'video/mp4',
@@ -31,7 +31,7 @@ exports.getDateName = (start, end) => {
 }
 
 exports.getAccountCreationDate = (results) => {
-  return moment(results.created_at)
+  return moment.utc(results.created_at)
 }
 
 exports.getUserEmail = (results) => {
@@ -65,10 +65,9 @@ exports.getIsFolder = (item) => {
   return !item.file_type
 }
 
-exports.getItemName = (item) => {
-  const start = moment(item.start_time || item.recording_start)
-    .clone()
-    .format('YYYY-MM-DD, kk:mm')
+exports.getItemName = (item, userResponse) => {
+  const start = moment.tz(item.start_time || item.recording_start, userResponse.timezone || 'UTC')
+    .format('YYYY-MM-DD, HH:mm')
 
   if (item.file_type) {
     const ext = EXT[item.file_type] ? `.${EXT[item.file_type]}` : ''

+ 77 - 76
packages/@uppy/companion/src/server/provider/zoom/index.js

@@ -1,7 +1,7 @@
 const Provider = require('../Provider')
 
 const request = require('request')
-const moment = require('moment')
+const moment = require('moment-timezone')
 const purest = require('purest')({ request })
 const logger = require('../../logger')
 const adapter = require('./adapter')
@@ -37,78 +37,66 @@ class Zoom extends Provider {
     const query = options.query || {}
     const { cursor, from, to } = query
     const meetingId = options.directory || ''
-    let meetingsPromise = Promise.resolve(undefined)
-    let recordingsPromise = Promise.resolve(undefined)
 
-    const userPromise = new Promise((resolve, reject) => {
-      this.client
-        .get(`${BASE_URL}${GET_USER_PATH}`)
-        .auth(token)
-        .request((err, resp, body) => {
-          if (err || resp.statusCode !== 200) {
-            return this._listError(err, resp, done)
-          }
-          resolve(resp)
-        })
-    })
+    this.client
+      .get(`${BASE_URL}${GET_USER_PATH}`)
+      .auth(token)
+      .request((err, userResponse, userBody) => {
+        if (err || userResponse.statusCode !== 200) {
+          return this._listError(err, userResponse, done)
+        }
+
+        if (!from && !to && !meetingId) {
+          const end = cursor && moment.utc(cursor).endOf('day').tz(userResponse.timezone || 'UTC')
+          return done(null, this._initializeData(userResponse.body, end))
+        }
 
-    if (from && to) {
-      const queryObj = {
-        page_size: PAGE_SIZE,
-        from,
-        to
-      }
-
-      if (cursor) {
-        queryObj.next_page_token = cursor
-      }
-
-      meetingsPromise = new Promise((resolve, reject) => {
-        this.client.get(`${BASE_URL}${GET_LIST_PATH}`)
-          .qs(queryObj)
-          .auth(token)
-          .request((err, resp, body) => {
-            if (err || resp.statusCode !== 200) {
-              return this._listError(err, resp, done)
-            } else {
-              resolve(resp)
+        if (from && to) {
+          this._meetingsInfo({ token, query }, userResponse, (err, meetingResp) => {
+            if (err || meetingResp.statusCode !== 200) {
+              return this._listError(err, meetingResp, done)
             }
+            done(null, this._adaptData(userResponse.body, meetingResp.body, query))
           })
-      })
-    } else if (meetingId) {
-      const GET_MEETING_FILES = `/meetings/${encodeURIComponent(meetingId)}/recordings`
-      recordingsPromise = new Promise((resolve, reject) => {
-        this.client
-          .get(`${BASE_URL}${GET_MEETING_FILES}`)
-          .auth(token)
-          .request((err, resp, body) => {
-            if (err || resp.statusCode !== 200) {
-              return this._listError(err, resp, done)
-            } else {
-              resolve(resp)
+        } else if (meetingId) {
+          this._recordingInfo({ token }, meetingId, (err, recordingResp) => {
+            if (err || recordingResp.statusCode !== 200) {
+              return this._listError(err, recordingResp, done)
             }
+            done(null, this._adaptData(userResponse.body, recordingResp.body, query))
           })
+        }
       })
+  }
+
+  _meetingsInfo ({ token, query }, userResponse, done) {
+    const { cursor, from, to } = query
+    /*  we need to convert local datetime to UTC date for Zoom query
+    eg: user in PST (UTC-08:00) wants 2020-08-01 (00:00) to 2020-08-31 (23:59)
+    => in UTC, that's 2020-07-31 (16:00) to 2020-08-31 (15:59)
+    */
+    const queryObj = {
+      page_size: PAGE_SIZE,
+      from: moment.tz(from, userResponse.body.timezone || 'UTC').startOf('day').tz('UTC').format('YYYY-MM-DD'),
+      to: moment.tz(to, userResponse.body.timezone || 'UTC').endOf('day').tz('UTC').format('YYYY-MM-DD')
     }
 
-    Promise.all([userPromise, meetingsPromise, recordingsPromise])
-      .then(
-        ([userResponse, meetingsResponse, recordingsResponse]) => {
-          let returnData = null
-          if (!meetingsResponse && !recordingsResponse) {
-            const end = cursor && moment(cursor)
-            returnData = this._initializeData(userResponse.body, end)
-          } else if (meetingsResponse) {
-            returnData = this._adaptData(userResponse.body, meetingsResponse.body)
-          } else if (recordingsResponse) {
-            returnData = this._adaptData(userResponse.body, recordingsResponse.body)
-          }
-          done(null, returnData)
-        },
-        (reqErr) => {
-          done(reqErr)
-        }
-      )
+    if (cursor) {
+      queryObj.next_page_token = cursor
+    }
+
+    this.client.get(`${BASE_URL}${GET_LIST_PATH}`)
+      .qs(queryObj)
+      .auth(token)
+      .request(done)
+  }
+
+  _recordingInfo ({ token }, meetingId, done) {
+    const GET_MEETING_FILES = `/meetings/${encodeURIComponent(meetingId)}/recordings`
+    this.client
+      .get(`${BASE_URL}${GET_MEETING_FILES}`)
+      .auth(token)
+      .request(done)
   }
 
   download ({ id, token, query }, done) {
@@ -117,7 +105,7 @@ class Zoom extends Provider {
     const meetingId = id
     const fileId = query.recordingId
     const recordingStart = query.recordingStart
-    const GET_MEETING_FILES = `/meetings/${meetingId}/recordings`
+    const GET_MEETING_FILES = `/meetings/${encodeURIComponent(meetingId)}/recordings`
 
     const downloadUrlPromise = new Promise((resolve) => {
       this.client
@@ -185,13 +173,13 @@ class Zoom extends Provider {
   }
 
   _initializeData (body, initialEnd = null) {
-    let end = initialEnd || moment()
-    const accountCreation = adapter.getAccountCreationDate(body)
-    const defaultLimit = end.clone().subtract(DEFAULT_RANGE_MOS, 'months').date(1)
+    let end = initialEnd || moment.utc().tz(body.timezone || 'UTC')
+    const accountCreation = adapter.getAccountCreationDate(body).tz(body.timezone || 'UTC').startOf('day')
+    const defaultLimit = end.clone().subtract(DEFAULT_RANGE_MOS, 'months').date(1).startOf('day')
     const allResultsShown = accountCreation > defaultLimit
     const limit = allResultsShown ? accountCreation : defaultLimit
     // if the limit is mid-month, keep that exact date
-    let start = (end.isSame(limit, 'month') && limit.date() !== 1) ? limit.clone() : end.clone().date(1)
+    let start = (end.isSame(limit, 'month') && limit.date() !== 1) ? limit.clone() : end.clone().date(1).startOf('day')
 
     const data = {
       items: [],
@@ -210,30 +198,43 @@ class Zoom extends Provider {
         modifiedDate: adapter.getDateFolderModified(end),
         size: null
       })
-      end = start.clone().subtract(1, 'days')
-      // if the limit is mid-month, keep that exact date
-      start = (end.isSame(limit, 'month') && limit.date() !== 1) ? limit.clone() : end.clone().date(1)
+      end = start.clone().subtract(1, 'days').endOf('day')
+      start = (end.isSame(limit, 'month') && limit.date() !== 1) ? limit.clone() : end.clone().date(1).startOf('day')
     }
-    data.nextPagePath = allResultsShown ? null : adapter.getDateNextPagePath(end.clone())
+    data.nextPagePath = allResultsShown ? null : adapter.getDateNextPagePath(end)
     return data
   }
 
-  _adaptData (userResponse, results) {
+  _adaptData (userResponse, results, query) {
     if (!results) {
       return { items: [] }
     }
 
+    // we query the zoom api by date (from 00:00 - 23:59 UTC) which may include extra results for 00:00 - 23:59 local time that we want to filter out
+    const utcFrom = moment.tz(query.from, userResponse.timezone || 'UTC').startOf('day').tz('UTC')
+    const utcTo = moment.tz(query.to, userResponse.timezone || 'UTC').endOf('day').tz('UTC')
+
     const data = {
       nextPagePath: adapter.getNextPagePath(results),
       items: [],
       username: adapter.getUserEmail(userResponse)
     }
-    const items = results.meetings || results.recording_files.filter(file => file.file_type !== 'TIMELINE')
+
+    let items = []
+    if (results.meetings) {
+      items = results.meetings
+        .map(item => { return { ...item, utcStart: moment.utc(item.start_time) } })
+        .filter(item => moment.utc(item.start_time).isAfter(utcFrom) && moment.utc(item.start_time).isBefore(utcTo))
+    } else {
+      items = results.recording_files
+        .map(item => item).filter(file => file.file_type !== 'TIMELINE')
+    }
+
     items.forEach(item => {
       data.items.push({
         isFolder: adapter.getIsFolder(item),
         icon: adapter.getIcon(item),
-        name: adapter.getItemName(item),
+        name: adapter.getItemName(item, userResponse),
         mimeType: adapter.getMimeType(item),
         id: adapter.getId(item),
         thumbnail: null,