128 lines
4.8 KiB
TypeScript
128 lines
4.8 KiB
TypeScript
import { CREDENTIALS_SECRET } from '@/config';
|
|
import { OfficeToken } from '@/interfaces/office-token.interface';
|
|
import driveService from '@/services/drive.service';
|
|
import fileService from '@/services/file.service';
|
|
import logger from '@/lib/logger';
|
|
import * as OnlyOffice from '@/services/onlyoffice.service';
|
|
import { Request, Response, NextFunction } from 'express';
|
|
import jwt from 'jsonwebtoken';
|
|
import * as Utils from '@/utils';
|
|
|
|
interface RequestQuery {
|
|
company_id: string;
|
|
file_id: string;
|
|
token: string;
|
|
}
|
|
|
|
/** These expose a OnlyOffice document storage service methods, called by the OnlyOffice document editing service
|
|
* to load and save files
|
|
*/
|
|
class OnlyOfficeController {
|
|
/**
|
|
* Get a file from Twake Drive backend, and proxy it back the previewer/editor (via the document editing service).
|
|
*
|
|
* Parameters are standard Express middleware.
|
|
* @see https://api.onlyoffice.com/editors/open
|
|
*/
|
|
public read = async (req: Request<{}, {}, {}, RequestQuery>, res: Response, next: NextFunction): Promise<void> => {
|
|
try {
|
|
const { token } = req.query;
|
|
|
|
const officeTokenPayload = jwt.verify(token, CREDENTIALS_SECRET) as OfficeToken;
|
|
const { company_id, file_id, in_page_token } = officeTokenPayload;
|
|
|
|
// check token is an in_page_token
|
|
if (!in_page_token) throw new Error('Invalid token, must be a in_page_token');
|
|
|
|
if (!file_id) throw new Error(`File id is missing in the last version cache for ${JSON.stringify(file_id)}`);
|
|
const file = await fileService.download({
|
|
company_id,
|
|
file_id: file_id,
|
|
});
|
|
|
|
file.pipe(res);
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Receive a file from OnlyOffice document editing service and save it into Twake Drive backend.
|
|
*
|
|
* This is the endpoint of the callback url provided to the editor in the browser.
|
|
*
|
|
* Parameters are standard Express middleware.
|
|
* @see https://api.onlyoffice.com/editors/save
|
|
* @see https://api.onlyoffice.com/editors/callback
|
|
*/
|
|
public ooCallback = async (
|
|
req: Request<{}, {}, OnlyOffice.Callback.Parameters, RequestQuery>,
|
|
res: Response,
|
|
next: NextFunction,
|
|
): Promise<void> => {
|
|
const respondToOO = (error = 0) => void res.send({ error });
|
|
try {
|
|
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)}`,
|
|
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;
|
|
|
|
// 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);
|
|
|
|
// 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');
|
|
|
|
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:
|
|
// No-op
|
|
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);
|
|
|
|
case OnlyOffice.Callback.Status.CLOSED_WITHOUT_CHANGES:
|
|
// Save end of transaction
|
|
break;
|
|
|
|
case OnlyOffice.Callback.Status.ERROR_SAVING:
|
|
// Save end of transaction
|
|
logger.info(`Error saving file ${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 });
|
|
|
|
default:
|
|
throw new Error(`Unexpected OO Callback status field: ${req.body.status}`);
|
|
}
|
|
return respondToOO();
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
};
|
|
}
|
|
|
|
export default OnlyOfficeController;
|