♻️ OOConnector: refactor OnlyOffice logic to its own file, implement API interface (#515)

This commit is contained in:
Eric Doughty-Papassideris
2024-06-14 13:55:34 +02:00
committed by ericlinagora
parent ca267df7ac
commit 3a6e683acf
4 changed files with 155 additions and 41 deletions
@@ -4,6 +4,7 @@ import apiService from '@/services/api.service';
import driveService from '@/services/drive.service';
import fileService from '@/services/file.service';
import loggerService from '@/services/logger.service';
import onlyofficeService, * as OnlyOffice from '@/services/onlyoffice.service';
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
@@ -129,7 +130,9 @@ class OnlyOfficeController {
});
} else {
loggerService.error('URL not present, force saving file');
await apiService.runCommand('forcesave', key);
await onlyofficeService.forceSave(key).catch(error => {
loggerService.warn("Expected error while force saving without changes (ignored):", error);
});
}
res.send({
@@ -11,7 +11,6 @@ export interface IApiServiceRequestParams<T> {
export interface IApiService {
get<T>(params: IApiServiceRequestParams<T>): Promise<T>;
post<T>(params: IApiServiceRequestParams<T>): Promise<T>;
runCommand<T>(c: string, key: string): Promise<void>;
}
export interface IApiServiceApplicationTokenRequestParams {
@@ -111,45 +111,6 @@ class ApiService implements IApiService {
}
};
public runCommand = async (c: string, key: string): Promise<void> => {
try {
loggerService.info('SENDING COMMAND TO: ', `${ONLY_OFFICE_SERVER}coauthoring/CommandService.ashx`);
const response = await axios.post(`${ONLY_OFFICE_SERVER}coauthoring/CommandService.ashx`, {
c,
key,
userdata: '',
});
const { data } = response;
switch (data.error) {
case 0:
loggerService.info('File saved successfully');
break;
case 1:
loggerService.error('Document key is missing or no document with such key could be found.');
throw new Error('Document key is missing or no document with such key could be found.');
case 2:
loggerService.error('Callback url not correct.');
throw new Error('Callback url not correct.');
case 3:
loggerService.error('Internal server error.');
throw new Error('Internal server error.');
case 4:
loggerService.error('No changes were applied to the document before the forcesave command was received.');
throw new Error('No changes were applied to the document before the forcesave command was received.');
case 5:
loggerService.error('Command not correct.');
throw new Error('Command not correct.');
case 6:
loggerService.error('Invalid token.');
throw new Error('Invalid token.');
default:
loggerService.error('Unknown error occurred.');
throw new Error('Unknown error occurred.');
}
} catch (error) {
loggerService.error(`Error executing command: ${c}, ${error}`);
}
};
}
export default new ApiService();
@@ -0,0 +1,151 @@
import axios, { Axios, AxiosRequestConfig, AxiosResponse } from 'axios';
import { ONLY_OFFICE_SERVER } from '@config';
import loggerService from './logger.service';
import * as Utils from '@/utils';
/** @see https://api.onlyoffice.com/editors/basic */
export enum ErrorCode {
SUCCESS = 0,
KEY_MISSING_OR_DOC_NOT_FOUND = 1,
INVALID_CALLBACK_URL = 2,
INTERNAL_SERVER_ERROR = 3,
FORCE_SAVE_BUT_NO_CHANGES_TO_APPLY = 4,
COMMAND_NOT_CORRECT = 5,
INVALID_TOKEN = 6,
}
/** Return the name of the error code in the `ErrorCode` enum if recognised, or a descript string */
export const ErrorCodeFromValue = (value: number) => Utils.getKeyForValueSafe(value, ErrorCode, "OnlyOffice.ErrorCode");
/** @see https://api.onlyoffice.com/editors/callback */
export namespace Callback {
enum ActionType {
USER_DISCONNECTED = 0,
USER_CONNECTED = 1,
USER_INITIATED_FORCE_SAVE = 2,
};
enum ForceSaveType {
FROM_COMMAND_SERVICE = 0,
FORCE_SAVE_BUTTON_CLICKED = 1,
SERVER_TIMER = 2,
FORM_SUBMITTED = 3,
}
enum Status {
BEING_EDITED = 1,
/** `url` field present with this status */
READY_FOR_SAVING = 2,
/** `url` field present with this status */
ERROR_SAVING = 3,
CLOSED_WITHOUT_CHANGES = 4,
/** `url` and `forcesavetype` fields present with this status */
BEING_EDITED_BUT_IS_SAVED = 6,
/** `url` and `forcesavetype` fields present with this status */
ERROR_FORCE_SAVING = 7,
}
interface Action {
type: ActionType;
userid: string;
}
/** Parameters given to the callback by the editing service */
interface Parameters {
key: string;
status: Status;
filetype?: string;
forcesavetype?: ForceSaveType;
url?: string;
actions?: Action[];
users?: string[];
}
}
/**
* Helpers to define the protocol of the OnlyOffice editor service command API
* @see https://api.onlyoffice.com/editors/command/
*/
namespace CommandService {
interface BaseResponse { error: ErrorCode; }
interface SuccessResponse extends BaseResponse { error: ErrorCode.SUCCESS; }
interface ErrorResponse extends BaseResponse { error: Exclude<ErrorCode, ErrorCode.SUCCESS>; }
export class CommandError extends Error {
constructor(errorCode: ErrorCode, req: any, res: any) {
super(`OnlyOffice command service error ${ErrorCodeFromValue(errorCode)} (${errorCode}): Requested ${JSON.stringify(req)} got ${JSON.stringify(res)}`);
}
}
abstract class BaseRequest<TSuccessResponse extends BaseResponse> {
constructor(public c: string) {}
/** POST this OnlyOffice command, does not check the `error` field of the response */
async postUnsafe(): Promise<ErrorResponse | TSuccessResponse> {
loggerService.silly(`OnlyOffice command ${this.c} sent: ${JSON.stringify(this)}`);
const result = (await axios.post(`${ONLY_OFFICE_SERVER}coauthoring/CommandService.ashx`, this));
loggerService.info(`OnlyOffice command ${this.c} response: ${result.status}: ${JSON.stringify(result.data)}`);
return result.data as ErrorResponse | TSuccessResponse;
}
/** POST this request, and return the result, or throw if the `errorCode` returned isn't 0 */
async post(): Promise<TSuccessResponse> {
const result = await this.postUnsafe();
if (result.error === ErrorCode.SUCCESS)
return result;
throw new CommandError(result.error, this, result);
}
}
export namespace Version {
interface Response extends SuccessResponse { version: string; }
export class Request extends BaseRequest<Response> { constructor() { super("version"); } }
}
export namespace ForceSave {
interface Response extends SuccessResponse { key: string; }
export class Request extends BaseRequest<Response> { constructor(public key: string, public userdata: string = "") { super("forcesave"); } }
}
export namespace GetForgotten {
interface Response extends SuccessResponse { key: string; url: string; }
export class Request extends BaseRequest<Response> { constructor(public key: string) { super("getForgotten"); } }
}
export namespace GetForgottenList {
interface Response extends SuccessResponse { keys: string[]; }
export class Request extends BaseRequest<Response> { constructor() { super("getForgottenList"); } }
}
export namespace DeleteForgotten {
interface Response extends SuccessResponse { key: string; }
export class Request extends BaseRequest<Response> { constructor(public key: string) { super("deleteForgotten"); } }
}
}
/**
* Exposed OnlyOffice command service
* @see https://api.onlyoffice.com/editors/command/
*/
class OnlyOfficeService {
/** Return the version string of OnlyOffice */
async getVersion(): Promise<string> {
return new CommandService.Version.Request().post().then(response => response.version);
}
/** Force a save in the editing session key provided. `userdata` will be forwarded to the callback */
async forceSave(key: string, userdata: string = ""): Promise<string> {
return new CommandService.ForceSave.Request(key, userdata).post().then(response => response.key);
}
/** Return the keys of all forgotten documents in OnlyOffice's document editing service */
async getForgottenList(): Promise<string[]> {
return new CommandService.GetForgottenList.Request().post().then(response => response.keys);
}
/** Return the url of a forgotten document specified by its key in OnlyOffice's document editing service */
async getForgotten(key: string): Promise<string> {
return new CommandService.GetForgotten.Request(key).post().then(response => response.url);
}
/** Delete a forgotten document specified by its key in OnlyOffice's document editing service */
async deleteForgotten(key: string): Promise<string> {
return new CommandService.DeleteForgotten.Request(key).post().then(response => response.key);
}
}
export default new OnlyOfficeService();