From b8814c0968687d65a8530de4770d333109d95061 Mon Sep 17 00:00:00 2001 From: Eric Doughty-Papassideris Date: Mon, 16 Sep 2024 17:32:34 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=A9=B9=20backend,oo:=20small=20fixes,=20r?= =?UTF-8?q?elated=20to=20instance=20id=20(#525)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/services/documents/services/index.ts | 3 +- .../src/services/documents/web/schemas.ts | 1 + .../onlyoffice-connector/src/config/index.ts | 1 + .../backend-callbacks.controller.ts | 61 +++++++++++++++++-- .../src/lib/pending-request-matcher.ts | 2 +- .../src/services/drive.service.ts | 4 +- 6 files changed, 63 insertions(+), 9 deletions(-) diff --git a/tdrive/backend/node/src/services/documents/services/index.ts b/tdrive/backend/node/src/services/documents/services/index.ts index 89027fc2..28179376 100644 --- a/tdrive/backend/node/src/services/documents/services/index.ts +++ b/tdrive/backend/node/src/services/documents/services/index.ts @@ -960,7 +960,8 @@ export class DocumentsService { * @param id DriveFile ID of the document to begin editing * @param editorApplicationId Editor/Application/Plugin specific identifier * @param appInstanceId For that `editorApplicationId` a unique identifier - * when multiple instances are running. Unused today. + * when multiple instances are running. Unused today - would need a mapping + * from `appInstanceId` to server host. * @param context * @returns An object in the format `{}` with the unique identifier for the * editing session diff --git a/tdrive/backend/node/src/services/documents/web/schemas.ts b/tdrive/backend/node/src/services/documents/web/schemas.ts index 81a095d2..65384de7 100644 --- a/tdrive/backend/node/src/services/documents/web/schemas.ts +++ b/tdrive/backend/node/src/services/documents/web/schemas.ts @@ -115,6 +115,7 @@ export const beginEditingSchema = { type: "object", properties: { editorApplicationId: { type: "string" }, + appInstanceId: { type: "string" }, }, required: ["editorApplicationId"], }, diff --git a/tdrive/connectors/onlyoffice-connector/src/config/index.ts b/tdrive/connectors/onlyoffice-connector/src/config/index.ts index a312ed13..5fdc7997 100644 --- a/tdrive/connectors/onlyoffice-connector/src/config/index.ts +++ b/tdrive/connectors/onlyoffice-connector/src/config/index.ts @@ -12,6 +12,7 @@ export const { CREDENTIALS_SECRET, SERVER_PREFIX, SERVER_ORIGIN, + INSTANCE_ID, } = process.env; const secs = 1000, 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 45a8801a..7f2d584f 100644 --- a/tdrive/connectors/onlyoffice-connector/src/controllers/backend-callbacks.controller.ts +++ b/tdrive/connectors/onlyoffice-connector/src/controllers/backend-callbacks.controller.ts @@ -1,6 +1,7 @@ -import { NextFunction, Request, Response } from 'express'; +import { Request, Response } from 'express'; import logger from '@/lib/logger'; -import onlyofficeService, { CommandError, ErrorCode } from '@/services/onlyoffice.service'; +import onlyofficeService, { Callback, CommandError, ErrorCode } from '@/services/onlyoffice.service'; +import driveService from '@/services/drive.service'; interface RequestQuery { editing_session_key: string; @@ -27,12 +28,60 @@ export default class TwakeDriveBackendCallbackController { * Get status of an `editing_session_key` from OO, and return a URL to get the latest version, * or an object with no `url` property, in which case the key is not known as forgotten by OO and should * be considered lost after an admin alert. + * + * @returns + * - `{ status: 'updated' }`: the key needed updating but is now invalid + * - `{ status: 'expired' }`: the key can't be used (it is verified unknown) + * - `{ 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: Find a way to check if the key is live (`info` uses the callback url) - await ignoreMissingKeyErrorButNoneElse(res, async () => { - await res.send({ url: await onlyofficeService.getForgotten(req.params.editing_session_key) }); - }); + //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 + 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 }); + } + } + 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' }); + } + if (info.error !== undefined) { + logger.error(`getInfo failed`, { error: info }); + return void res.status(502).send({ error: -52 }); + } + switch (info.result.status) { + case Callback.Status.BEING_EDITED: + case Callback.Status.BEING_EDITED_BUT_IS_SAVED: + // use it as is + return void res.send({ status: 'live' }); + + case Callback.Status.CLOSED_WITHOUT_CHANGES: + // just cancel it + return void res.send({ status: 'expired' }); + + case Callback.Status.ERROR_FORCE_SAVING: + case Callback.Status.ERROR_SAVING: + return void res.status(502).send({ error: info.result.status }); + + 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' }); + + default: + throw new Error(`Unexpected callback status: ${JSON.stringify(info.result)}`); + } } /** diff --git a/tdrive/connectors/onlyoffice-connector/src/lib/pending-request-matcher.ts b/tdrive/connectors/onlyoffice-connector/src/lib/pending-request-matcher.ts index c739d47d..26cd6387 100644 --- a/tdrive/connectors/onlyoffice-connector/src/lib/pending-request-matcher.ts +++ b/tdrive/connectors/onlyoffice-connector/src/lib/pending-request-matcher.ts @@ -46,7 +46,7 @@ class PendingRequest { export class PendingRequestQueue { private queue: PendingRequest[] = []; - constructor(private readonly timeoutMs: number, niquystSamplingRatio: 4) { + constructor(private readonly timeoutMs: number, niquystSamplingRatio = 4) { setInterval(() => { this.flush(); }, timeoutMs / niquystSamplingRatio); diff --git a/tdrive/connectors/onlyoffice-connector/src/services/drive.service.ts b/tdrive/connectors/onlyoffice-connector/src/services/drive.service.ts index bf93d75a..7a91e8dd 100644 --- a/tdrive/connectors/onlyoffice-connector/src/services/drive.service.ts +++ b/tdrive/connectors/onlyoffice-connector/src/services/drive.service.ts @@ -3,6 +3,7 @@ import apiService from './api.service'; import logger from '../lib/logger'; import { Stream } from 'stream'; import FormData from 'form-data'; +import { INSTANCE_ID } from '@/config'; /** * Client for Twake Drive's APIs dealing with `DriveItem`s, using {@see apiService} @@ -55,6 +56,7 @@ class DriveService implements IDriveService { url: `/internal/services/documents/v1/companies/${company_id}/item/${drive_file_id}/editing_session`, payload: { editorApplicationId: 'tdrive_onlyoffice', + appInstanceId: INSTANCE_ID ?? '', }, }); if (resource?.editingSessionKey) { @@ -113,7 +115,7 @@ class DriveService implements IDriveService { headers: form.getHeaders(), }); } catch (error) { - logger.error('Failed to begin editing session: ', error.stack); + logger.error('Failed to end editing session: ', error.stack); throw error; //TODO make monitoring for such kind of errors }