@uppy/companion
Version:
OAuth helper and remote fetcher for Uppy's (https://uppy.io) extensible file upload widget with support for drag&drop, resumable uploads, previews, restrictions, file processing/encoding, remote providers like Dropbox and Google Drive, S3 and more :dog:
146 lines (145 loc) • 6.69 kB
JavaScript
;
var _a;
Object.defineProperty(exports, "__esModule", { value: true });
const moment = require('moment-timezone');
const Provider = require('../Provider');
const { initializeData, adaptData } = require('./adapter');
const { withProviderErrorHandling } = require('../providerErrors');
const { prepareStream, getBasicAuthHeader } = require('../../helpers/utils');
const got = require('../../got');
const BASE_URL = 'https://zoom.us/v2';
const PAGE_SIZE = 300;
const DEAUTH_EVENT_NAME = 'app_deauthorized';
const getClient = async ({ token }) => (await got).extend({
prefixUrl: BASE_URL,
headers: {
authorization: `Bearer ${token}`,
},
});
async function findFile({ client, meetingId, fileId, recordingStart }) {
const { recording_files: files } = await client.get(`meetings/${encodeURIComponent(meetingId)}/recordings`, { responseType: 'json' }).json();
return files.find((file) => (fileId === file.id || (file.file_type === fileId && file.recording_start === recordingStart)));
}
/**
* Adapter for API https://marketplace.zoom.us/docs/api-reference/zoom-api
*/
class Zoom extends Provider {
static get oauthProvider() {
return 'zoom';
}
/*
- returns list of months by default
- drill down for specific files in each month
*/
async list(options) {
return this.#withErrorHandling('provider.zoom.list.error', async () => {
const { token } = options;
const query = options.query || {};
const { cursor, from, to } = query;
const meetingId = options.directory || '';
const client = await getClient({ token });
const user = await client.get('users/me', { responseType: 'json' }).json();
const { timezone } = user;
if (!from && !to && !meetingId) {
const end = cursor && moment.utc(cursor).endOf('day').tz(timezone || 'UTC');
return initializeData(user, end);
}
if (from && to) {
/* 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 searchParams = {
page_size: PAGE_SIZE,
from: moment.tz(from, timezone || 'UTC').startOf('day').tz('UTC').format('YYYY-MM-DD'),
to: moment.tz(to, timezone || 'UTC').endOf('day').tz('UTC').format('YYYY-MM-DD'),
};
if (cursor)
searchParams.next_page_token = cursor;
const meetingsInfo = await client.get('users/me/recordings', { searchParams, responseType: 'json' }).json();
return adaptData(user, meetingsInfo, query);
}
if (meetingId) {
const recordingInfo = await client.get(`meetings/${encodeURIComponent(meetingId)}/recordings`, { responseType: 'json' }).json();
return adaptData(user, recordingInfo, query);
}
throw new Error('Invalid list() arguments');
});
}
async download({ id: meetingId, token, query }) {
return this.#withErrorHandling('provider.zoom.download.error', async () => {
// meeting id + file id required
// cc files don't have an ID or size
const { recordingStart, recordingId: fileId } = query;
const client = await getClient({ token });
const foundFile = await findFile({ client, meetingId, fileId, recordingStart });
const url = foundFile?.download_url;
if (!url)
throw new Error('Download URL not found');
const stream = client.stream.get(`${url}?access_token=${token}`, { prefixUrl: '', responseType: 'json' });
const { size } = await prepareStream(stream);
return { stream, size };
});
}
async size({ id: meetingId, token, query }) {
return this.#withErrorHandling('provider.zoom.size.error', async () => {
const client = await getClient({ token });
const { recordingStart, recordingId: fileId } = query;
const foundFile = await findFile({ client, meetingId, fileId, recordingStart });
if (!foundFile)
throw new Error('File not found');
return foundFile.file_size; // Note: May be undefined.
});
}
async logout({ companion, token }) {
return this.#withErrorHandling('provider.zoom.logout.error', async () => {
const { key, secret } = await companion.getProviderCredentials();
const { status } = await (await got).post('https://zoom.us/oauth/revoke', {
searchParams: { token },
headers: { Authorization: getBasicAuthHeader(key, secret) },
responseType: 'json',
}).json();
return { revoked: status === 'success' };
});
}
async deauthorizationCallback({ companion, body, headers }) {
return this.#withErrorHandling('provider.zoom.deauth.error', async () => {
if (!body || body.event !== DEAUTH_EVENT_NAME) {
return { data: {}, status: 400 };
}
const { verificationToken, key, secret } = await companion.getProviderCredentials();
const tokenSupplied = headers.authorization;
if (!tokenSupplied || verificationToken !== tokenSupplied) {
return { data: {}, status: 400 };
}
await (await got).post('https://api.zoom.us/oauth/data/compliance', {
headers: { Authorization: getBasicAuthHeader(key, secret) },
json: {
client_id: key,
user_id: body.payload.user_id,
account_id: body.payload.account_id,
deauthorization_event_received: body.payload,
compliance_completed: true,
},
responseType: 'json',
});
return {};
});
}
// eslint-disable-next-line class-methods-use-this
async #withErrorHandling(tag, fn) {
const authErrorCodes = [
124, // expired token
401,
];
return withProviderErrorHandling({
fn,
tag,
providerName: _a.oauthProvider,
isAuthError: (response) => authErrorCodes.includes(response.statusCode),
getJsonErrorMessage: (body) => body?.message,
});
}
}
_a = Zoom;
module.exports = Zoom;