(core) Adding google drive plugin as a fallback for url plugin

Summary:
When importing from url, user types a url for google spreadsheet,
Grist will switch to Google Drive plugin to allow user to choose file manualy.

Test Plan: Browser tests

Reviewers: paulfitz, dsagal

Reviewed By: paulfitz

Differential Revision: https://phab.getgrist.com/D2945
This commit is contained in:
Jarosław Sadziński
2021-08-03 12:34:05 +02:00
parent 5aed22dc1e
commit 6ed1d8dfea
7 changed files with 162 additions and 20 deletions

View File

@@ -760,7 +760,7 @@ export class FlexServer implements GristServer {
// Those keys are eventually visible by the client, but should be usable
// only from Grist's domains.
const secrets = {
googleClientId : config.googleClientId,
googleClientId: config.googleClientId,
};
res.set('Content-Type', 'application/javascript');
res.status(200);

View File

@@ -0,0 +1,72 @@
import {drive} from '@googleapis/drive';
import {Readable} from 'form-data';
import {GaxiosError, GaxiosPromise} from 'gaxios';
import {FetchError, Response as FetchResponse, Headers} from 'node-fetch';
const
SPREADSHEETS_MIMETYPE = 'application/vnd.google-apps.spreadsheet',
XLSX_MIMETYPE = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
export async function downloadFromGDrive(url: string) {
const fileId = fileIdFromUrl(url);
const googleDrive = drive("v3");
const key = process.env.GOOGLE_API_KEY;
if (!key) {
throw new Error("Can't download file from Google Drive. Api key is not configured");
}
if (!fileId) {
throw new Error(`Can't download from ${url}. Url is not valid`);
}
const fileRes = await googleDrive.files.get({
key,
fileId
});
if (fileRes.data.mimeType === SPREADSHEETS_MIMETYPE) {
return await asFetchResponse(googleDrive.files.export(
{ key, fileId, alt: 'media', mimeType: XLSX_MIMETYPE },
{ responseType: 'stream' }
));
} else {
return await asFetchResponse(googleDrive.files.get(
{ key, fileId, alt: 'media' },
{ responseType: 'stream' }
));
}
}
async function asFetchResponse(req: GaxiosPromise<Readable>) {
try {
const res = await req;
return new FetchResponse(res.data, {
headers: new Headers(res.headers),
status: res.status,
statusText: res.statusText
});
} catch (err) {
const error: GaxiosError<Readable> = err;
if (!error.response) {
// Fetch throws exception on network error.
// https://github.com/node-fetch/node-fetch/blob/master/docs/ERROR-HANDLING.md
throw new FetchError(error.message, "system", error.code || "unknown");
} else {
// Fetch returns failure response on http error
const resInit = error.response ? {
status: error.response.status,
headers: new Headers(error.response.headers),
statusText: error.response.statusText
} : undefined;
return new FetchResponse(error.response.data, resInit);
}
}
}
export function isDriveUrl(url: string) {
return !!fileIdFromUrl(url);
}
function fileIdFromUrl(url: string) {
if (!url) { return null; }
const match = /^https:\/\/(docs|drive).google.com\/(spreadsheets|file)\/d\/([^/]*)/i.exec(url);
return match ? match[3] : null;
}

View File

@@ -5,6 +5,7 @@ import {getAuthorizedUserId, getTransitiveHeaders, getUserId, isSingleUserMode,
RequestWithLogin} from 'app/server/lib/Authorizer';
import {expressWrap} from 'app/server/lib/expressWrap';
import {RequestWithGrist} from 'app/server/lib/FlexServer';
import {downloadFromGDrive, isDriveUrl} from 'app/server/lib/GoogleImport';
import {GristServer} from 'app/server/lib/GristServer';
import {guessExt} from 'app/server/lib/guessExt';
import * as log from 'app/server/lib/log';
@@ -338,11 +339,16 @@ export async function fetchURL(url: string, accessId: string|null): Promise<Uplo
async function _fetchURL(url: string, accessId: string|null, fileName: string,
headers?: {[key: string]: string}): Promise<UploadResult> {
try {
const response: FetchResponse = await Deps.fetch(url, {
redirect: 'follow',
follow: 10,
headers
});
let response: FetchResponse;
if (isDriveUrl(url)) {
response = await downloadFromGDrive(url);
} else {
response = await Deps.fetch(url, {
redirect: 'follow',
follow: 10,
headers
});
}
await _checkForError(response);
if (fileName === '') {
const disposition = response.headers.get('content-disposition') || '';