♻️ OOConnector: refactor OnlyOffice logic to its own file, implement API interface (#515)
This commit is contained in:
committed by
ericlinagora
parent
ca267df7ac
commit
3a6e683acf
@@ -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();
|
||||
Reference in New Issue
Block a user