123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293 |
- const Provider = require('../Provider')
- const request = require('request')
- const moment = require('moment')
- const purest = require('purest')({ request })
- const logger = require('../../logger')
- const adapter = require('./adapter')
- const { ProviderApiError, ProviderAuthError } = require('../error')
- const BASE_URL = 'https://zoom.us/v2'
- const GET_LIST_PATH = '/users/me/recordings'
- const GET_USER_PATH = '/users/me'
- const PAGE_SIZE = 300
- const DEFAULT_RANGE_MOS = 23
- /**
- * Adapter for API https://marketplace.zoom.us/docs/api-reference/zoom-api
- */
- class Zoom extends Provider {
- constructor (options) {
- super(options)
- this.authProvider = options.provider = Zoom.authProvider
- this.client = purest(options)
- }
- static get authProvider () {
- return 'zoom'
- }
- list (options, done) {
- /*
- - returns list of months by default
- - drill down for specific files in each month
- */
- const token = options.token
- 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)
- })
- })
- 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)
- }
- })
- })
- } else if (meetingId) {
- const GET_MEETING_FILES = `/meetings/${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)
- }
- })
- })
- }
- 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)
- }
- )
- }
- download ({ id, token, query }, done) {
- // meeting id + file id required
- // timeline files don't have an ID or size
- const meetingId = id
- const fileId = query.recordingId
- const GET_MEETING_FILES = `/meetings/${meetingId}/recordings`
- const downloadUrlPromise = new Promise((resolve) => {
- this.client
- .get(`${BASE_URL}${GET_MEETING_FILES}`)
- .auth(token)
- .request((err, resp) => {
- if (err || resp.statusCode !== 200) {
- return this._downloadError(resp, done)
- }
- const file = resp
- .body
- .recording_files
- .find(file => fileId === file.id || fileId === file.file_type)
- if (!file || !file.download_url) {
- return this._downloadError(resp, done)
- }
- resolve(file.download_url)
- })
- })
- downloadUrlPromise.then((downloadUrl) => {
- this.client
- .get(`${downloadUrl}?access_token=${token}`)
- .request()
- .on('response', (resp) => {
- if (resp.statusCode !== 200) {
- done(this._error(null, resp))
- } else {
- resp.on('data', (chunk) => done(null, chunk))
- }
- })
- .on('end', () => {
- done(null, null)
- })
- .on('error', (err) => {
- logger.error(err, 'provider.zoom.download.error')
- done(err)
- })
- })
- }
- size ({ id, token, query }, done) {
- const meetingId = id
- const fileId = query.recordingId
- const GET_MEETING_FILES = `/meetings/${meetingId}/recordings`
- return this.client
- .get(`${BASE_URL}${GET_MEETING_FILES}`)
- .auth(token)
- .request((err, resp, body) => {
- if (err || resp.statusCode !== 200) {
- return this._downloadError(resp, done)
- }
- const file = resp
- .body
- .recording_files
- .find(file => file.id === fileId || file.file_type === fileId)
- if (!file) {
- return this._downloadError(resp, done)
- }
- // timeline files don't have file size, but are typically small json files, should be much less than 1MB
- const maxExportFileSize = 1024 * 1024
- done(null, file.file_size || maxExportFileSize)
- })
- }
- _initializeData (body, initialEnd = null) {
- let end = initialEnd || moment()
- let start = end.clone().date(1)
- const accountCreation = adapter.getAccountCreationDate(body)
- const defaultLimit = end.clone().subtract(DEFAULT_RANGE_MOS, 'months')
- const limit = accountCreation > defaultLimit ? accountCreation : defaultLimit
- const data = {
- items: [],
- username: adapter.getUserEmail(body)
- }
- while (start > limit) {
- start = end.clone().date(1)
- data.items.push({
- isFolder: true,
- icon: 'folder',
- name: adapter.getDateName(start, end),
- mimeType: null,
- id: adapter.getDateFolderId(start, end),
- thumbnail: null,
- requestPath: adapter.getDateFolderRequestPath(start, end),
- modifiedDate: adapter.getDateFolderModified(end),
- size: null
- })
- end = start.clone().subtract(1, 'days')
- }
- data.nextPagePath = adapter.getDateNextPagePath(start)
- return data
- }
- _adaptData (userResponse, results) {
- if (!results) {
- return { items: [] }
- }
- const data = {
- nextPagePath: adapter.getNextPagePath(results),
- items: [],
- username: adapter.getUserEmail(userResponse)
- }
- const items = results.meetings || results.recording_files
- items.forEach(item => {
- data.items.push({
- isFolder: adapter.getIsFolder(item),
- icon: adapter.getIcon(item),
- name: adapter.getItemName(item),
- mimeType: adapter.getMimeType(item),
- id: adapter.getId(item),
- thumbnail: null,
- requestPath: adapter.getRequestPath(item),
- modifiedDate: adapter.getStartDate(item),
- size: adapter.getSize(item)
- })
- })
- return data
- }
- logout ({ companion, token }, done) {
- const key = companion.options.providerOptions.zoom.key
- const secret = companion.options.providerOptions.zoom.secret
- const encodedAuth = Buffer.from(`${key}:${secret}`, 'binary').toString('base64')
- return this.client
- .post('https://zoom.us/oauth/revoke')
- .options({
- headers: {
- Authorization: `Basic ${encodedAuth}`
- }
- })
- .qs({ token })
- .request((err, resp, body) => {
- if (err || resp.statusCode !== 200) {
- logger.error(err, 'provider.zoom.logout.error')
- done(this._error(err, resp))
- return
- }
- done(null, { revoked: (body || {}).status === 'success' })
- })
- }
- _error (err, resp) {
- const authErrorCodes = [
- 124, // expired token
- 401
- ]
- if (resp) {
- const fallbackMsg = `request to ${this.authProvider} returned ${resp.statusCode}`
- const errMsg = (resp.body || {}).message ? resp.body.message : fallbackMsg
- return authErrorCodes.indexOf(resp.statusCode) > -1 ? new ProviderAuthError() : new ProviderApiError(errMsg, resp.statusCode)
- }
- return err
- }
- _downloadError (resp, done) {
- const error = this._error(null, resp)
- logger.error(error, 'provider.zoom.download.error')
- return done(error)
- }
- _listError (err, resp, done) {
- const error = this._error(err, resp)
- logger.error(error, 'provider.zoom.list.error')
- return done(error)
- }
- }
- module.exports = Zoom
|