diff --git a/tdrive/connectors/onlyoffice-connector/src/controllers/backend-callbacks.controller.ts b/tdrive/connectors/onlyoffice-connector/src/controllers/backend-callbacks.controller.ts index 67083d31..6a31afef 100644 --- a/tdrive/connectors/onlyoffice-connector/src/controllers/backend-callbacks.controller.ts +++ b/tdrive/connectors/onlyoffice-connector/src/controllers/backend-callbacks.controller.ts @@ -7,18 +7,6 @@ interface RequestQuery { editing_session_key: string; } -async function ignoreMissingKeyErrorButNoneElse(res: Response, call: () => Promise): Promise { - try { - await call(); - } catch (e) { - if (e instanceof CommandError && e.errorCode == ErrorCode.KEY_MISSING_OR_DOC_NOT_FOUND) { - return void (await res.send({ info: 'Unknown editing_session_key' })); - } - logger.error('Running OO command for TDrive backend', e); - return void (await res.sendStatus(500)); - } -} - /** * These routes are called by Twake Drive backend, for ex. before editing or retreiving a file, * if it has an editing_session_key still, get the status of that and force a resolution. @@ -30,34 +18,42 @@ export default class TwakeDriveBackendCallbackController { * be considered lost after an admin alert. * * @returns + * - `{ status: 'unknown' }`: the key isn't known and maybe used for a new session * - `{ status: 'updated' }`: the key needed updating but is now invalid - * - `{ status: 'expired' }`: the key can't be used (it is verified unknown) + * - `{ status: 'expired' }`: the key was already used in a finished session and can't be used again * - `{ status: 'live' }`: the key is valid and current and should be used again for the same file * - `{ error: number }`: there was an error retreiving the status of the key, http status `!= 200` */ public async checkSessionStatus(req: Request, res: Response): Promise { - //TODO: check there is auth from backend before this is ran - - // have to get forgotten first, if it's there it's definitive, - // but if we paralelise it risks calling the callback try { const forgottenURL = await onlyofficeService.getForgotten(req.params.editing_session_key); - // Run upload before returning + try { + await driveService.endEditing(req.params.editing_session_key, forgottenURL); + } catch (error) { + logger.error(`endEditing failed`, { error }); + return void res.status(502).send({ error: -57649 }); + } + try { + await onlyofficeService.deleteForgotten(req.params.editing_session_key); + } catch (error) { + logger.error(`deleteForgotten failed`, { error }); + return void res.status(502).send({ error: -57650 }); + } return void res.send({ status: 'updated' }); } catch (e) { if (!(e instanceof CommandError && e.errorCode == ErrorCode.KEY_MISSING_OR_DOC_NOT_FOUND)) { - logger.error(`getForgotten failed`, e); - return void res.status(e instanceof CommandError ? 502 : 500).send({ error: -51 }); + logger.error(`getForgotten failed`, { error: e }); + return void res.status(e instanceof CommandError ? 502 : 500).send({ error: -57651 }); } } const info = await onlyofficeService.getInfoAndWaitForCallbackUnsafe(req.params.editing_session_key); if (info.error === ErrorCode.KEY_MISSING_OR_DOC_NOT_FOUND) { - // just cancel it - return void res.send({ status: 'expired' }); + // just start using it + return void res.send({ status: 'unknown' }); } if (info.error !== undefined) { logger.error(`getInfo failed`, { error: info }); - return void res.status(502).send({ error: -52 }); + return void res.status(502).send({ error: -57652 }); } switch (info.result.status) { case Callback.Status.BEING_EDITED: @@ -75,7 +71,6 @@ export default class TwakeDriveBackendCallbackController { case Callback.Status.READY_FOR_SAVING: // upload it, have to do it here for correct user stored in url in OO - //TODO: Need to fix so company_id is not needed by ooconnector but parsed from key server side await driveService.endEditing(req.params.editing_session_key, info.result.url); return void res.send({ status: 'updated' }); diff --git a/tdrive/connectors/onlyoffice-connector/src/controllers/onlyoffice.controller.ts b/tdrive/connectors/onlyoffice-connector/src/controllers/onlyoffice.controller.ts index 4a74366f..112e25be 100644 --- a/tdrive/connectors/onlyoffice-connector/src/controllers/onlyoffice.controller.ts +++ b/tdrive/connectors/onlyoffice-connector/src/controllers/onlyoffice.controller.ts @@ -65,61 +65,70 @@ class OnlyOfficeController { const { url, key } = req.body; const { token } = req.query; logger.info( - `OO callback status: ${Utils.getKeyForValueSafe( - req.body.status, - OnlyOffice.Callback.Status, - 'OnlyOffice.Callback.Status', - )} - ${JSON.stringify(req.body.userdata)}`, + `OO callback status: ${Utils.getKeyForValueSafe(req.body.status, OnlyOffice.Callback.Status, 'Callback.Status')} - ${JSON.stringify( + req.body.userdata, + )}`, req.body, ); const officeTokenPayload = jwt.verify(token, CREDENTIALS_SECRET) as OfficeToken; - const { preview, company_id, file_id, /* user_id, */ drive_file_id, in_page_token, editing_session_key } = officeTokenPayload; + const { preview, /* company_id, file_id, user_id, drive_file_id, */ in_page_token /* editing_session_key */ } = officeTokenPayload; // Ignore errors generated by pending request // try-catch not needed because it is async // there may be later reasons to wait for callbacks // to process and eventually respond accordingly to // OO an error for certain statuses - void OnlyOffice.default.ooCallbackCalled(req.body); + void OnlyOffice.default.ooCallbackCalled(req.body); // has to be single thread per key // check token is an in_page_token and allow save - if (!in_page_token) throw new Error('Invalid token, must be a in_page_token'); - if (preview) throw new Error('Invalid token, must not be a preview token for save operation'); + if (!in_page_token) throw new Error('OO Callback invalid token, must be a in_page_token'); + if (preview) throw new Error('OO Callback invalid token, must not be a preview token for save operation'); switch (req.body.status) { case OnlyOffice.Callback.Status.BEING_EDITED: - // TODO this call back we recieve almost all the time, and here we save - // the user identifiers who start file editing and even control the amount of onlin users - // to have license constraint warning before OnlyOffice error about this - case OnlyOffice.Callback.Status.BEING_EDITED_BUT_IS_SAVED: + // TODO this call back we recieve almost all the time, and here we save + // the user identifiers who start file editing and even control the amount of onlin users + // to have license constraint warning before OnlyOffice error about this // No-op break; + case OnlyOffice.Callback.Status.BEING_EDITED_BUT_IS_SAVED: + logger.info(`OO Callback force save for session ${key} for reason: ${OnlyOffice.Callback.ForceSaveTypeToString(req.body.forcesavetype)}`); + await driveService.addEditingSessionVersion(key, url); //, token); //TODO Fix user token (getting 401) + break; + case OnlyOffice.Callback.Status.READY_FOR_SAVING: - logger.info(`New version for session ${editing_session_key} created`); - return respondToOO(); - await driveService.endEditing(editing_session_key, url); + logger.info(`OO Callback new version for session ${key} created`); + await driveService.endEditing(key, url); //, token); //TODO Fix user token (getting 401) + break; case OnlyOffice.Callback.Status.CLOSED_WITHOUT_CHANGES: - // Save end of transaction + await driveService.cancelEditing(key); break; case OnlyOffice.Callback.Status.ERROR_SAVING: - // Save end of transaction - logger.info(`Error saving file ${req.body.url} (key: ${req.body.key})`); + // TODO: Save end of transaction ? + logger.error(`OO Callback with Status.ERROR_SAVING: ${req.body.url} (key: ${req.body.key})`); break; case OnlyOffice.Callback.Status.ERROR_FORCE_SAVING: // TODO: notify user ? - logger.info(`Error force saving (reason: ${req.body.forcesavetype}) file ${req.body.url} (key: ${req.body.key})`); - return void res.send({ error: 0 }); + logger.error( + `OO Callback with Status.ERROR_FORCE_SAVING (reason: ${OnlyOffice.Callback.ForceSaveTypeToString(req.body.forcesavetype)}) file ${ + req.body.url + } (key: ${req.body.key})`, + ); + break; default: - throw new Error(`Unexpected OO Callback status field: ${req.body.status}`); + throw new Error( + `OO Callback unexpected status field: ${OnlyOffice.Callback.StatusToString(req.body.status)} in ${JSON.stringify(req.body)}`, + ); } - return respondToOO(); + return respondToOO(0); } catch (error) { - next(error); + logger.error(`OO Callback root error`, { error }); + next(error || 'error'); } }; } diff --git a/tdrive/connectors/onlyoffice-connector/src/services/onlyoffice.service.ts b/tdrive/connectors/onlyoffice-connector/src/services/onlyoffice.service.ts index 9b67f2fd..7fd81c28 100644 --- a/tdrive/connectors/onlyoffice-connector/src/services/onlyoffice.service.ts +++ b/tdrive/connectors/onlyoffice-connector/src/services/onlyoffice.service.ts @@ -32,6 +32,7 @@ export namespace Callback { USER_CONNECTED = 1, USER_INITIATED_FORCE_SAVE = 2, } + export const ActionTypeToString = (value: number) => Utils.getKeyForValueSafe(value, ActionType, 'OnlyOffice.Callback.ActionType'); interface Action { type: ActionType; @@ -44,6 +45,7 @@ export namespace Callback { SERVER_TIMER = 2, FORM_SUBMITTED = 3, } + export const ForceSaveTypeToString = (value: number) => Utils.getKeyForValueSafe(value, ForceSaveType, 'OnlyOffice.Callback.ForceSaveType'); export enum Status { BEING_EDITED = 1, @@ -57,6 +59,7 @@ export namespace Callback { /** `url` and `forcesavetype` fields present with this status */ ERROR_FORCE_SAVING = 7, } + export const StatusToString = (value: number) => Utils.getKeyForValueSafe(value, Status, 'OnlyOffice.Callback.Status'); /** Parameters given to the callback by the editing service */ export interface Parameters { @@ -305,6 +308,7 @@ class OnlyOfficeService { return await new CommandService.Info.Request(key, userdata).postUnsafe().then(({ error }) => error); } + /** Send the info command, wait for the callback if warranted, and return the error or callback body */ async getInfoAndWaitForCallbackUnsafe(key: string): Promise { // const userdata = randomUUID(); return new Promise((resolve, reject) => {