🖥️ Onlyoffice connector moved into the TwakeDrive repository (#366)

This commit is contained in:
Montassar Ghanmy
2024-02-27 10:35:01 +01:00
committed by GitHub
parent ff74104a1b
commit dcb6fa1c36
40 changed files with 1557 additions and 3 deletions
@@ -0,0 +1,152 @@
import {
IApiServiceRequestParams,
IApiService,
IApiServiceApplicationTokenRequestParams,
IApiServiceApplicationTokenResponse,
} from '@/interfaces/api.interface';
import axios, { Axios, AxiosRequestConfig, AxiosResponse } from 'axios';
import { CREDENTIALS_ENDPOINT, CREDENTIALS_ID, CREDENTIALS_SECRET, ONLY_OFFICE_SERVER } from '@config';
import loggerService from './logger.service';
class ApiService implements IApiService {
private axios: Axios;
private initialized: Promise<string>;
constructor() {
this.initialized = this.refreshToken();
this.initialized.catch(error => {
loggerService.error('failed to init API', error);
});
setInterval(() => {
this.initialized = this.refreshToken();
loggerService.info('Refreshing token 🪙');
}, 1000 * 60); //Every 10 minutes
}
public get = async <T>(params: IApiServiceRequestParams<T>): Promise<T> => {
const { url, token, responseType, headers } = params;
await this.initialized;
const config: AxiosRequestConfig = {};
if (token) {
config['headers'] = {
Authorization: `Bearer ${token}`,
...headers,
};
}
if (responseType) {
config['responseType'] = responseType;
}
return await this.axios.get(url, config);
};
public post = async <T, R>(params: IApiServiceRequestParams<T>): Promise<R> => {
const { url, payload, headers } = params;
await this.initialized;
try {
return await this.axios.post(url, payload, {
headers: {
...headers,
},
});
} catch (error) {
loggerService.error('Failed to post: ', error.message);
this.refreshToken();
}
};
private handleErrors = (error: any): Promise<any> => {
loggerService.error('Failed Request', error.message);
return Promise.reject(error);
};
private handleResponse = <T>({ data }: AxiosResponse): T => data;
private refreshToken = async (): Promise<string> => {
try {
const response = await axios.post<IApiServiceApplicationTokenRequestParams, { data: IApiServiceApplicationTokenResponse }>(
`${CREDENTIALS_ENDPOINT.replace(/\/$/, '')}/api/console/v1/login`,
{
id: CREDENTIALS_ID,
secret: CREDENTIALS_SECRET,
},
{
headers: {
Authorization: `Basic ${Buffer.from(`${CREDENTIALS_ID}:${CREDENTIALS_SECRET}`).toString('base64')}`,
},
},
);
const {
resource: {
access_token: { value },
},
} = response.data;
this.axios = axios.create({
baseURL: CREDENTIALS_ENDPOINT,
headers: {
Authorization: `Bearer ${value}`,
},
});
this.axios.interceptors.response.use(this.handleResponse, this.handleErrors);
return value;
} catch (error) {
loggerService.error('failed to get application token', error.message);
loggerService.info('Using token ', CREDENTIALS_ID, CREDENTIALS_SECRET);
loggerService.info(`POST ${CREDENTIALS_ENDPOINT.replace(/\/$/, '')}/api/console/v1/login`);
loggerService.info(`Basic ${Buffer.from(`${CREDENTIALS_ID}:${CREDENTIALS_SECRET}`).toString('base64')}`);
throw Error(error);
}
};
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,49 @@
import { DriveFileType, IDriveService } from '@/interfaces/drive.interface';
import apiService from './api.service';
import loggerService from './logger.service';
class DriveService implements IDriveService {
public get = async (params: { company_id: string; drive_file_id: string; user_token?: string }): Promise<DriveFileType> => {
try {
const { company_id, drive_file_id } = params;
const resource = await apiService.get<DriveFileType>({
url: `/internal/services/documents/v1/companies/${company_id}/item/${drive_file_id}`,
token: params.user_token,
});
return resource;
} catch (error) {
loggerService.error('Failed to fetch file metadata: ', error.message);
return Promise.reject();
}
};
public createVersion = async (params: {
company_id: string;
drive_file_id: string;
file_id: string;
}): Promise<DriveFileType['item']['last_version_cache']> => {
try {
const { company_id, drive_file_id, file_id } = params;
const resource = await apiService.post<{}, DriveFileType['item']['last_version_cache']>({
url: `/internal/services/documents/v1/companies/${company_id}/item/${drive_file_id}/version`,
payload: {
drive_item_id: drive_file_id,
provider: 'internal',
file_metadata: {
external_id: file_id,
source: 'internal',
},
},
});
return resource;
} catch (error) {
loggerService.error('Failed to create version: ', error.message);
return Promise.reject();
}
};
}
export default new DriveService();
@@ -0,0 +1,91 @@
import { EditConfigInitResult, IEditorService, ModeParametersType } from '@/interfaces/editor.interface';
import { UserType } from '@/interfaces/user.interface';
import { ONLY_OFFICE_SERVER } from '@config';
class EditorService implements IEditorService {
public init = async (
company_id: string,
file_name: string,
file_version_id: string,
user: UserType,
preview: boolean,
file_id: string,
): Promise<EditConfigInitResult> => {
const { color, mode: fileMode } = this.getFileMode(file_name);
return {
color,
file_id,
file_version_id,
file_type: file_name.split('.').pop(),
filename: file_name,
language: user.preferences.locale || 'en',
mode: fileMode,
onlyoffice_server: ONLY_OFFICE_SERVER,
user_id: user.id,
user_image: user.thumbnail || user.picture || '',
username: user.username,
company_id,
preview,
editable: !preview,
};
};
private getFileMode = (filename: string): ModeParametersType => {
const extension = filename.split('.').pop();
if (
[
'doc',
'docm',
'docx',
'docxf',
'dot',
'dotm',
'dotx',
'epub',
'fodt',
'fb2',
'htm',
'html',
'mht',
'odt',
'oform',
'ott',
'oxps',
'pdf',
'rtf',
'txt',
'djvu',
'xml',
'xps',
].includes(extension)
) {
return {
mode: 'word',
color: '#aa5252',
};
}
if (['csv', 'fods', 'ods', 'ots', 'xls', 'xlsb', 'xlsm', 'xlsx', 'xlt', 'xltm', 'xltx'].includes(extension)) {
return {
mode: 'cell',
color: '#40865c',
};
}
if (['fodp', 'odp', 'otp', 'pot', 'potm', 'potx', 'pps', 'ppsm', 'ppsx', 'ppt', 'pptm', 'pptx'].includes(extension)) {
return {
mode: 'slide',
color: '#aa5252',
};
}
return {
mode: 'text',
color: 'grey',
};
};
}
export default new EditorService();
@@ -0,0 +1,82 @@
import { FileRequestParams, FileType, IFileService } from '@/interfaces/file.interface';
import apiService from './api.service';
import loggerService from './logger.service';
import { Stream } from 'stream';
import FormData from 'form-data';
class FileService implements IFileService {
public get = async (params: FileRequestParams): Promise<FileType> => {
try {
const { company_id, file_id } = params;
const { resource } = await apiService.get<{ resource: FileType }>({
url: `/internal/services/files/v1/companies/${company_id}/files/${file_id}`,
});
return resource;
} catch (error) {
loggerService.error('Failed to fetch file metadata: ', error.message);
return Promise.reject();
}
};
public download = async (params: FileRequestParams): Promise<any> => {
try {
const { company_id, file_id } = params;
const file = await apiService.get({
url: `/internal/services/files/v1/companies/${company_id}/files/${file_id}/download`,
responseType: 'stream',
});
return file;
} catch (error) {
loggerService.error('Failed to download file: ', error.message);
}
};
public save = async (params: FileRequestParams): Promise<{ resource: FileType }> => {
try {
const { company_id, file_id, url, create_new } = params;
if (!url) {
throw Error('no url found');
}
const originalFile = await this.get(params);
if (!originalFile) {
throw Error('original file not found');
}
const newFile = await apiService.get<Stream>({
url,
responseType: 'stream',
});
const form = new FormData();
const nameSplit = (originalFile.metadata.name || '').split('.');
const filename =
nameSplit[0].replace(/-[0-9]{8}-[0-9]{4}$/, '') +
(!create_new ? '.' : `-${new Date().toISOString().split('.')[0].split(':').slice(0, 2).join('').replace(/-/gm, '').split('T').join('-')}.`) +
nameSplit.slice(1).join('.');
form.append('file', newFile, {
filename,
});
loggerService.info('Saving file version: ', filename);
return await apiService.post<any, { resource: FileType }>({
url: create_new
? `/internal/services/files/v1/companies/${company_id}/files?thumbnail_sync=1`
: `/internal/services/files/v1/companies/${company_id}/files/${file_id}?thumbnail_sync=1`,
payload: form,
headers: form.getHeaders(),
});
} catch (error) {
loggerService.error('Failed to save file: ', error.message);
}
};
}
export default new FileService();
@@ -0,0 +1,5 @@
import { Logger } from 'tslog';
export default new Logger({
name: 'twake-onlyoffice-plugin',
});
@@ -0,0 +1,22 @@
import { IuserService, UserType } from '@/interfaces/user.interface';
import apiService from './api.service';
import loggerService from './logger.service';
class UserService implements IuserService {
public getCurrentUser = async (token: string): Promise<UserType> => {
try {
const { resource } = await apiService.get<{ resource: UserType }>({
url: '/internal/services/users/v1/users/me',
token,
});
return resource;
} catch (error) {
loggerService.error('Failed to fetch the current user', error.message);
return null;
}
};
}
export default new UserService();