🖥️ Onlyoffice connector moved into the TwakeDrive repository (#366)
This commit is contained in:
@@ -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();
|
||||
Reference in New Issue
Block a user