Move from browser-request
to fetch
(#9345)
This commit is contained in:
parent
ae883bb94b
commit
8b54be6f48
50 changed files with 1474 additions and 607 deletions
|
@ -85,7 +85,7 @@ export default class AddThreepid {
|
|||
const identityAccessToken = await authClient.getAccessToken();
|
||||
return MatrixClientPeg.get().requestEmailToken(
|
||||
emailAddress, this.clientSecret, 1,
|
||||
undefined, undefined, identityAccessToken,
|
||||
undefined, identityAccessToken,
|
||||
).then((res) => {
|
||||
this.sessionId = res.sid;
|
||||
return res;
|
||||
|
@ -142,7 +142,7 @@ export default class AddThreepid {
|
|||
const identityAccessToken = await authClient.getAccessToken();
|
||||
return MatrixClientPeg.get().requestMsisdnToken(
|
||||
phoneCountry, phoneNumber, this.clientSecret, 1,
|
||||
undefined, undefined, identityAccessToken,
|
||||
undefined, identityAccessToken,
|
||||
).then((res) => {
|
||||
this.sessionId = res.sid;
|
||||
return res;
|
||||
|
|
|
@ -17,16 +17,16 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||
import { IUploadOpts } from "matrix-js-sdk/src/@types/requests";
|
||||
import { MsgType } from "matrix-js-sdk/src/@types/event";
|
||||
import encrypt from "matrix-encrypt-attachment";
|
||||
import extractPngChunks from "png-chunks-extract";
|
||||
import { IAbortablePromise, IImageInfo } from "matrix-js-sdk/src/@types/partials";
|
||||
import { IImageInfo } from "matrix-js-sdk/src/@types/partials";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { IEventRelation, ISendEventResponse, MatrixError, MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { IEventRelation, ISendEventResponse, MatrixEvent, UploadOpts, UploadProgress } from "matrix-js-sdk/src/matrix";
|
||||
import { THREAD_RELATION_TYPE } from "matrix-js-sdk/src/models/thread";
|
||||
import { removeElement } from "matrix-js-sdk/src/utils";
|
||||
|
||||
import { IEncryptedFile, IMediaEventInfo } from "./customisations/models/IMediaEventContent";
|
||||
import { IEncryptedFile, IMediaEventContent, IMediaEventInfo } from "./customisations/models/IMediaEventContent";
|
||||
import dis from './dispatcher/dispatcher';
|
||||
import { _t } from './languageHandler';
|
||||
import Modal from './Modal';
|
||||
|
@ -39,7 +39,7 @@ import {
|
|||
UploadProgressPayload,
|
||||
UploadStartedPayload,
|
||||
} from "./dispatcher/payloads/UploadPayload";
|
||||
import { IUpload } from "./models/IUpload";
|
||||
import { RoomUpload } from "./models/RoomUpload";
|
||||
import SettingsStore from "./settings/SettingsStore";
|
||||
import { decorateStartSendingTime, sendRoundTripMetric } from "./sendTimePerformanceMetrics";
|
||||
import { TimelineRenderingType } from "./contexts/RoomContext";
|
||||
|
@ -62,14 +62,6 @@ interface IMediaConfig {
|
|||
"m.upload.size"?: number;
|
||||
}
|
||||
|
||||
interface IContent {
|
||||
body: string;
|
||||
msgtype: string;
|
||||
info: IMediaEventInfo;
|
||||
file?: string;
|
||||
url?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a file into a newly created image element.
|
||||
*
|
||||
|
@ -78,7 +70,7 @@ interface IContent {
|
|||
*/
|
||||
async function loadImageElement(imageFile: File) {
|
||||
// Load the file into an html element
|
||||
const img = document.createElement("img");
|
||||
const img = new Image();
|
||||
const objectUrl = URL.createObjectURL(imageFile);
|
||||
const imgPromise = new Promise((resolve, reject) => {
|
||||
img.onload = function() {
|
||||
|
@ -93,7 +85,7 @@ async function loadImageElement(imageFile: File) {
|
|||
|
||||
// check for hi-dpi PNGs and fudge display resolution as needed.
|
||||
// this is mainly needed for macOS screencaps
|
||||
let parsePromise;
|
||||
let parsePromise: Promise<boolean>;
|
||||
if (imageFile.type === "image/png") {
|
||||
// in practice macOS happens to order the chunks so they fall in
|
||||
// the first 0x1000 bytes (thanks to a massive ICC header).
|
||||
|
@ -277,71 +269,58 @@ function readFileAsArrayBuffer(file: File | Blob): Promise<ArrayBuffer> {
|
|||
* @param {File} file The file to upload.
|
||||
* @param {Function?} progressHandler optional callback to be called when a chunk of
|
||||
* data is uploaded.
|
||||
* @param {AbortController?} controller optional abortController to use for this upload.
|
||||
* @return {Promise} A promise that resolves with an object.
|
||||
* If the file is unencrypted then the object will have a "url" key.
|
||||
* If the file is encrypted then the object will have a "file" key.
|
||||
*/
|
||||
export function uploadFile(
|
||||
export async function uploadFile(
|
||||
matrixClient: MatrixClient,
|
||||
roomId: string,
|
||||
file: File | Blob,
|
||||
progressHandler?: IUploadOpts["progressHandler"],
|
||||
): IAbortablePromise<{ url?: string, file?: IEncryptedFile }> {
|
||||
let canceled = false;
|
||||
progressHandler?: UploadOpts["progressHandler"],
|
||||
controller?: AbortController,
|
||||
): Promise<{ url?: string, file?: IEncryptedFile }> {
|
||||
const abortController = controller ?? new AbortController();
|
||||
|
||||
// If the room is encrypted then encrypt the file before uploading it.
|
||||
if (matrixClient.isRoomEncrypted(roomId)) {
|
||||
// If the room is encrypted then encrypt the file before uploading it.
|
||||
// First read the file into memory.
|
||||
let uploadPromise: IAbortablePromise<string>;
|
||||
const prom = readFileAsArrayBuffer(file).then(function(data) {
|
||||
if (canceled) throw new UploadCanceledError();
|
||||
// Then encrypt the file.
|
||||
return encrypt.encryptAttachment(data);
|
||||
}).then(function(encryptResult) {
|
||||
if (canceled) throw new UploadCanceledError();
|
||||
const data = await readFileAsArrayBuffer(file);
|
||||
if (abortController.signal.aborted) throw new UploadCanceledError();
|
||||
|
||||
// Pass the encrypted data as a Blob to the uploader.
|
||||
const blob = new Blob([encryptResult.data]);
|
||||
uploadPromise = matrixClient.uploadContent(blob, {
|
||||
progressHandler,
|
||||
includeFilename: false,
|
||||
});
|
||||
// Then encrypt the file.
|
||||
const encryptResult = await encrypt.encryptAttachment(data);
|
||||
if (abortController.signal.aborted) throw new UploadCanceledError();
|
||||
|
||||
return uploadPromise.then(url => {
|
||||
if (canceled) throw new UploadCanceledError();
|
||||
// Pass the encrypted data as a Blob to the uploader.
|
||||
const blob = new Blob([encryptResult.data]);
|
||||
|
||||
// If the attachment is encrypted then bundle the URL along
|
||||
// with the information needed to decrypt the attachment and
|
||||
// add it under a file key.
|
||||
return {
|
||||
file: {
|
||||
...encryptResult.info,
|
||||
url,
|
||||
},
|
||||
};
|
||||
});
|
||||
}) as IAbortablePromise<{ file: IEncryptedFile }>;
|
||||
prom.abort = () => {
|
||||
canceled = true;
|
||||
if (uploadPromise) matrixClient.cancelUpload(uploadPromise);
|
||||
const { content_uri: url } = await matrixClient.uploadContent(blob, {
|
||||
progressHandler,
|
||||
abortController,
|
||||
includeFilename: false,
|
||||
});
|
||||
if (abortController.signal.aborted) throw new UploadCanceledError();
|
||||
|
||||
// If the attachment is encrypted then bundle the URL along with the information
|
||||
// needed to decrypt the attachment and add it under a file key.
|
||||
return {
|
||||
file: {
|
||||
...encryptResult.info,
|
||||
url,
|
||||
} as IEncryptedFile,
|
||||
};
|
||||
return prom;
|
||||
} else {
|
||||
const basePromise = matrixClient.uploadContent(file, { progressHandler });
|
||||
const promise1 = basePromise.then(function(url) {
|
||||
if (canceled) throw new UploadCanceledError();
|
||||
// If the attachment isn't encrypted then include the URL directly.
|
||||
return { url };
|
||||
}) as IAbortablePromise<{ url: string }>;
|
||||
promise1.abort = () => {
|
||||
canceled = true;
|
||||
matrixClient.cancelUpload(basePromise);
|
||||
};
|
||||
return promise1;
|
||||
const { content_uri: url } = await matrixClient.uploadContent(file, { progressHandler, abortController });
|
||||
if (abortController.signal.aborted) throw new UploadCanceledError();
|
||||
// If the attachment isn't encrypted then include the URL directly.
|
||||
return { url };
|
||||
}
|
||||
}
|
||||
|
||||
export default class ContentMessages {
|
||||
private inprogress: IUpload[] = [];
|
||||
private inprogress: RoomUpload[] = [];
|
||||
private mediaConfig: IMediaConfig = null;
|
||||
|
||||
public sendStickerContentToRoom(
|
||||
|
@ -460,36 +439,33 @@ export default class ContentMessages {
|
|||
});
|
||||
}
|
||||
|
||||
public getCurrentUploads(relation?: IEventRelation): IUpload[] {
|
||||
return this.inprogress.filter(upload => {
|
||||
const noRelation = !relation && !upload.relation;
|
||||
const matchingRelation = relation && upload.relation
|
||||
&& relation.rel_type === upload.relation.rel_type
|
||||
&& relation.event_id === upload.relation.event_id;
|
||||
public getCurrentUploads(relation?: IEventRelation): RoomUpload[] {
|
||||
return this.inprogress.filter(roomUpload => {
|
||||
const noRelation = !relation && !roomUpload.relation;
|
||||
const matchingRelation = relation && roomUpload.relation
|
||||
&& relation.rel_type === roomUpload.relation.rel_type
|
||||
&& relation.event_id === roomUpload.relation.event_id;
|
||||
|
||||
return (noRelation || matchingRelation) && !upload.canceled;
|
||||
return (noRelation || matchingRelation) && !roomUpload.cancelled;
|
||||
});
|
||||
}
|
||||
|
||||
public cancelUpload(promise: IAbortablePromise<any>, matrixClient: MatrixClient): void {
|
||||
const upload = this.inprogress.find(item => item.promise === promise);
|
||||
if (upload) {
|
||||
upload.canceled = true;
|
||||
matrixClient.cancelUpload(upload.promise);
|
||||
dis.dispatch<UploadCanceledPayload>({ action: Action.UploadCanceled, upload });
|
||||
}
|
||||
public cancelUpload(upload: RoomUpload): void {
|
||||
upload.abort();
|
||||
dis.dispatch<UploadCanceledPayload>({ action: Action.UploadCanceled, upload });
|
||||
}
|
||||
|
||||
private sendContentToRoom(
|
||||
public async sendContentToRoom(
|
||||
file: File,
|
||||
roomId: string,
|
||||
relation: IEventRelation | undefined,
|
||||
matrixClient: MatrixClient,
|
||||
replyToEvent: MatrixEvent | undefined,
|
||||
promBefore: Promise<any>,
|
||||
promBefore?: Promise<any>,
|
||||
) {
|
||||
const content: Omit<IContent, "info"> & { info: Partial<IMediaEventInfo> } = {
|
||||
body: file.name || 'Attachment',
|
||||
const fileName = file.name || _t("Attachment");
|
||||
const content: Omit<IMediaEventContent, "info"> & { info: Partial<IMediaEventInfo> } = {
|
||||
body: fileName,
|
||||
info: {
|
||||
size: file.size,
|
||||
},
|
||||
|
@ -512,91 +488,72 @@ export default class ContentMessages {
|
|||
content.info.mimetype = file.type;
|
||||
}
|
||||
|
||||
const prom = new Promise<void>((resolve) => {
|
||||
if (file.type.indexOf('image/') === 0) {
|
||||
content.msgtype = MsgType.Image;
|
||||
infoForImageFile(matrixClient, roomId, file).then((imageInfo) => {
|
||||
Object.assign(content.info, imageInfo);
|
||||
resolve();
|
||||
}, (e) => {
|
||||
// Failed to thumbnail, fall back to uploading an m.file
|
||||
logger.error(e);
|
||||
content.msgtype = MsgType.File;
|
||||
resolve();
|
||||
});
|
||||
} else if (file.type.indexOf('audio/') === 0) {
|
||||
content.msgtype = MsgType.Audio;
|
||||
resolve();
|
||||
} else if (file.type.indexOf('video/') === 0) {
|
||||
content.msgtype = MsgType.Video;
|
||||
infoForVideoFile(matrixClient, roomId, file).then((videoInfo) => {
|
||||
Object.assign(content.info, videoInfo);
|
||||
resolve();
|
||||
}, (e) => {
|
||||
// Failed to thumbnail, fall back to uploading an m.file
|
||||
logger.error(e);
|
||||
content.msgtype = MsgType.File;
|
||||
resolve();
|
||||
});
|
||||
} else {
|
||||
content.msgtype = MsgType.File;
|
||||
resolve();
|
||||
}
|
||||
}) as IAbortablePromise<void>;
|
||||
|
||||
// create temporary abort handler for before the actual upload gets passed off to js-sdk
|
||||
prom.abort = () => {
|
||||
upload.canceled = true;
|
||||
};
|
||||
|
||||
const upload: IUpload = {
|
||||
fileName: file.name || 'Attachment',
|
||||
roomId,
|
||||
relation,
|
||||
total: file.size,
|
||||
loaded: 0,
|
||||
promise: prom,
|
||||
};
|
||||
const upload = new RoomUpload(roomId, fileName, relation, file.size);
|
||||
this.inprogress.push(upload);
|
||||
dis.dispatch<UploadStartedPayload>({ action: Action.UploadStarted, upload });
|
||||
|
||||
function onProgress(ev) {
|
||||
upload.total = ev.total;
|
||||
upload.loaded = ev.loaded;
|
||||
function onProgress(progress: UploadProgress) {
|
||||
upload.onProgress(progress);
|
||||
dis.dispatch<UploadProgressPayload>({ action: Action.UploadProgress, upload });
|
||||
}
|
||||
|
||||
let error: MatrixError;
|
||||
return prom.then(() => {
|
||||
if (upload.canceled) throw new UploadCanceledError();
|
||||
// XXX: upload.promise must be the promise that
|
||||
// is returned by uploadFile as it has an abort()
|
||||
// method hacked onto it.
|
||||
upload.promise = uploadFile(matrixClient, roomId, file, onProgress);
|
||||
return upload.promise.then(function(result) {
|
||||
content.file = result.file;
|
||||
content.url = result.url;
|
||||
});
|
||||
}).then(() => {
|
||||
// Await previous message being sent into the room
|
||||
return promBefore;
|
||||
}).then(function() {
|
||||
if (upload.canceled) throw new UploadCanceledError();
|
||||
const threadId = relation?.rel_type === THREAD_RELATION_TYPE.name
|
||||
? relation.event_id
|
||||
: null;
|
||||
const prom = matrixClient.sendMessage(roomId, threadId, content);
|
||||
if (SettingsStore.getValue("Performance.addSendMessageTimingMetadata")) {
|
||||
prom.then(resp => {
|
||||
sendRoundTripMetric(matrixClient, roomId, resp.event_id);
|
||||
});
|
||||
try {
|
||||
if (file.type.startsWith('image/')) {
|
||||
content.msgtype = MsgType.Image;
|
||||
try {
|
||||
const imageInfo = await infoForImageFile(matrixClient, roomId, file);
|
||||
Object.assign(content.info, imageInfo);
|
||||
} catch (e) {
|
||||
// Failed to thumbnail, fall back to uploading an m.file
|
||||
logger.error(e);
|
||||
content.msgtype = MsgType.File;
|
||||
}
|
||||
} else if (file.type.indexOf('audio/') === 0) {
|
||||
content.msgtype = MsgType.Audio;
|
||||
} else if (file.type.indexOf('video/') === 0) {
|
||||
content.msgtype = MsgType.Video;
|
||||
try {
|
||||
const videoInfo = await infoForVideoFile(matrixClient, roomId, file);
|
||||
Object.assign(content.info, videoInfo);
|
||||
} catch (e) {
|
||||
// Failed to thumbnail, fall back to uploading an m.file
|
||||
logger.error(e);
|
||||
content.msgtype = MsgType.File;
|
||||
}
|
||||
} else {
|
||||
content.msgtype = MsgType.File;
|
||||
}
|
||||
return prom;
|
||||
}, function(err: MatrixError) {
|
||||
error = err;
|
||||
if (!upload.canceled) {
|
||||
|
||||
if (upload.cancelled) throw new UploadCanceledError();
|
||||
const result = await uploadFile(matrixClient, roomId, file, onProgress, upload.abortController);
|
||||
content.file = result.file;
|
||||
content.url = result.url;
|
||||
|
||||
if (upload.cancelled) throw new UploadCanceledError();
|
||||
// Await previous message being sent into the room
|
||||
if (promBefore) await promBefore;
|
||||
|
||||
if (upload.cancelled) throw new UploadCanceledError();
|
||||
const threadId = relation?.rel_type === THREAD_RELATION_TYPE.name ? relation.event_id : null;
|
||||
|
||||
const response = await matrixClient.sendMessage(roomId, threadId, content);
|
||||
|
||||
if (SettingsStore.getValue("Performance.addSendMessageTimingMetadata")) {
|
||||
sendRoundTripMetric(matrixClient, roomId, response.event_id);
|
||||
}
|
||||
|
||||
dis.dispatch<UploadFinishedPayload>({ action: Action.UploadFinished, upload });
|
||||
dis.dispatch({ action: 'message_sent' });
|
||||
} catch (error) {
|
||||
// 413: File was too big or upset the server in some way:
|
||||
// clear the media size limit so we fetch it again next time we try to upload
|
||||
if (error?.httpStatus === 413) {
|
||||
this.mediaConfig = null;
|
||||
}
|
||||
|
||||
if (!upload.cancelled) {
|
||||
let desc = _t("The file '%(fileName)s' failed to upload.", { fileName: upload.fileName });
|
||||
if (err.httpStatus === 413) {
|
||||
if (error.httpStatus === 413) {
|
||||
desc = _t(
|
||||
"The file '%(fileName)s' exceeds this homeserver's size limit for uploads",
|
||||
{ fileName: upload.fileName },
|
||||
|
@ -606,27 +563,11 @@ export default class ContentMessages {
|
|||
title: _t('Upload Failed'),
|
||||
description: desc,
|
||||
});
|
||||
}
|
||||
}).finally(() => {
|
||||
for (let i = 0; i < this.inprogress.length; ++i) {
|
||||
if (this.inprogress[i].promise === upload.promise) {
|
||||
this.inprogress.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (error) {
|
||||
// 413: File was too big or upset the server in some way:
|
||||
// clear the media size limit so we fetch it again next time
|
||||
// we try to upload
|
||||
if (error?.httpStatus === 413) {
|
||||
this.mediaConfig = null;
|
||||
}
|
||||
dis.dispatch<UploadErrorPayload>({ action: Action.UploadFailed, upload, error });
|
||||
} else {
|
||||
dis.dispatch<UploadFinishedPayload>({ action: Action.UploadFinished, upload });
|
||||
dis.dispatch({ action: 'message_sent' });
|
||||
}
|
||||
});
|
||||
} finally {
|
||||
removeElement(this.inprogress, e => e.promise === upload.promise);
|
||||
}
|
||||
}
|
||||
|
||||
private isFileSizeAcceptable(file: File) {
|
||||
|
|
|
@ -739,7 +739,7 @@ export function logout(): void {
|
|||
_isLoggingOut = true;
|
||||
const client = MatrixClientPeg.get();
|
||||
PlatformPeg.get().destroyPickleKey(client.getUserId(), client.getDeviceId());
|
||||
client.logout(undefined, true).then(onLoggedOut, (err) => {
|
||||
client.logout(true).then(onLoggedOut, (err) => {
|
||||
// Just throwing an error here is going to be very unhelpful
|
||||
// if you're trying to log out because your server's down and
|
||||
// you want to log into a different server, so just forget the
|
||||
|
|
|
@ -169,7 +169,7 @@ export default class Login {
|
|||
* @param {string} loginType the type of login to do
|
||||
* @param {ILoginParams} loginParams the parameters for the login
|
||||
*
|
||||
* @returns {MatrixClientCreds}
|
||||
* @returns {IMatrixClientCreds}
|
||||
*/
|
||||
export async function sendLoginRequest(
|
||||
hsUrl: string,
|
||||
|
|
|
@ -15,10 +15,10 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import url from 'url';
|
||||
import request from "browser-request";
|
||||
import { SERVICE_TYPES } from "matrix-js-sdk/src/service-types";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { IOpenIDToken } from 'matrix-js-sdk/src/matrix';
|
||||
|
||||
import SettingsStore from "./settings/SettingsStore";
|
||||
import { Service, startTermsFlow, TermsInteractionCallback, TermsNotSignedError } from './Terms';
|
||||
|
@ -103,29 +103,29 @@ export default class ScalarAuthClient {
|
|||
}
|
||||
}
|
||||
|
||||
private getAccountName(token: string): Promise<string> {
|
||||
const url = this.apiUrl + "/account";
|
||||
private async getAccountName(token: string): Promise<string> {
|
||||
const url = new URL(this.apiUrl + "/account");
|
||||
url.searchParams.set("scalar_token", token);
|
||||
url.searchParams.set("v", imApiVersion);
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
request({
|
||||
method: "GET",
|
||||
uri: url,
|
||||
qs: { scalar_token: token, v: imApiVersion },
|
||||
json: true,
|
||||
}, (err, response, body) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else if (body && body.errcode === 'M_TERMS_NOT_SIGNED') {
|
||||
reject(new TermsNotSignedError());
|
||||
} else if (response.statusCode / 100 !== 2) {
|
||||
reject(body);
|
||||
} else if (!body || !body.user_id) {
|
||||
reject(new Error("Missing user_id in response"));
|
||||
} else {
|
||||
resolve(body.user_id);
|
||||
}
|
||||
});
|
||||
const res = await fetch(url, {
|
||||
method: "GET",
|
||||
});
|
||||
|
||||
const body = await res.json();
|
||||
if (body?.errcode === "M_TERMS_NOT_SIGNED") {
|
||||
throw new TermsNotSignedError();
|
||||
}
|
||||
|
||||
if (!res.ok) {
|
||||
throw body;
|
||||
}
|
||||
|
||||
if (!body?.user_id) {
|
||||
throw new Error("Missing user_id in response");
|
||||
}
|
||||
|
||||
return body.user_id;
|
||||
}
|
||||
|
||||
private checkToken(token: string): Promise<string> {
|
||||
|
@ -183,56 +183,41 @@ export default class ScalarAuthClient {
|
|||
});
|
||||
}
|
||||
|
||||
exchangeForScalarToken(openidTokenObject: any): Promise<string> {
|
||||
const scalarRestUrl = this.apiUrl;
|
||||
public async exchangeForScalarToken(openidTokenObject: IOpenIDToken): Promise<string> {
|
||||
const scalarRestUrl = new URL(this.apiUrl + "/register");
|
||||
scalarRestUrl.searchParams.set("v", imApiVersion);
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
request({
|
||||
method: 'POST',
|
||||
uri: scalarRestUrl + '/register',
|
||||
qs: { v: imApiVersion },
|
||||
body: openidTokenObject,
|
||||
json: true,
|
||||
}, (err, response, body) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else if (response.statusCode / 100 !== 2) {
|
||||
reject(new Error(`Scalar request failed: ${response.statusCode}`));
|
||||
} else if (!body || !body.scalar_token) {
|
||||
reject(new Error("Missing scalar_token in response"));
|
||||
} else {
|
||||
resolve(body.scalar_token);
|
||||
}
|
||||
});
|
||||
const res = await fetch(scalarRestUrl, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(openidTokenObject),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(`Scalar request failed: ${res.status}`);
|
||||
}
|
||||
|
||||
const body = await res.json();
|
||||
if (!body?.scalar_token) {
|
||||
throw new Error("Missing scalar_token in response");
|
||||
}
|
||||
|
||||
return body.scalar_token;
|
||||
}
|
||||
|
||||
getScalarPageTitle(url: string): Promise<string> {
|
||||
let scalarPageLookupUrl = this.apiUrl + '/widgets/title_lookup';
|
||||
scalarPageLookupUrl = this.getStarterLink(scalarPageLookupUrl);
|
||||
scalarPageLookupUrl += '&curl=' + encodeURIComponent(url);
|
||||
public async getScalarPageTitle(url: string): Promise<string> {
|
||||
const scalarPageLookupUrl = new URL(this.getStarterLink(this.apiUrl + '/widgets/title_lookup'));
|
||||
scalarPageLookupUrl.searchParams.set("curl", encodeURIComponent(url));
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
request({
|
||||
method: 'GET',
|
||||
uri: scalarPageLookupUrl,
|
||||
json: true,
|
||||
}, (err, response, body) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else if (response.statusCode / 100 !== 2) {
|
||||
reject(new Error(`Scalar request failed: ${response.statusCode}`));
|
||||
} else if (!body) {
|
||||
reject(new Error("Missing page title in response"));
|
||||
} else {
|
||||
let title = "";
|
||||
if (body.page_title_cache_item && body.page_title_cache_item.cached_title) {
|
||||
title = body.page_title_cache_item.cached_title;
|
||||
}
|
||||
resolve(title);
|
||||
}
|
||||
});
|
||||
const res = await fetch(scalarPageLookupUrl, {
|
||||
method: "GET",
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(`Scalar request failed: ${res.status}`);
|
||||
}
|
||||
|
||||
const body = await res.json();
|
||||
return body?.page_title_cache_item?.cached_title;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -243,31 +228,24 @@ export default class ScalarAuthClient {
|
|||
* @param {string} widgetId The widget ID to disable assets for
|
||||
* @return {Promise} Resolves on completion
|
||||
*/
|
||||
disableWidgetAssets(widgetType: WidgetType, widgetId: string): Promise<void> {
|
||||
let url = this.apiUrl + '/widgets/set_assets_state';
|
||||
url = this.getStarterLink(url);
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
request({
|
||||
method: 'GET', // XXX: Actions shouldn't be GET requests
|
||||
uri: url,
|
||||
json: true,
|
||||
qs: {
|
||||
'widget_type': widgetType.preferred,
|
||||
'widget_id': widgetId,
|
||||
'state': 'disable',
|
||||
},
|
||||
}, (err, response, body) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else if (response.statusCode / 100 !== 2) {
|
||||
reject(new Error(`Scalar request failed: ${response.statusCode}`));
|
||||
} else if (!body) {
|
||||
reject(new Error("Failed to set widget assets state"));
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
public async disableWidgetAssets(widgetType: WidgetType, widgetId: string): Promise<void> {
|
||||
const url = new URL(this.getStarterLink(this.apiUrl + "/widgets/set_assets_state"));
|
||||
url.searchParams.set("widget_type", widgetType.preferred);
|
||||
url.searchParams.set("widget_id", widgetId);
|
||||
url.searchParams.set("state", "disable");
|
||||
|
||||
const res = await fetch(url, {
|
||||
method: "GET", // XXX: Actions shouldn't be GET requests
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(`Scalar request failed: ${res.status}`);
|
||||
}
|
||||
|
||||
const body = await res.text();
|
||||
if (!body) {
|
||||
throw new Error("Failed to set widget assets state");
|
||||
}
|
||||
}
|
||||
|
||||
getScalarInterfaceUrlForRoom(room: Room, screen: string, id: string): string {
|
||||
|
|
|
@ -17,7 +17,6 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import request from 'browser-request';
|
||||
import sanitizeHtml from 'sanitize-html';
|
||||
import classnames from 'classnames';
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
@ -61,6 +60,37 @@ export default class EmbeddedPage extends React.PureComponent<IProps, IState> {
|
|||
return sanitizeHtml(_t(s));
|
||||
}
|
||||
|
||||
private async fetchEmbed() {
|
||||
let res: Response;
|
||||
|
||||
try {
|
||||
res = await fetch(this.props.url, { method: "GET" });
|
||||
} catch (err) {
|
||||
if (this.unmounted) return;
|
||||
logger.warn(`Error loading page: ${err}`);
|
||||
this.setState({ page: _t("Couldn't load page") });
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.unmounted) return;
|
||||
|
||||
if (!res.ok) {
|
||||
logger.warn(`Error loading page: ${res.status}`);
|
||||
this.setState({ page: _t("Couldn't load page") });
|
||||
return;
|
||||
}
|
||||
|
||||
let body = (await res.text()).replace(/_t\(['"]([\s\S]*?)['"]\)/mg, (match, g1) => this.translate(g1));
|
||||
|
||||
if (this.props.replaceMap) {
|
||||
Object.keys(this.props.replaceMap).forEach(key => {
|
||||
body = body.split(key).join(this.props.replaceMap[key]);
|
||||
});
|
||||
}
|
||||
|
||||
this.setState({ page: body });
|
||||
}
|
||||
|
||||
public componentDidMount(): void {
|
||||
this.unmounted = false;
|
||||
|
||||
|
@ -68,34 +98,10 @@ export default class EmbeddedPage extends React.PureComponent<IProps, IState> {
|
|||
return;
|
||||
}
|
||||
|
||||
// we use request() to inline the page into the react component
|
||||
// We use fetch to inline the page into the react component
|
||||
// so that it can inherit CSS and theming easily rather than mess around
|
||||
// with iframes and trying to synchronise document.stylesheets.
|
||||
|
||||
request(
|
||||
{ method: "GET", url: this.props.url },
|
||||
(err, response, body) => {
|
||||
if (this.unmounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (err || response.status < 200 || response.status >= 300) {
|
||||
logger.warn(`Error loading page: ${err}`);
|
||||
this.setState({ page: _t("Couldn't load page") });
|
||||
return;
|
||||
}
|
||||
|
||||
body = body.replace(/_t\(['"]([\s\S]*?)['"]\)/mg, (match, g1) => this.translate(g1));
|
||||
|
||||
if (this.props.replaceMap) {
|
||||
Object.keys(this.props.replaceMap).forEach(key => {
|
||||
body = body.split(key).join(this.props.replaceMap[key]);
|
||||
});
|
||||
}
|
||||
|
||||
this.setState({ page: body });
|
||||
},
|
||||
);
|
||||
this.fetchEmbed();
|
||||
|
||||
this.dispatcherRef = dis.register(this.onAction);
|
||||
}
|
||||
|
|
|
@ -1362,7 +1362,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
if (this.unmounted) return;
|
||||
|
||||
this.setState({ timelineLoading: false });
|
||||
logger.error(`Error loading timeline panel at ${this.props.timelineSet.room?.roomId}/${eventId}: ${error}`);
|
||||
logger.error(`Error loading timeline panel at ${this.props.timelineSet.room?.roomId}/${eventId}`, error);
|
||||
|
||||
let onFinished: () => void;
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ limitations under the License.
|
|||
import React from 'react';
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
import filesize from "filesize";
|
||||
import { IAbortablePromise, IEventRelation } from 'matrix-js-sdk/src/matrix';
|
||||
import { IEventRelation } from 'matrix-js-sdk/src/matrix';
|
||||
import { Optional } from "matrix-events-sdk";
|
||||
|
||||
import ContentMessages from '../../ContentMessages';
|
||||
|
@ -26,8 +26,7 @@ import { _t } from '../../languageHandler';
|
|||
import { Action } from "../../dispatcher/actions";
|
||||
import ProgressBar from "../views/elements/ProgressBar";
|
||||
import AccessibleButton, { ButtonEvent } from "../views/elements/AccessibleButton";
|
||||
import { IUpload } from "../../models/IUpload";
|
||||
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
||||
import { RoomUpload } from "../../models/RoomUpload";
|
||||
import { ActionPayload } from '../../dispatcher/payloads';
|
||||
import { UploadPayload } from "../../dispatcher/payloads/UploadPayload";
|
||||
|
||||
|
@ -38,7 +37,7 @@ interface IProps {
|
|||
|
||||
interface IState {
|
||||
currentFile?: string;
|
||||
currentPromise?: IAbortablePromise<any>;
|
||||
currentUpload?: RoomUpload;
|
||||
currentLoaded?: number;
|
||||
currentTotal?: number;
|
||||
countFiles: number;
|
||||
|
@ -55,8 +54,6 @@ function isUploadPayload(payload: ActionPayload): payload is UploadPayload {
|
|||
}
|
||||
|
||||
export default class UploadBar extends React.PureComponent<IProps, IState> {
|
||||
static contextType = MatrixClientContext;
|
||||
|
||||
private dispatcherRef: Optional<string>;
|
||||
private mounted = false;
|
||||
|
||||
|
@ -78,7 +75,7 @@ export default class UploadBar extends React.PureComponent<IProps, IState> {
|
|||
dis.unregister(this.dispatcherRef!);
|
||||
}
|
||||
|
||||
private getUploadsInRoom(): IUpload[] {
|
||||
private getUploadsInRoom(): RoomUpload[] {
|
||||
const uploads = ContentMessages.sharedInstance().getCurrentUploads(this.props.relation);
|
||||
return uploads.filter(u => u.roomId === this.props.room.roomId);
|
||||
}
|
||||
|
@ -86,8 +83,8 @@ export default class UploadBar extends React.PureComponent<IProps, IState> {
|
|||
private calculateState(): IState {
|
||||
const [currentUpload, ...otherUploads] = this.getUploadsInRoom();
|
||||
return {
|
||||
currentUpload,
|
||||
currentFile: currentUpload?.fileName,
|
||||
currentPromise: currentUpload?.promise,
|
||||
currentLoaded: currentUpload?.loaded,
|
||||
currentTotal: currentUpload?.total,
|
||||
countFiles: otherUploads.length + 1,
|
||||
|
@ -103,7 +100,7 @@ export default class UploadBar extends React.PureComponent<IProps, IState> {
|
|||
|
||||
private onCancelClick = (ev: ButtonEvent) => {
|
||||
ev.preventDefault();
|
||||
ContentMessages.sharedInstance().cancelUpload(this.state.currentPromise!, this.context);
|
||||
ContentMessages.sharedInstance().cancelUpload(this.state.currentUpload!);
|
||||
};
|
||||
|
||||
render() {
|
||||
|
|
|
@ -15,7 +15,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React, { ReactNode } from 'react';
|
||||
import { MatrixError } from "matrix-js-sdk/src/http-api";
|
||||
import { ConnectionError, MatrixError } from "matrix-js-sdk/src/http-api";
|
||||
import classNames from "classnames";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
|
@ -453,7 +453,7 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
|
|||
let errorText: ReactNode = _t("There was a problem communicating with the homeserver, " +
|
||||
"please try again later.") + (errCode ? " (" + errCode + ")" : "");
|
||||
|
||||
if (err["cors"] === 'rejected') { // browser-request specific error field
|
||||
if (err instanceof ConnectionError) {
|
||||
if (window.location.protocol === 'https:' &&
|
||||
(this.props.serverConfig.hsUrl.startsWith("http:") ||
|
||||
!this.props.serverConfig.hsUrl.startsWith("http"))
|
||||
|
|
|
@ -16,7 +16,6 @@ Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import request from 'browser-request';
|
||||
|
||||
import { _t } from '../../../languageHandler';
|
||||
import QuestionDialog from "./QuestionDialog";
|
||||
|
@ -37,22 +36,33 @@ export default class ChangelogDialog extends React.Component<IProps> {
|
|||
this.state = {};
|
||||
}
|
||||
|
||||
private async fetchChanges(repo: string, oldVersion: string, newVersion: string): Promise<void> {
|
||||
const url = `https://riot.im/github/repos/${repo}/compare/${oldVersion}...${newVersion}`;
|
||||
|
||||
try {
|
||||
const res = await fetch(url);
|
||||
|
||||
if (!res.ok) {
|
||||
this.setState({ [repo]: res.statusText });
|
||||
return;
|
||||
}
|
||||
|
||||
const body = await res.json();
|
||||
this.setState({ [repo]: body.commits });
|
||||
} catch (err) {
|
||||
this.setState({ [repo]: err.message });
|
||||
}
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
const version = this.props.newVersion.split('-');
|
||||
const version2 = this.props.version.split('-');
|
||||
if (version == null || version2 == null) return;
|
||||
// parse versions of form: [vectorversion]-react-[react-sdk-version]-js-[js-sdk-version]
|
||||
for (let i=0; i<REPOS.length; i++) {
|
||||
for (let i = 0; i < REPOS.length; i++) {
|
||||
const oldVersion = version2[2*i];
|
||||
const newVersion = version[2*i];
|
||||
const url = `https://riot.im/github/repos/${REPOS[i]}/compare/${oldVersion}...${newVersion}`;
|
||||
request(url, (err, response, body) => {
|
||||
if (response.statusCode < 200 || response.statusCode >= 300) {
|
||||
this.setState({ [REPOS[i]]: response.statusText });
|
||||
return;
|
||||
}
|
||||
this.setState({ [REPOS[i]]: JSON.parse(body).commits });
|
||||
});
|
||||
this.fetchChanges(REPOS[i], oldVersion, newVersion);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -654,12 +654,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
|
|||
const token = await authClient.getAccessToken();
|
||||
if (term !== this.state.filterText) return; // abandon hope
|
||||
|
||||
const lookup = await MatrixClientPeg.get().lookupThreePid(
|
||||
'email',
|
||||
term,
|
||||
undefined, // callback
|
||||
token,
|
||||
);
|
||||
const lookup = await MatrixClientPeg.get().lookupThreePid('email', term, token);
|
||||
if (term !== this.state.filterText) return; // abandon hope
|
||||
|
||||
if (!lookup || !lookup.mxid) {
|
||||
|
|
|
@ -15,7 +15,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { MatrixClient } from 'matrix-js-sdk/src/matrix';
|
||||
import { MatrixClient, Method } from 'matrix-js-sdk/src/matrix';
|
||||
import { logger } from 'matrix-js-sdk/src/logger';
|
||||
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
@ -33,17 +33,10 @@ import { SettingLevel } from "../../../settings/SettingLevel";
|
|||
* @throws if the proxy server is unreachable or not configured to the given homeserver
|
||||
*/
|
||||
async function syncHealthCheck(cli: MatrixClient): Promise<void> {
|
||||
const controller = new AbortController();
|
||||
const id = setTimeout(() => controller.abort(), 10 * 1000); // 10s
|
||||
const url = cli.http.getUrl("/sync", {}, "/_matrix/client/unstable/org.matrix.msc3575");
|
||||
const res = await fetch(url, {
|
||||
signal: controller.signal,
|
||||
method: "POST",
|
||||
await cli.http.authedRequest(Method.Post, "/sync", undefined, undefined, {
|
||||
localTimeoutMs: 10 * 1000, // 10s
|
||||
prefix: "/_matrix/client/unstable/org.matrix.msc3575",
|
||||
});
|
||||
clearTimeout(id);
|
||||
if (res.status != 200) {
|
||||
throw new Error(`syncHealthCheck: server returned HTTP ${res.status}`);
|
||||
}
|
||||
logger.info("server natively support sliding sync OK");
|
||||
}
|
||||
|
||||
|
|
|
@ -74,7 +74,7 @@ const MiniAvatarUploader: React.FC<IProps> = ({
|
|||
if (!ev.target.files?.length) return;
|
||||
setBusy(true);
|
||||
const file = ev.target.files[0];
|
||||
const uri = await cli.uploadContent(file);
|
||||
const { content_uri: uri } = await cli.uploadContent(file);
|
||||
await setAvatarUrl(uri);
|
||||
setBusy(false);
|
||||
}}
|
||||
|
|
|
@ -134,7 +134,7 @@ export default class RoomProfileSettings extends React.Component<IProps, IState>
|
|||
}
|
||||
|
||||
if (this.state.avatarFile) {
|
||||
const uri = await client.uploadContent(this.state.avatarFile);
|
||||
const { content_uri: uri } = await client.uploadContent(this.state.avatarFile);
|
||||
await client.sendStateEvent(this.props.roomId, 'm.room.avatar', { url: uri }, '');
|
||||
newState.avatarUrl = mediaFromMxc(uri).getSquareThumbnailHttp(96);
|
||||
newState.originalAvatarUrl = newState.avatarUrl;
|
||||
|
|
|
@ -148,7 +148,6 @@ export default class RoomPreviewBar extends React.Component<IProps, IState> {
|
|||
const result = await MatrixClientPeg.get().lookupThreePid(
|
||||
'email',
|
||||
this.props.invitedEmail,
|
||||
undefined /* callback */,
|
||||
identityAccessToken,
|
||||
);
|
||||
this.setState({ invitedEmailMxid: result.mxid });
|
||||
|
|
|
@ -115,13 +115,13 @@ export default class ChangeAvatar extends React.Component<IProps, IState> {
|
|||
this.setState({
|
||||
phase: Phases.Uploading,
|
||||
});
|
||||
const httpPromise = MatrixClientPeg.get().uploadContent(file).then((url) => {
|
||||
const httpPromise = MatrixClientPeg.get().uploadContent(file).then(({ content_uri: url }) => {
|
||||
newUrl = url;
|
||||
if (this.props.room) {
|
||||
return MatrixClientPeg.get().sendStateEvent(
|
||||
this.props.room.roomId,
|
||||
'm.room.avatar',
|
||||
{ url: url },
|
||||
{ url },
|
||||
'',
|
||||
);
|
||||
} else {
|
||||
|
|
|
@ -111,7 +111,7 @@ export default class ProfileSettings extends React.Component<{}, IState> {
|
|||
logger.log(
|
||||
`Uploading new avatar, ${this.state.avatarFile.name} of type ${this.state.avatarFile.type},` +
|
||||
` (${this.state.avatarFile.size}) bytes`);
|
||||
const uri = await client.uploadContent(this.state.avatarFile);
|
||||
const { content_uri: uri } = await client.uploadContent(this.state.avatarFile);
|
||||
await client.setAvatarUrl(uri);
|
||||
newState.avatarUrl = mediaFromMxc(uri).getSquareThumbnailHttp(96);
|
||||
newState.originalAvatarUrl = newState.avatarUrl;
|
||||
|
|
|
@ -246,7 +246,7 @@ export default async function createRoom(opts: IOpts): Promise<string | null> {
|
|||
if (opts.avatar) {
|
||||
let url = opts.avatar;
|
||||
if (opts.avatar instanceof File) {
|
||||
url = await client.uploadContent(opts.avatar);
|
||||
({ content_uri: url } = await client.uploadContent(opts.avatar));
|
||||
}
|
||||
|
||||
createOpts.initial_state.push({
|
||||
|
|
|
@ -151,7 +151,7 @@ export class Media {
|
|||
* @param {MatrixClient} client? Optional client to use.
|
||||
* @returns {Media} The media object.
|
||||
*/
|
||||
export function mediaFromContent(content: IMediaEventContent, client?: MatrixClient): Media {
|
||||
export function mediaFromContent(content: Partial<IMediaEventContent>, client?: MatrixClient): Media {
|
||||
return new Media(prepEventContentAsMedia(content), client);
|
||||
}
|
||||
|
||||
|
|
|
@ -46,6 +46,7 @@ export interface IMediaEventInfo {
|
|||
}
|
||||
|
||||
export interface IMediaEventContent {
|
||||
msgtype: string;
|
||||
body?: string;
|
||||
filename?: string; // `m.file` optional field
|
||||
url?: string; // required on unencrypted media
|
||||
|
@ -69,7 +70,7 @@ export interface IMediaObject {
|
|||
* @returns {IPreparedMedia} A prepared media object.
|
||||
* @throws Throws if the given content cannot be packaged into a prepared media object.
|
||||
*/
|
||||
export function prepEventContentAsMedia(content: IMediaEventContent): IPreparedMedia {
|
||||
export function prepEventContentAsMedia(content: Partial<IMediaEventContent>): IPreparedMedia {
|
||||
let thumbnail: IMediaObject = null;
|
||||
if (content?.info?.thumbnail_url) {
|
||||
thumbnail = {
|
||||
|
|
|
@ -16,13 +16,13 @@ limitations under the License.
|
|||
|
||||
import { ActionPayload } from "../payloads";
|
||||
import { Action } from "../actions";
|
||||
import { IUpload } from "../../models/IUpload";
|
||||
import { RoomUpload } from "../../models/RoomUpload";
|
||||
|
||||
export interface UploadPayload extends ActionPayload {
|
||||
/**
|
||||
* The upload with fields representing the new upload state.
|
||||
*/
|
||||
upload: IUpload;
|
||||
upload: RoomUpload;
|
||||
}
|
||||
|
||||
export interface UploadStartedPayload extends UploadPayload {
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
"Error": "Error",
|
||||
"Unable to load! Check your network connectivity and try again.": "Unable to load! Check your network connectivity and try again.",
|
||||
"Dismiss": "Dismiss",
|
||||
"Attachment": "Attachment",
|
||||
"The file '%(fileName)s' failed to upload.": "The file '%(fileName)s' failed to upload.",
|
||||
"The file '%(fileName)s' exceeds this homeserver's size limit for uploads": "The file '%(fileName)s' exceeds this homeserver's size limit for uploads",
|
||||
"Upload Failed": "Upload Failed",
|
||||
|
@ -654,7 +655,6 @@
|
|||
"This homeserver has exceeded one of its resource limits.": "This homeserver has exceeded one of its resource limits.",
|
||||
"Please <a>contact your service administrator</a> to continue using the service.": "Please <a>contact your service administrator</a> to continue using the service.",
|
||||
"Unable to connect to Homeserver. Retrying...": "Unable to connect to Homeserver. Retrying...",
|
||||
"Attachment": "Attachment",
|
||||
"%(items)s and %(count)s others|other": "%(items)s and %(count)s others",
|
||||
"%(items)s and %(count)s others|one": "%(items)s and one other",
|
||||
"%(items)s and %(lastItem)s": "%(items)s and %(lastItem)s",
|
||||
|
|
|
@ -17,7 +17,6 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import request from 'browser-request';
|
||||
import counterpart from 'counterpart';
|
||||
import React from 'react';
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
@ -386,6 +385,11 @@ export function setMissingEntryGenerator(f: (value: string) => void) {
|
|||
counterpart.setMissingEntryGenerator(f);
|
||||
}
|
||||
|
||||
type Language = {
|
||||
fileName: string;
|
||||
label: string;
|
||||
};
|
||||
|
||||
export function setLanguage(preferredLangs: string | string[]) {
|
||||
if (!Array.isArray(preferredLangs)) {
|
||||
preferredLangs = [preferredLangs];
|
||||
|
@ -396,8 +400,8 @@ export function setLanguage(preferredLangs: string | string[]) {
|
|||
plaf.setLanguage(preferredLangs);
|
||||
}
|
||||
|
||||
let langToUse;
|
||||
let availLangs;
|
||||
let langToUse: string;
|
||||
let availLangs: { [lang: string]: Language };
|
||||
return getLangsJson().then((result) => {
|
||||
availLangs = result;
|
||||
|
||||
|
@ -532,29 +536,21 @@ export function pickBestLanguage(langs: string[]): string {
|
|||
return langs[0];
|
||||
}
|
||||
|
||||
function getLangsJson(): Promise<object> {
|
||||
return new Promise((resolve, reject) => {
|
||||
let url;
|
||||
if (typeof(webpackLangJsonUrl) === 'string') { // in Jest this 'url' isn't a URL, so just fall through
|
||||
url = webpackLangJsonUrl;
|
||||
} else {
|
||||
url = i18nFolder + 'languages.json';
|
||||
}
|
||||
request(
|
||||
{ method: "GET", url },
|
||||
(err, response, body) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
if (response.status < 200 || response.status >= 300) {
|
||||
reject(new Error(`Failed to load ${url}, got ${response.status}`));
|
||||
return;
|
||||
}
|
||||
resolve(JSON.parse(body));
|
||||
},
|
||||
);
|
||||
});
|
||||
async function getLangsJson(): Promise<{ [lang: string]: Language }> {
|
||||
let url: string;
|
||||
if (typeof(webpackLangJsonUrl) === 'string') { // in Jest this 'url' isn't a URL, so just fall through
|
||||
url = webpackLangJsonUrl;
|
||||
} else {
|
||||
url = i18nFolder + 'languages.json';
|
||||
}
|
||||
|
||||
const res = await fetch(url, { method: "GET" });
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(`Failed to load ${url}, got ${res.status}`);
|
||||
}
|
||||
|
||||
return res.json();
|
||||
}
|
||||
|
||||
interface ICounterpartTranslation {
|
||||
|
@ -571,23 +567,14 @@ async function getLanguageRetry(langPath: string, num = 3): Promise<ICounterpart
|
|||
});
|
||||
}
|
||||
|
||||
function getLanguage(langPath: string): Promise<ICounterpartTranslation> {
|
||||
return new Promise((resolve, reject) => {
|
||||
request(
|
||||
{ method: "GET", url: langPath },
|
||||
(err, response, body) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
if (response.status < 200 || response.status >= 300) {
|
||||
reject(new Error(`Failed to load ${langPath}, got ${response.status}`));
|
||||
return;
|
||||
}
|
||||
resolve(JSON.parse(body));
|
||||
},
|
||||
);
|
||||
});
|
||||
async function getLanguage(langPath: string): Promise<ICounterpartTranslation> {
|
||||
const res = await fetch(langPath, { method: "GET" });
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(`Failed to load ${langPath}, got ${res.status}`);
|
||||
}
|
||||
|
||||
return res.json();
|
||||
}
|
||||
|
||||
export interface ICustomTranslations {
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
/*
|
||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { IEventRelation } from "matrix-js-sdk/src/matrix";
|
||||
import { IAbortablePromise } from "matrix-js-sdk/src/@types/partials";
|
||||
|
||||
export interface IUpload {
|
||||
fileName: string;
|
||||
roomId: string;
|
||||
relation?: IEventRelation;
|
||||
total: number;
|
||||
loaded: number;
|
||||
promise: IAbortablePromise<any>;
|
||||
canceled?: boolean;
|
||||
}
|
53
src/models/RoomUpload.ts
Normal file
53
src/models/RoomUpload.ts
Normal file
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { IEventRelation, UploadProgress } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { IEncryptedFile } from "../customisations/models/IMediaEventContent";
|
||||
|
||||
export class RoomUpload {
|
||||
public readonly abortController = new AbortController();
|
||||
public promise: Promise<{ url?: string, file?: IEncryptedFile }>;
|
||||
private uploaded = 0;
|
||||
|
||||
constructor(
|
||||
public readonly roomId: string,
|
||||
public readonly fileName: string,
|
||||
public readonly relation?: IEventRelation,
|
||||
public fileSize = 0,
|
||||
) {}
|
||||
|
||||
public onProgress(progress: UploadProgress) {
|
||||
this.uploaded = progress.loaded;
|
||||
this.fileSize = progress.total;
|
||||
}
|
||||
|
||||
public abort(): void {
|
||||
this.abortController.abort();
|
||||
}
|
||||
|
||||
public get cancelled(): boolean {
|
||||
return this.abortController.signal.aborted;
|
||||
}
|
||||
|
||||
public get total(): number {
|
||||
return this.fileSize;
|
||||
}
|
||||
|
||||
public get loaded(): number {
|
||||
return this.uploaded;
|
||||
}
|
||||
}
|
|
@ -184,7 +184,7 @@ export default class MultiInviter {
|
|||
}
|
||||
}
|
||||
|
||||
return this.matrixClient.invite(roomId, addr, undefined, this.reason);
|
||||
return this.matrixClient.invite(roomId, addr, this.reason);
|
||||
} else {
|
||||
throw new Error('Unsupported address');
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { IAbortablePromise, MatrixClient, MatrixEvent, RelationType } from "matrix-js-sdk/src/matrix";
|
||||
import { MatrixClient, MatrixEvent, RelationType } from "matrix-js-sdk/src/matrix";
|
||||
import { TypedEventEmitter } from "matrix-js-sdk/src/models/typed-event-emitter";
|
||||
|
||||
import {
|
||||
|
@ -108,7 +108,7 @@ export class VoiceBroadcastRecording
|
|||
await this.sendVoiceMessage(chunk, url, file);
|
||||
};
|
||||
|
||||
private uploadFile(chunk: ChunkRecordedPayload): IAbortablePromise<{ url?: string, file?: IEncryptedFile }> {
|
||||
private uploadFile(chunk: ChunkRecordedPayload): ReturnType<typeof uploadFile> {
|
||||
return uploadFile(
|
||||
this.client,
|
||||
this.infoEvent.getRoomId(),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue