Merge remote-tracking branch 'upstream/develop' into feature/collapse-pinned-mels/17938
This commit is contained in:
commit
2df4f7b859
483 changed files with 11911 additions and 7811 deletions
|
@ -90,7 +90,7 @@ export default class AutoDiscoveryUtils {
|
|||
href="https://github.com/vector-im/element-web/blob/master/docs/config.md"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>{sub}</a>;
|
||||
>{ sub }</a>;
|
||||
},
|
||||
},
|
||||
);
|
||||
|
@ -130,8 +130,8 @@ export default class AutoDiscoveryUtils {
|
|||
serverErrorIsFatal: isFatalError,
|
||||
serverDeadError: (
|
||||
<div>
|
||||
<strong>{title}</strong>
|
||||
<div>{body}</div>
|
||||
<strong>{ title }</strong>
|
||||
<div>{ body }</div>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
|
|
@ -44,7 +44,7 @@ export function messageForResourceLimitError(
|
|||
|
||||
const linkSub = sub => {
|
||||
if (adminContact) {
|
||||
return <a href={adminContact} target="_blank" rel="noreferrer noopener">{sub}</a>;
|
||||
return <a href={adminContact} target="_blank" rel="noreferrer noopener">{ sub }</a>;
|
||||
} else {
|
||||
return sub;
|
||||
}
|
||||
|
@ -76,12 +76,12 @@ export function messageForSyncError(err: MatrixError | Error): ReactNode {
|
|||
},
|
||||
);
|
||||
return <div>
|
||||
<div>{limitError}</div>
|
||||
<div>{adminContact}</div>
|
||||
<div>{ limitError }</div>
|
||||
<div>{ adminContact }</div>
|
||||
</div>;
|
||||
} else {
|
||||
return <div>
|
||||
{_t("Unable to connect to Homeserver. Retrying...")}
|
||||
{ _t("Unable to connect to Homeserver. Retrying...") }
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,9 @@ import { MatrixEvent, EventStatus } from 'matrix-js-sdk/src/models/event';
|
|||
|
||||
import { MatrixClientPeg } from '../MatrixClientPeg';
|
||||
import shouldHideEvent from "../shouldHideEvent";
|
||||
import { getHandlerTile, haveTileForEvent } from "../components/views/rooms/EventTile";
|
||||
import SettingsStore from "../settings/SettingsStore";
|
||||
import { EventType } from "matrix-js-sdk/src/@types/event";
|
||||
|
||||
/**
|
||||
* Returns whether an event should allow actions like reply, reactions, edit, etc.
|
||||
|
@ -96,3 +99,43 @@ export function findEditableEvent(room: Room, isForward: boolean, fromEventId: s
|
|||
}
|
||||
}
|
||||
|
||||
export function getEventDisplayInfo(mxEvent: MatrixEvent): {
|
||||
isInfoMessage: boolean;
|
||||
tileHandler: string;
|
||||
isBubbleMessage: boolean;
|
||||
} {
|
||||
const content = mxEvent.getContent();
|
||||
const msgtype = content.msgtype;
|
||||
const eventType = mxEvent.getType();
|
||||
|
||||
let tileHandler = getHandlerTile(mxEvent);
|
||||
|
||||
// Info messages are basically information about commands processed on a room
|
||||
let isBubbleMessage = (
|
||||
eventType.startsWith("m.key.verification") ||
|
||||
(eventType === EventType.RoomMessage && msgtype && msgtype.startsWith("m.key.verification")) ||
|
||||
(eventType === EventType.RoomCreate) ||
|
||||
(eventType === EventType.RoomEncryption) ||
|
||||
(eventType === EventType.CallInvite) ||
|
||||
(tileHandler === "messages.MJitsiWidgetEvent")
|
||||
);
|
||||
let isInfoMessage = (
|
||||
!isBubbleMessage &&
|
||||
eventType !== EventType.RoomMessage &&
|
||||
eventType !== EventType.Sticker &&
|
||||
eventType !== EventType.RoomCreate
|
||||
);
|
||||
|
||||
// If we're showing hidden events in the timeline, we should use the
|
||||
// source tile when there's no regular tile for an event and also for
|
||||
// replace relations (which otherwise would display as a confusing
|
||||
// duplicate of the thing they are replacing).
|
||||
if (SettingsStore.getValue("showHiddenEventsInTimeline") && !haveTileForEvent(mxEvent)) {
|
||||
tileHandler = "messages.ViewSourceEvent";
|
||||
isBubbleMessage = false;
|
||||
// Reuse info message avatar and sender profile styling
|
||||
isInfoMessage = true;
|
||||
}
|
||||
|
||||
return { tileHandler, isInfoMessage, isBubbleMessage };
|
||||
}
|
||||
|
|
54
src/utils/FileUtils.ts
Normal file
54
src/utils/FileUtils.ts
Normal file
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
Copyright 2015 - 2021 The Matrix.org Foundation C.I.C.
|
||||
Copyright 2021 Šimon Brandner <simon.bra.ag@gmail.com>
|
||||
|
||||
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 filesize from 'filesize';
|
||||
import { IMediaEventContent } from '../customisations/models/IMediaEventContent';
|
||||
import { _t } from '../languageHandler';
|
||||
|
||||
/**
|
||||
* Extracts a human readable label for the file attachment to use as
|
||||
* link text.
|
||||
*
|
||||
* @param {IMediaEventContent} content The "content" key of the matrix event.
|
||||
* @param {string} fallbackText The fallback text
|
||||
* @param {boolean} withSize Whether to include size information. Default true.
|
||||
* @return {string} the human readable link text for the attachment.
|
||||
*/
|
||||
export function presentableTextForFile(
|
||||
content: IMediaEventContent,
|
||||
fallbackText = _t("Attachment"),
|
||||
withSize = true,
|
||||
): string {
|
||||
let text = fallbackText;
|
||||
if (content.body && content.body.length > 0) {
|
||||
// The content body should be the name of the file including a
|
||||
// file extension.
|
||||
text = content.body;
|
||||
}
|
||||
|
||||
if (content.info && content.info.size && withSize) {
|
||||
// If we know the size of the file then add it as human readable
|
||||
// string to the end of the link text so that the user knows how
|
||||
// big a file they are downloading.
|
||||
// The content.info also contains a MIME-type but we don't display
|
||||
// it since it is "ugly", users generally aren't aware what it
|
||||
// means and the type of the attachment can usually be inferrered
|
||||
// from the file extension.
|
||||
text += ' (' + filesize(content.info.size) + ')';
|
||||
}
|
||||
return text;
|
||||
}
|
54
src/utils/FixedRollingArray.ts
Normal file
54
src/utils/FixedRollingArray.ts
Normal file
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
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 { arrayFastClone, arraySeed } from "./arrays";
|
||||
|
||||
/**
|
||||
* An array which is of fixed length and accepts rolling values. Values will
|
||||
* be inserted on the left, falling off the right.
|
||||
*/
|
||||
export class FixedRollingArray<T> {
|
||||
private samples: T[] = [];
|
||||
|
||||
/**
|
||||
* Creates a new fixed rolling array.
|
||||
* @param width The width of the array.
|
||||
* @param padValue The value to seed the array with.
|
||||
*/
|
||||
constructor(private width: number, padValue: T) {
|
||||
this.samples = arraySeed(padValue, this.width);
|
||||
}
|
||||
|
||||
/**
|
||||
* The array, as a fixed length.
|
||||
*/
|
||||
public get value(): T[] {
|
||||
return this.samples;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pushes a value to the array.
|
||||
* @param value The value to push.
|
||||
*/
|
||||
public pushValue(value: T) {
|
||||
let swap = arrayFastClone(this.samples);
|
||||
swap.splice(0, 0, value);
|
||||
if (swap.length > this.width) {
|
||||
swap = swap.slice(0, this.width);
|
||||
}
|
||||
this.samples = swap;
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
Copyright 2019 - 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.
|
||||
|
@ -21,7 +21,7 @@ limitations under the License.
|
|||
* MIT license
|
||||
*/
|
||||
|
||||
function safariVersionCheck(ua) {
|
||||
function safariVersionCheck(ua: string): boolean {
|
||||
console.log("Browser is Safari - checking version for COLR support");
|
||||
try {
|
||||
const safariVersionMatch = ua.match(/Mac OS X ([\d|_]+).*Version\/([\d|.]+).*Safari/);
|
||||
|
@ -44,7 +44,7 @@ function safariVersionCheck(ua) {
|
|||
return false;
|
||||
}
|
||||
|
||||
async function isColrFontSupported() {
|
||||
async function isColrFontSupported(): Promise<boolean> {
|
||||
console.log("Checking for COLR support");
|
||||
|
||||
const { userAgent } = navigator;
|
||||
|
@ -101,7 +101,7 @@ async function isColrFontSupported() {
|
|||
}
|
||||
|
||||
let colrFontCheckStarted = false;
|
||||
export async function fixupColorFonts() {
|
||||
export async function fixupColorFonts(): Promise<void> {
|
||||
if (colrFontCheckStarted) {
|
||||
return;
|
||||
}
|
||||
|
@ -112,14 +112,14 @@ export async function fixupColorFonts() {
|
|||
document.fonts.add(new FontFace("Twemoji", path, {}));
|
||||
// For at least Chrome on Windows 10, we have to explictly add extra
|
||||
// weights for the emoji to appear in bold messages, etc.
|
||||
document.fonts.add(new FontFace("Twemoji", path, { weight: 600 }));
|
||||
document.fonts.add(new FontFace("Twemoji", path, { weight: 700 }));
|
||||
document.fonts.add(new FontFace("Twemoji", path, { weight: "600" }));
|
||||
document.fonts.add(new FontFace("Twemoji", path, { weight: "700" }));
|
||||
} else {
|
||||
// fall back to SBIX, generated via https://github.com/matrix-org/twemoji-colr/tree/matthew/sbix
|
||||
const path = `url('${require("../../res/fonts/Twemoji_Mozilla/TwemojiMozilla-sbix.woff2")}')`;
|
||||
document.fonts.add(new FontFace("Twemoji", path, {}));
|
||||
document.fonts.add(new FontFace("Twemoji", path, { weight: 600 }));
|
||||
document.fonts.add(new FontFace("Twemoji", path, { weight: 700 }));
|
||||
document.fonts.add(new FontFace("Twemoji", path, { weight: "600" }));
|
||||
document.fonts.add(new FontFace("Twemoji", path, { weight: "700" }));
|
||||
}
|
||||
// ...and if SBIX is not supported, the browser will fall back to one of the native fonts specified.
|
||||
}
|
|
@ -14,9 +14,6 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import url from 'url';
|
||||
import qs from 'qs';
|
||||
|
||||
import SdkConfig from '../SdkConfig';
|
||||
import { MatrixClientPeg } from '../MatrixClientPeg';
|
||||
|
||||
|
@ -28,11 +25,8 @@ export function getHostingLink(campaign) {
|
|||
if (MatrixClientPeg.get().getDomain() !== 'matrix.org') return null;
|
||||
|
||||
try {
|
||||
const hostingUrl = url.parse(hostingLink);
|
||||
const params = qs.parse(hostingUrl.query);
|
||||
params.utm_campaign = campaign;
|
||||
hostingUrl.search = undefined;
|
||||
hostingUrl.query = params;
|
||||
const hostingUrl = new URL(hostingLink);
|
||||
hostingUrl.searchParams.set("utm_campaign", campaign);
|
||||
return hostingUrl.format();
|
||||
} catch (e) {
|
||||
return hostingLink;
|
||||
|
|
59
src/utils/LazyValue.ts
Normal file
59
src/utils/LazyValue.ts
Normal file
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Utility class for lazily getting a variable.
|
||||
*/
|
||||
export class LazyValue<T> {
|
||||
private val: T;
|
||||
private prom: Promise<T>;
|
||||
private done = false;
|
||||
|
||||
public constructor(private getFn: () => Promise<T>) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not a cached value is present.
|
||||
*/
|
||||
public get present(): boolean {
|
||||
// we use a tracking variable just in case the final value is falsey
|
||||
return this.done;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value without invoking a get. May be undefined until the
|
||||
* value is fetched properly.
|
||||
*/
|
||||
public get cachedValue(): T {
|
||||
return this.val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a promise which resolves to the value, eventually.
|
||||
*/
|
||||
public get value(): Promise<T> {
|
||||
if (this.prom) return this.prom;
|
||||
this.prom = this.getFn();
|
||||
|
||||
// Fork the promise chain to avoid accidentally making it return undefined always.
|
||||
this.prom.then(v => {
|
||||
this.val = v;
|
||||
this.done = true;
|
||||
});
|
||||
|
||||
return this.prom;
|
||||
}
|
||||
}
|
119
src/utils/MediaEventHelper.ts
Normal file
119
src/utils/MediaEventHelper.ts
Normal file
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
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 { MatrixEvent } from "matrix-js-sdk/src";
|
||||
import { LazyValue } from "./LazyValue";
|
||||
import { Media, mediaFromContent } from "../customisations/Media";
|
||||
import { decryptFile } from "./DecryptFile";
|
||||
import { IMediaEventContent } from "../customisations/models/IMediaEventContent";
|
||||
import { IDestroyable } from "./IDestroyable";
|
||||
import { EventType, MsgType } from "matrix-js-sdk/src/@types/event";
|
||||
|
||||
// TODO: We should consider caching the blobs. https://github.com/vector-im/element-web/issues/17192
|
||||
|
||||
export class MediaEventHelper implements IDestroyable {
|
||||
// Either an HTTP or Object URL (when encrypted) to the media.
|
||||
public readonly sourceUrl: LazyValue<string>;
|
||||
public readonly thumbnailUrl: LazyValue<string>;
|
||||
|
||||
// Either the raw or decrypted (when encrypted) contents of the file.
|
||||
public readonly sourceBlob: LazyValue<Blob>;
|
||||
public readonly thumbnailBlob: LazyValue<Blob>;
|
||||
|
||||
public readonly media: Media;
|
||||
|
||||
public constructor(private event: MatrixEvent) {
|
||||
this.sourceUrl = new LazyValue(this.prepareSourceUrl);
|
||||
this.thumbnailUrl = new LazyValue(this.prepareThumbnailUrl);
|
||||
this.sourceBlob = new LazyValue(this.fetchSource);
|
||||
this.thumbnailBlob = new LazyValue(this.fetchThumbnail);
|
||||
|
||||
this.media = mediaFromContent(this.event.getContent());
|
||||
}
|
||||
|
||||
public get fileName(): string {
|
||||
return this.event.getContent<IMediaEventContent>().body || "download";
|
||||
}
|
||||
|
||||
public destroy() {
|
||||
if (this.media.isEncrypted) {
|
||||
if (this.sourceUrl.present) URL.revokeObjectURL(this.sourceUrl.cachedValue);
|
||||
if (this.thumbnailUrl.present) URL.revokeObjectURL(this.thumbnailUrl.cachedValue);
|
||||
}
|
||||
}
|
||||
|
||||
private prepareSourceUrl = async () => {
|
||||
if (this.media.isEncrypted) {
|
||||
const blob = await this.sourceBlob.value;
|
||||
return URL.createObjectURL(blob);
|
||||
} else {
|
||||
return this.media.srcHttp;
|
||||
}
|
||||
};
|
||||
|
||||
private prepareThumbnailUrl = async () => {
|
||||
if (this.media.isEncrypted) {
|
||||
const blob = await this.thumbnailBlob.value;
|
||||
return URL.createObjectURL(blob);
|
||||
} else {
|
||||
return this.media.thumbnailHttp;
|
||||
}
|
||||
};
|
||||
|
||||
private fetchSource = () => {
|
||||
if (this.media.isEncrypted) {
|
||||
return decryptFile(this.event.getContent<IMediaEventContent>().file);
|
||||
}
|
||||
return this.media.downloadSource().then(r => r.blob());
|
||||
};
|
||||
|
||||
private fetchThumbnail = () => {
|
||||
if (!this.media.hasThumbnail) return Promise.resolve(null);
|
||||
|
||||
if (this.media.isEncrypted) {
|
||||
const content = this.event.getContent<IMediaEventContent>();
|
||||
if (content.info?.thumbnail_file) {
|
||||
return decryptFile(content.info.thumbnail_file);
|
||||
} else {
|
||||
// "Should never happen"
|
||||
console.warn("Media claims to have thumbnail and is encrypted, but no thumbnail_file found");
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
}
|
||||
|
||||
return fetch(this.media.thumbnailHttp).then(r => r.blob());
|
||||
};
|
||||
|
||||
public static isEligible(event: MatrixEvent): boolean {
|
||||
if (!event) return false;
|
||||
if (event.isRedacted()) return false;
|
||||
if (event.getType() === EventType.Sticker) return true;
|
||||
if (event.getType() !== EventType.RoomMessage) return false;
|
||||
|
||||
const content = event.getContent();
|
||||
const mediaMsgTypes: string[] = [
|
||||
MsgType.Video,
|
||||
MsgType.Audio,
|
||||
MsgType.Image,
|
||||
MsgType.File,
|
||||
];
|
||||
if (mediaMsgTypes.includes(content.msgtype)) return true;
|
||||
if (typeof(content.url) === 'string') return true;
|
||||
|
||||
// Finally, it's probably not media
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -39,6 +39,9 @@ const UNKNOWN_PROFILE_ERRORS = ['M_NOT_FOUND', 'M_USER_NOT_FOUND', 'M_PROFILE_UN
|
|||
|
||||
export type CompletionStates = Record<string, InviteState>;
|
||||
|
||||
const USER_ALREADY_JOINED = "IO.ELEMENT.ALREADY_JOINED";
|
||||
const USER_ALREADY_INVITED = "IO.ELEMENT.ALREADY_INVITED";
|
||||
|
||||
/**
|
||||
* Invites multiple addresses to a room or group, handling rate limiting from the server
|
||||
*/
|
||||
|
@ -130,9 +133,14 @@ export default class MultiInviter {
|
|||
if (!room) throw new Error("Room not found");
|
||||
|
||||
const member = room.getMember(addr);
|
||||
if (member && ['join', 'invite'].includes(member.membership)) {
|
||||
throw new new MatrixError({
|
||||
errcode: "RIOT.ALREADY_IN_ROOM",
|
||||
if (member?.membership === "join") {
|
||||
throw new MatrixError({
|
||||
errcode: USER_ALREADY_JOINED,
|
||||
error: "Member already joined",
|
||||
});
|
||||
} else if (member?.membership === "invite") {
|
||||
throw new MatrixError({
|
||||
errcode: USER_ALREADY_INVITED,
|
||||
error: "Member already invited",
|
||||
});
|
||||
}
|
||||
|
@ -180,30 +188,47 @@ export default class MultiInviter {
|
|||
|
||||
let errorText;
|
||||
let fatal = false;
|
||||
if (err.errcode === 'M_FORBIDDEN') {
|
||||
fatal = true;
|
||||
errorText = _t('You do not have permission to invite people to this room.');
|
||||
} else if (err.errcode === "RIOT.ALREADY_IN_ROOM") {
|
||||
errorText = _t("User %(userId)s is already in the room", { userId: address });
|
||||
} else if (err.errcode === 'M_LIMIT_EXCEEDED') {
|
||||
// we're being throttled so wait a bit & try again
|
||||
setTimeout(() => {
|
||||
this.doInvite(address, ignoreProfile).then(resolve, reject);
|
||||
}, 5000);
|
||||
return;
|
||||
} else if (['M_NOT_FOUND', 'M_USER_NOT_FOUND'].includes(err.errcode)) {
|
||||
errorText = _t("User %(user_id)s does not exist", { user_id: address });
|
||||
} else if (err.errcode === 'M_PROFILE_UNDISCLOSED') {
|
||||
errorText = _t("User %(user_id)s may or may not exist", { user_id: address });
|
||||
} else if (err.errcode === 'M_PROFILE_NOT_FOUND' && !ignoreProfile) {
|
||||
// Invite without the profile check
|
||||
console.warn(`User ${address} does not have a profile - inviting anyways automatically`);
|
||||
this.doInvite(address, true).then(resolve, reject);
|
||||
} else if (err.errcode === "M_BAD_STATE") {
|
||||
errorText = _t("The user must be unbanned before they can be invited.");
|
||||
} else if (err.errcode === "M_UNSUPPORTED_ROOM_VERSION") {
|
||||
errorText = _t("The user's homeserver does not support the version of the room.");
|
||||
} else {
|
||||
switch (err.errcode) {
|
||||
case "M_FORBIDDEN":
|
||||
errorText = _t('You do not have permission to invite people to this room.');
|
||||
fatal = true;
|
||||
break;
|
||||
case USER_ALREADY_INVITED:
|
||||
errorText = _t("User %(userId)s is already invited to the room", { userId: address });
|
||||
break;
|
||||
case USER_ALREADY_JOINED:
|
||||
errorText = _t("User %(userId)s is already in the room", { userId: address });
|
||||
break;
|
||||
case "M_LIMIT_EXCEEDED":
|
||||
// we're being throttled so wait a bit & try again
|
||||
setTimeout(() => {
|
||||
this.doInvite(address, ignoreProfile).then(resolve, reject);
|
||||
}, 5000);
|
||||
return;
|
||||
case "M_NOT_FOUND":
|
||||
case "M_USER_NOT_FOUND":
|
||||
errorText = _t("User %(user_id)s does not exist", { user_id: address });
|
||||
break;
|
||||
case "M_PROFILE_UNDISCLOSED":
|
||||
errorText = _t("User %(user_id)s may or may not exist", { user_id: address });
|
||||
break;
|
||||
case "M_PROFILE_NOT_FOUND":
|
||||
if (!ignoreProfile) {
|
||||
// Invite without the profile check
|
||||
console.warn(`User ${address} does not have a profile - inviting anyways automatically`);
|
||||
this.doInvite(address, true).then(resolve, reject);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case "M_BAD_STATE":
|
||||
errorText = _t("The user must be unbanned before they can be invited.");
|
||||
break;
|
||||
case "M_UNSUPPORTED_ROOM_VERSION":
|
||||
errorText = _t("The user's homeserver does not support the version of the room.");
|
||||
break;
|
||||
}
|
||||
|
||||
if (!errorText) {
|
||||
errorText = _t('Unknown server error');
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import zxcvbn from 'zxcvbn';
|
||||
import zxcvbn, { ZXCVBNFeedbackWarning } from 'zxcvbn';
|
||||
|
||||
import { MatrixClientPeg } from '../MatrixClientPeg';
|
||||
import { _t, _td } from '../languageHandler';
|
||||
|
@ -84,7 +84,7 @@ export function scorePassword(password: string) {
|
|||
}
|
||||
// and warning, if any
|
||||
if (zxcvbnResult.feedback.warning) {
|
||||
zxcvbnResult.feedback.warning = _t(zxcvbnResult.feedback.warning);
|
||||
zxcvbnResult.feedback.warning = _t(zxcvbnResult.feedback.warning) as ZXCVBNFeedbackWarning;
|
||||
}
|
||||
|
||||
return zxcvbnResult;
|
||||
|
|
|
@ -26,7 +26,7 @@ Once a timer is finished or aborted, it can't be started again
|
|||
a new one through `clone()` or `cloneIfRun()`.
|
||||
*/
|
||||
export default class Timer {
|
||||
private timerHandle: NodeJS.Timeout;
|
||||
private timerHandle: number;
|
||||
private startTs: number;
|
||||
private promise: Promise<void>;
|
||||
private resolve: () => void;
|
||||
|
|
|
@ -386,7 +386,7 @@ export default class WidgetUtils {
|
|||
});
|
||||
}
|
||||
|
||||
static removeIntegrationManagerWidgets(): Promise<void> {
|
||||
static async removeIntegrationManagerWidgets(): Promise<void> {
|
||||
const client = MatrixClientPeg.get();
|
||||
if (!client) {
|
||||
throw new Error('User not logged in');
|
||||
|
@ -399,7 +399,7 @@ export default class WidgetUtils {
|
|||
delete userWidgets[key];
|
||||
}
|
||||
});
|
||||
return client.setAccountData('m.widgets', userWidgets);
|
||||
await client.setAccountData('m.widgets', userWidgets);
|
||||
}
|
||||
|
||||
static addIntegrationManagerWidget(name: string, uiUrl: string, apiUrl: string): Promise<void> {
|
||||
|
@ -407,7 +407,7 @@ export default class WidgetUtils {
|
|||
"integration_manager_" + (new Date().getTime()),
|
||||
WidgetType.INTEGRATION_MANAGER,
|
||||
uiUrl,
|
||||
"Integration Manager: " + name,
|
||||
"Integration manager: " + name,
|
||||
{ "api_url": apiUrl },
|
||||
);
|
||||
}
|
||||
|
@ -416,7 +416,7 @@ export default class WidgetUtils {
|
|||
* Remove all stickerpicker widgets (stickerpickers are user widgets by nature)
|
||||
* @return {Promise} Resolves on account data updated
|
||||
*/
|
||||
static removeStickerpickerWidgets(): Promise<void> {
|
||||
static async removeStickerpickerWidgets(): Promise<void> {
|
||||
const client = MatrixClientPeg.get();
|
||||
if (!client) {
|
||||
throw new Error('User not logged in');
|
||||
|
@ -429,7 +429,7 @@ export default class WidgetUtils {
|
|||
delete userWidgets[key];
|
||||
}
|
||||
});
|
||||
return client.setAccountData('m.widgets', userWidgets);
|
||||
await client.setAccountData('m.widgets', userWidgets);
|
||||
}
|
||||
|
||||
static makeAppConfig(
|
||||
|
|
|
@ -112,11 +112,9 @@ export function arrayRescale(input: number[], newMin: number, newMax: number): n
|
|||
* @returns {T[]} The array.
|
||||
*/
|
||||
export function arraySeed<T>(val: T, length: number): T[] {
|
||||
const a: T[] = [];
|
||||
for (let i = 0; i < length; i++) {
|
||||
a.push(val);
|
||||
}
|
||||
return a;
|
||||
// Size the array up front for performance, and use `fill` to let the browser
|
||||
// optimize the operation better than we can with a `for` loop, if it wants.
|
||||
return new Array<T>(length).fill(val);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -14,6 +14,8 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
// @ts-ignore - `.ts` is needed here to make TS happy
|
||||
import IndexedDBWorker from "../workers/indexeddb.worker.ts";
|
||||
import { createClient, ICreateClientOpts } from "matrix-js-sdk/src/matrix";
|
||||
import { IndexedDBCryptoStore } from "matrix-js-sdk/src/crypto/store/indexeddb-crypto-store";
|
||||
import { WebStorageSessionStore } from "matrix-js-sdk/src/store/session/webstorage";
|
||||
|
@ -35,10 +37,6 @@ try {
|
|||
* @param {Object} opts options to pass to Matrix.createClient. This will be
|
||||
* extended with `sessionStore` and `store` members.
|
||||
*
|
||||
* @property {string} indexedDbWorkerScript Optional URL for a web worker script
|
||||
* for IndexedDB store operations. By default, indexeddb ops are done on
|
||||
* the main thread.
|
||||
*
|
||||
* @returns {MatrixClient} the newly-created MatrixClient
|
||||
*/
|
||||
export default function createMatrixClient(opts: ICreateClientOpts) {
|
||||
|
@ -51,7 +49,7 @@ export default function createMatrixClient(opts: ICreateClientOpts) {
|
|||
indexedDB: indexedDB,
|
||||
dbName: "riot-web-sync",
|
||||
localStorage: localStorage,
|
||||
workerScript: createMatrixClient.indexedDbWorkerScript,
|
||||
workerFactory: () => new IndexedDBWorker(),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -70,5 +68,3 @@ export default function createMatrixClient(opts: ICreateClientOpts) {
|
|||
...opts,
|
||||
});
|
||||
}
|
||||
|
||||
createMatrixClient.indexedDbWorkerScript = null;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue