Chore: RestApiClient as Package (#25469)
<!-- This is a pull request template, you do not need to uncomment or remove the comments, they won't show up in the PR text. --> <!-- Your Pull Request name should start with one of the following tags [NEW] For new features [IMPROVE] For an improvement (performance or little improvements) in existing features [FIX] For bug fixes that affect the end-user [BREAK] For pull requests including breaking changes Chore: For small tasks Doc: For documentation --> <!-- Checklist!!! If you're unsure about any of them, don't hesitate to ask. We're here to help! This is simply a reminder of what we are going to look for before merging your code. - I have read the Contributing Guide - https://github.com/RocketChat/Rocket.Chat/blob/develop/.github/CONTRIBUTING.md#contributing-to-rocketchat doc - I have signed the CLA - https://cla-assistant.io/RocketChat/Rocket.Chat - Lint and unit tests pass locally with my changes - I have added tests that prove my fix is effective or that my feature works (if applicable) - I have added necessary documentation (if applicable) - Any dependent changes have been merged and published in downstream modules --> ## Proposed changes (including videos or screenshots) <!-- CHANGELOG --> <!-- Describe the big picture of your changes here to communicate to the maintainers why we should accept this pull request. If it fixes a bug or resolves a feature request, be sure to link to that issue below. This description will appear in the release notes if we accept the contribution. --> <!-- END CHANGELOG --> ## Issue(s) <!-- Link the issues being closed by or related to this PR. For example, you can use #594 if this PR closes issue number 594 --> ## Steps to test or reproduce <!-- Mention how you would reproduce the bug if not mentioned on the issue page already. Also mention which screens are going to have the changes if applicable --> ## Further comments <!-- If this is a relatively large or complex change, kick off the discussion by explaining why you chose the solution you did and what alternatives you considered, etc... --> Co-authored-by: Tasso Evangelista <2263066+tassoevan@users.noreply.github.com>pull/25774/head^2
parent
d5367a725b
commit
611c0b31fb
@ -1,164 +0,0 @@ |
||||
import { Tracker } from 'meteor/tracker'; |
||||
import { Session } from 'meteor/session'; |
||||
import { Random } from 'meteor/random'; |
||||
import { Meteor } from 'meteor/meteor'; |
||||
|
||||
import { settings } from '../../../settings/client'; |
||||
import { UserAction, USER_ACTIVITIES } from '../index'; |
||||
import { fileUploadIsValidContentType, APIClient } from '../../../utils'; |
||||
import { imperativeModal } from '../../../../client/lib/imperativeModal'; |
||||
import FileUploadModal from '../../../../client/views/room/modals/FileUploadModal'; |
||||
import { prependReplies } from '../../../../client/lib/utils/prependReplies'; |
||||
import { chatMessages } from '../views/app/room'; |
||||
|
||||
export const uploadFileWithMessage = async (rid, tmid, { description, fileName, msg, file }) => { |
||||
const data = new FormData(); |
||||
description && data.append('description', description); |
||||
msg && data.append('msg', msg); |
||||
tmid && data.append('tmid', tmid); |
||||
data.append('file', file.file, fileName); |
||||
|
||||
const uploads = Session.get('uploading') || []; |
||||
|
||||
const upload = { |
||||
id: Random.id(), |
||||
name: fileName, |
||||
percentage: 0, |
||||
}; |
||||
|
||||
uploads.push(upload); |
||||
Session.set('uploading', uploads); |
||||
|
||||
const { xhr, promise } = APIClient.upload(`v1/rooms.upload/${rid}`, {}, data, { |
||||
progress(progress) { |
||||
const uploads = Session.get('uploading') || []; |
||||
|
||||
if (progress === 100) { |
||||
return; |
||||
} |
||||
uploads |
||||
.filter((u) => u.id === upload.id) |
||||
.forEach((u) => { |
||||
u.percentage = Math.round(progress) || 0; |
||||
}); |
||||
Session.set('uploading', uploads); |
||||
}, |
||||
error(error) { |
||||
const uploads = Session.get('uploading') || []; |
||||
uploads |
||||
.filter((u) => u.id === upload.id) |
||||
.forEach((u) => { |
||||
u.error = error.message; |
||||
u.percentage = 0; |
||||
}); |
||||
Session.set('uploading', uploads); |
||||
}, |
||||
}); |
||||
if (Session.get('uploading').length) { |
||||
UserAction.performContinuously(rid, USER_ACTIVITIES.USER_UPLOADING, { tmid }); |
||||
} |
||||
|
||||
Tracker.autorun((computation) => { |
||||
const isCanceling = Session.get(`uploading-cancel-${upload.id}`); |
||||
if (!isCanceling) { |
||||
return; |
||||
} |
||||
computation.stop(); |
||||
Session.delete(`uploading-cancel-${upload.id}`); |
||||
|
||||
xhr.abort(); |
||||
|
||||
const uploads = Session.get('uploading') || {}; |
||||
Session.set( |
||||
'uploading', |
||||
uploads.filter((u) => u.id !== upload.id), |
||||
); |
||||
}); |
||||
|
||||
try { |
||||
await promise; |
||||
const uploads = Session.get('uploading') || []; |
||||
const remainingUploads = Session.set( |
||||
'uploading', |
||||
uploads.filter((u) => u.id !== upload.id), |
||||
); |
||||
|
||||
if (!Session.get('uploading').length) { |
||||
UserAction.stop(rid, USER_ACTIVITIES.USER_UPLOADING, { tmid }); |
||||
} |
||||
return remainingUploads; |
||||
} catch (error) { |
||||
const uploads = Session.get('uploading') || []; |
||||
uploads |
||||
.filter((u) => u.id === upload.id) |
||||
.forEach((u) => { |
||||
u.error = (error.xhr && error.xhr.responseJSON && error.xhr.responseJSON.error) || error.message; |
||||
u.percentage = 0; |
||||
}); |
||||
if (!uploads.length) { |
||||
UserAction.stop(rid, USER_ACTIVITIES.USER_UPLOADING, { tmid }); |
||||
} |
||||
Session.set('uploading', uploads); |
||||
} |
||||
}; |
||||
|
||||
export const fileUpload = async (files, input, { rid, tmid }) => { |
||||
const threadsEnabled = settings.get('Threads_enabled'); |
||||
|
||||
files = [].concat(files); |
||||
|
||||
const replies = $(input).data('reply') || []; |
||||
const mention = $(input).data('mention-user') || false; |
||||
|
||||
let msg = ''; |
||||
|
||||
if (!mention || !threadsEnabled) { |
||||
msg = await prependReplies('', replies, mention); |
||||
} |
||||
|
||||
if (mention && threadsEnabled && replies.length) { |
||||
tmid = replies[0]._id; |
||||
} |
||||
|
||||
const key = ['messagebox', rid, tmid].filter(Boolean).join('_'); |
||||
const messageBoxText = Meteor._localStorage.getItem(key) || ''; |
||||
|
||||
const uploadNextFile = () => { |
||||
const file = files.pop(); |
||||
if (!file) { |
||||
return; |
||||
} |
||||
|
||||
imperativeModal.open({ |
||||
component: FileUploadModal, |
||||
props: { |
||||
file: file.file, |
||||
fileName: file.name, |
||||
fileDescription: messageBoxText, |
||||
onClose: () => { |
||||
imperativeModal.close(); |
||||
uploadNextFile(); |
||||
}, |
||||
onSubmit: (fileName, description) => { |
||||
uploadFileWithMessage(rid, tmid, { |
||||
description, |
||||
fileName, |
||||
msg: msg || undefined, |
||||
file, |
||||
}); |
||||
const localStorageKey = ['messagebox', rid, tmid].filter(Boolean).join('_'); |
||||
const chatMessageKey = [rid, tmid].filter(Boolean).join('-'); |
||||
const { input } = chatMessages[chatMessageKey]; |
||||
input.value = null; |
||||
$(input).trigger('input'); |
||||
Meteor._localStorage.removeItem(localStorageKey); |
||||
imperativeModal.close(); |
||||
uploadNextFile(); |
||||
}, |
||||
invalidContentType: file.file.type && !fileUploadIsValidContentType(file.file.type), |
||||
}, |
||||
}); |
||||
}; |
||||
|
||||
uploadNextFile(); |
||||
}; |
||||
@ -0,0 +1,236 @@ |
||||
import { Tracker } from 'meteor/tracker'; |
||||
import { Session } from 'meteor/session'; |
||||
import { Random } from 'meteor/random'; |
||||
import { Meteor } from 'meteor/meteor'; |
||||
|
||||
import { settings } from '../../../settings/client'; |
||||
import { UserAction, USER_ACTIVITIES } from '../index'; |
||||
import { fileUploadIsValidContentType, APIClient } from '../../../utils/client'; |
||||
import { imperativeModal } from '../../../../client/lib/imperativeModal'; |
||||
import FileUploadModal from '../../../../client/views/room/modals/FileUploadModal'; |
||||
import { prependReplies } from '../../../../client/lib/utils/prependReplies'; |
||||
import { chatMessages } from '../views/app/room'; |
||||
|
||||
type Uploading = { |
||||
id: string; |
||||
name: string; |
||||
percentage: number; |
||||
error?: Error; |
||||
}; |
||||
|
||||
declare module 'meteor/session' { |
||||
// eslint-disable-next-line @typescript-eslint/interface-name-prefix
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
namespace Session { |
||||
function get(key: 'uploading'): Uploading[]; |
||||
function set(key: 'uploading', param: Uploading[]): void; |
||||
} |
||||
} |
||||
|
||||
Session.setDefault('uploading', []); |
||||
|
||||
export const uploadFileWithMessage = async ( |
||||
rid: string, |
||||
{ |
||||
description, |
||||
msg, |
||||
file, |
||||
}: { |
||||
file: File; |
||||
description?: string; |
||||
msg?: string; |
||||
}, |
||||
tmid?: string, |
||||
): Promise<void> => { |
||||
const uploads = Session.get('uploading'); |
||||
|
||||
const upload = { |
||||
id: Random.id(), |
||||
name: file.name, |
||||
percentage: 0, |
||||
}; |
||||
|
||||
uploads.push(upload); |
||||
Session.set('uploading', uploads); |
||||
|
||||
try { |
||||
await new Promise((resolve, reject) => { |
||||
const xhr = APIClient.upload( |
||||
`/v1/rooms.upload/${rid}`, |
||||
{ |
||||
msg, |
||||
tmid, |
||||
file, |
||||
description, |
||||
}, |
||||
{ |
||||
load: (event) => { |
||||
return resolve(event); |
||||
}, |
||||
progress: (event) => { |
||||
if (!event.lengthComputable) { |
||||
return; |
||||
} |
||||
const progress = (event.loaded / event.total) * 100; |
||||
if (progress === 100) { |
||||
return; |
||||
} |
||||
|
||||
const uploads = Session.get('uploading'); |
||||
|
||||
uploads |
||||
.filter((u) => u.id === upload.id) |
||||
.forEach((u) => { |
||||
u.percentage = Math.round(progress) || 0; |
||||
}); |
||||
Session.set('uploading', uploads); |
||||
}, |
||||
error: (error) => { |
||||
const uploads = Session.get('uploading'); |
||||
uploads |
||||
.filter((u) => u.id === upload.id) |
||||
.forEach((u) => { |
||||
u.error = new Error(xhr.responseText); |
||||
u.percentage = 0; |
||||
}); |
||||
Session.set('uploading', uploads); |
||||
reject(error); |
||||
}, |
||||
}, |
||||
); |
||||
|
||||
if (Session.get('uploading').length) { |
||||
UserAction.performContinuously(rid, USER_ACTIVITIES.USER_UPLOADING, { tmid }); |
||||
} |
||||
|
||||
Tracker.autorun((computation) => { |
||||
const isCanceling = Session.get(`uploading-cancel-${upload.id}`); |
||||
if (!isCanceling) { |
||||
return; |
||||
} |
||||
computation.stop(); |
||||
Session.delete(`uploading-cancel-${upload.id}`); |
||||
|
||||
xhr.abort(); |
||||
|
||||
const uploads = Session.get('uploading'); |
||||
Session.set( |
||||
'uploading', |
||||
uploads.filter((u) => u.id !== upload.id), |
||||
); |
||||
}); |
||||
}); |
||||
|
||||
const uploads = Session.get('uploading'); |
||||
Session.set( |
||||
'uploading', |
||||
uploads.filter((u) => u.id !== upload.id), |
||||
); |
||||
|
||||
if (!Session.get('uploading').length) { |
||||
UserAction.stop(rid, USER_ACTIVITIES.USER_UPLOADING, { tmid }); |
||||
} |
||||
} catch (error) { |
||||
const uploads = Session.get('uploading'); |
||||
uploads |
||||
.filter((u) => u.id === upload.id) |
||||
.forEach((u) => { |
||||
u.error = (error.xhr && error.xhr.responseJSON && error.xhr.responseJSON.error) || error.message; |
||||
u.percentage = 0; |
||||
}); |
||||
if (!uploads.length) { |
||||
UserAction.stop(rid, USER_ACTIVITIES.USER_UPLOADING, { tmid }); |
||||
} |
||||
Session.set('uploading', uploads); |
||||
} |
||||
}; |
||||
|
||||
type SingleOrArray<T> = T | T[]; |
||||
|
||||
type FileUploadProp = SingleOrArray<{ |
||||
file: File; |
||||
name: string; |
||||
}>; |
||||
|
||||
/* @deprecated */ |
||||
export const fileUpload = async ( |
||||
f: FileUploadProp, |
||||
input: HTMLInputElement, |
||||
{ |
||||
rid, |
||||
tmid, |
||||
}: { |
||||
rid: string; |
||||
tmid?: string; |
||||
}, |
||||
): Promise<void> => { |
||||
if (!f) { |
||||
throw new Error('No files to upload'); |
||||
} |
||||
|
||||
const threadsEnabled = settings.get('Threads_enabled'); |
||||
|
||||
const files = Array.isArray(f) ? f : [f]; |
||||
|
||||
const replies = $(input).data('reply') || []; |
||||
const mention = $(input).data('mention-user') || false; |
||||
|
||||
let msg = ''; |
||||
|
||||
if (!mention || !threadsEnabled) { |
||||
msg = await prependReplies('', replies, mention); |
||||
} |
||||
|
||||
if (mention && threadsEnabled && replies.length) { |
||||
tmid = replies[0]._id; |
||||
} |
||||
|
||||
const key = ['messagebox', rid, tmid].filter(Boolean).join('_'); |
||||
const messageBoxText = Meteor._localStorage.getItem(key) || ''; |
||||
|
||||
const uploadNextFile = (): void => { |
||||
const file = files.pop(); |
||||
if (!file) { |
||||
return; |
||||
} |
||||
|
||||
imperativeModal.open({ |
||||
component: FileUploadModal, |
||||
props: { |
||||
file: file.file, |
||||
fileName: file.name, |
||||
fileDescription: messageBoxText, |
||||
onClose: (): void => { |
||||
imperativeModal.close(); |
||||
uploadNextFile(); |
||||
}, |
||||
onSubmit: (fileName: string, description?: string): void => { |
||||
Object.defineProperty(file.file, 'name', { |
||||
writable: true, |
||||
value: fileName, |
||||
}); |
||||
uploadFileWithMessage( |
||||
rid, |
||||
{ |
||||
description, |
||||
msg, |
||||
file: file.file, |
||||
}, |
||||
tmid, |
||||
); |
||||
const localStorageKey = ['messagebox', rid, tmid].filter(Boolean).join('_'); |
||||
const chatMessageKey = [rid, tmid].filter(Boolean).join('-'); |
||||
const { input } = chatMessages[chatMessageKey]; |
||||
input.value = null; |
||||
$(input).trigger('input'); |
||||
Meteor._localStorage.removeItem(localStorageKey); |
||||
imperativeModal.close(); |
||||
uploadNextFile(); |
||||
}, |
||||
invalidContentType: Boolean(file.file.type && !fileUploadIsValidContentType(file.file.type)), |
||||
}, |
||||
}); |
||||
}; |
||||
|
||||
uploadNextFile(); |
||||
}; |
||||
@ -0,0 +1,52 @@ |
||||
import { RestClient } from '@rocket.chat/api-client'; |
||||
import { Meteor } from 'meteor/meteor'; |
||||
import { Accounts } from 'meteor/accounts-base'; |
||||
|
||||
import { baseURI } from '../../../../client/lib/baseURI'; |
||||
import { process2faReturn } from '../../../../client/lib/2fa/process2faReturn'; |
||||
|
||||
export class RestApiClient extends RestClient { |
||||
getCredentials(): |
||||
| { |
||||
'X-User-Id': string; |
||||
'X-Auth-Token': string; |
||||
} |
||||
| undefined { |
||||
const [uid, token] = [Meteor._localStorage.getItem(Accounts.USER_ID_KEY), Meteor._localStorage.getItem(Accounts.LOGIN_TOKEN_KEY)]; |
||||
|
||||
if (!uid || !token) { |
||||
return; |
||||
} |
||||
return { |
||||
'X-User-Id': uid, |
||||
'X-Auth-Token': token, |
||||
}; |
||||
} |
||||
} |
||||
|
||||
export const APIClient = new RestApiClient({ |
||||
baseUrl: baseURI.replace(/\/$/, ''), |
||||
}); |
||||
|
||||
APIClient.use(function (request, next) { |
||||
try { |
||||
return next(...request); |
||||
} catch (e) { |
||||
return new Promise((resolve, reject) => { |
||||
process2faReturn({ |
||||
error: e, |
||||
result: null, |
||||
emailOrUsername: undefined, |
||||
originalCallback: () => reject(e), |
||||
onCode(code, method) { |
||||
return resolve( |
||||
next(request[0], request[1], { |
||||
...request[2], |
||||
headers: { ...request[2].headers, 'x-2fa-code': code, 'x-2fa-method': method }, |
||||
}), |
||||
); |
||||
}, |
||||
}); |
||||
}); |
||||
} |
||||
}); |
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue