|
|
|
@@ -1,6 +1,7 @@
|
|
|
|
|
import { DriveFileType, IDriveService } from '@/interfaces/drive.interface';
|
|
|
|
|
import apiService from './api.service';
|
|
|
|
|
import logger from '../lib/logger';
|
|
|
|
|
import * as Utils from '../utils';
|
|
|
|
|
import { Stream } from 'stream';
|
|
|
|
|
import FormData from 'form-data';
|
|
|
|
|
import { INSTANCE_ID } from '@/config';
|
|
|
|
@@ -8,6 +9,12 @@ import { INSTANCE_ID } from '@/config';
|
|
|
|
|
/** Thrown when Twake Drive returns a 404 for a key */
|
|
|
|
|
export class UnknownKeyInDriveError extends Error {}
|
|
|
|
|
|
|
|
|
|
const makeTwakeDriveApiUrl = (paths: string[], params?: Utils.QueryParams) => Utils.joinURL(['/internal/services/documents/v1', ...paths], params);
|
|
|
|
|
const makeNonEditingSessionItemUrl = (company_id: string, drive_file_id: string, paths: string[] = [], params?: Utils.QueryParams) =>
|
|
|
|
|
makeTwakeDriveApiUrl(['companies', company_id, 'item', drive_file_id, ...paths], params);
|
|
|
|
|
const makeEditingSessionItemUrl = (editing_session: string, params?: Utils.QueryParams) =>
|
|
|
|
|
makeTwakeDriveApiUrl(['editing_session', editing_session], params);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Client for Twake Drive's APIs dealing with `DriveItem`s, using {@see apiService}
|
|
|
|
|
* to handle authorization
|
|
|
|
@@ -17,7 +24,7 @@ class DriveService implements IDriveService {
|
|
|
|
|
try {
|
|
|
|
|
const { company_id, drive_file_id } = params;
|
|
|
|
|
const resource = await apiService.get<DriveFileType>({
|
|
|
|
|
url: `/internal/services/documents/v1/companies/${company_id}/item/${drive_file_id}`,
|
|
|
|
|
url: makeNonEditingSessionItemUrl(company_id, drive_file_id),
|
|
|
|
|
token: params.user_token,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
@@ -37,7 +44,7 @@ class DriveService implements IDriveService {
|
|
|
|
|
try {
|
|
|
|
|
const { company_id, drive_file_id, file_id } = params;
|
|
|
|
|
return await apiService.post<{}, DriveFileType['item']['last_version_cache']>({
|
|
|
|
|
url: `/internal/services/documents/v1/companies/${company_id}/item/${drive_file_id}/version`,
|
|
|
|
|
url: makeNonEditingSessionItemUrl(company_id, drive_file_id, ['version']),
|
|
|
|
|
payload: {
|
|
|
|
|
drive_item_id: drive_file_id,
|
|
|
|
|
provider: 'internal',
|
|
|
|
@@ -56,7 +63,7 @@ class DriveService implements IDriveService {
|
|
|
|
|
public async beginEditingSession(company_id: string, drive_file_id: string, user_token?: string) {
|
|
|
|
|
try {
|
|
|
|
|
const resource = await apiService.post<{}, { editingSessionKey: string }>({
|
|
|
|
|
url: `/internal/services/documents/v1/companies/${company_id}/item/${drive_file_id}/editing_session`,
|
|
|
|
|
url: makeNonEditingSessionItemUrl(company_id, drive_file_id, ['editing_session']),
|
|
|
|
|
token: user_token,
|
|
|
|
|
payload: {
|
|
|
|
|
editorApplicationId: 'tdrive_onlyoffice',
|
|
|
|
@@ -77,7 +84,7 @@ class DriveService implements IDriveService {
|
|
|
|
|
public async cancelEditing(editing_session_key: string) {
|
|
|
|
|
try {
|
|
|
|
|
await apiService.delete<{}>({
|
|
|
|
|
url: `/internal/services/documents/v1/editing_session/${encodeURIComponent(editing_session_key)}`,
|
|
|
|
|
url: makeEditingSessionItemUrl(editing_session_key),
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
logger.error('Failed to begin editing session: ', error.stack);
|
|
|
|
@@ -99,21 +106,6 @@ class DriveService implements IDriveService {
|
|
|
|
|
if (!url) {
|
|
|
|
|
throw Error('no url found');
|
|
|
|
|
}
|
|
|
|
|
//TODO: It would be better to avoid two requests for this operation
|
|
|
|
|
const originalFile = await this.getByEditingSessionKey({ editing_session_key });
|
|
|
|
|
|
|
|
|
|
if (!originalFile) {
|
|
|
|
|
// TODO: Make a single request and don't require the filename at all
|
|
|
|
|
// then in POST /editing_session/... if the key is not found, just
|
|
|
|
|
// put it in a users or company "lost and found" folder.
|
|
|
|
|
// and accept without error. Because really, if backend doesn't know
|
|
|
|
|
// the key anymore, there's not much we can do, and we should get OO
|
|
|
|
|
// to clean up and stop trying to upload it.
|
|
|
|
|
// but for today:
|
|
|
|
|
logger.error("Forgotten OO document that Twake Drive doesn't know the key of.", { editing_session_key, url });
|
|
|
|
|
throw new UnknownKeyInDriveError(`Unknown key ${JSON.stringify(editing_session_key)}`);
|
|
|
|
|
// TODO: Distinguish this case from a long disconnected browser tab waking up
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const newFile = await apiService.get<Stream>({
|
|
|
|
|
url,
|
|
|
|
@@ -122,47 +114,30 @@ class DriveService implements IDriveService {
|
|
|
|
|
|
|
|
|
|
const form = new FormData();
|
|
|
|
|
|
|
|
|
|
const filename = encodeURIComponent(originalFile.last_version_cache.file_metadata.name);
|
|
|
|
|
form.append('file', newFile);
|
|
|
|
|
|
|
|
|
|
form.append('file', newFile, {
|
|
|
|
|
filename,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
logger.info('Saving file version to Twake Drive: ', filename);
|
|
|
|
|
|
|
|
|
|
const queryString = keepEditing ? '?keepEditing=true' : '';
|
|
|
|
|
logger.info(`Saving file version to Twake Drive`, { editing_session_key, url });
|
|
|
|
|
|
|
|
|
|
await apiService.post({
|
|
|
|
|
url: `/internal/services/documents/v1/editing_session/${encodeURIComponent(editing_session_key)}` + queryString,
|
|
|
|
|
url: makeEditingSessionItemUrl(editing_session_key, {
|
|
|
|
|
keepEditing: keepEditing ? 'true' : null,
|
|
|
|
|
}),
|
|
|
|
|
payload: form,
|
|
|
|
|
token: user_token,
|
|
|
|
|
headers: form.getHeaders(),
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
logger.error('Failed to end editing session: ', error.stack);
|
|
|
|
|
throw error;
|
|
|
|
|
//TODO make monitoring for such kind of errors
|
|
|
|
|
if (error.response?.status === 404) {
|
|
|
|
|
logger.error('Forgotten OO document that Twake Drive doesnt know the key of.', { editing_session_key, url });
|
|
|
|
|
throw new UnknownKeyInDriveError(`Unknown key ${JSON.stringify(editing_session_key)}`);
|
|
|
|
|
//TODO: Distinguish this case from a long disconnected browser tab waking up
|
|
|
|
|
//TODO make monitoring for such kind of errors
|
|
|
|
|
} else {
|
|
|
|
|
logger.error('Failed to end editing session: ', error.stack);
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get the document information by the editing session key. Just simple call to the drive API
|
|
|
|
|
* /item/editing_session/${editing_session_key}
|
|
|
|
|
* @param params
|
|
|
|
|
* @returns null if the key was not found, or the api response body
|
|
|
|
|
*/
|
|
|
|
|
public getByEditingSessionKey = async (params: { editing_session_key: string; user_token?: string }): Promise<DriveFileType['item']> => {
|
|
|
|
|
try {
|
|
|
|
|
const { editing_session_key } = params;
|
|
|
|
|
return await apiService.get<DriveFileType['item']>({
|
|
|
|
|
url: `/internal/services/documents/v1/editing_session/${encodeURIComponent(editing_session_key)}`,
|
|
|
|
|
token: params.user_token,
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
if (error?.response?.status === 404) return null;
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default new DriveService();
|
|
|
|
|