Merge branch 'develop' into travis/welcome-login

This commit is contained in:
Travis Ralston 2021-03-12 14:55:40 -07:00
commit 25485edb3f
493 changed files with 13939 additions and 3387 deletions

View file

@ -1,6 +1,5 @@
/*
Copyright 2016 OpenMarket Ltd
Copyright 2018 New Vector Ltd
Copyright 2016, 2018, 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.
@ -17,8 +16,8 @@ limitations under the License.
// Pull in the encryption lib so that we can decrypt attachments.
import encrypt from 'browser-encrypt-attachment';
// Grab the client so that we can turn mxc:// URLs into https:// URLS.
import {MatrixClientPeg} from '../MatrixClientPeg';
import {mediaFromContent} from "../customisations/Media";
import {IEncryptedFile} from "../customisations/models/IMediaEventContent";
// WARNING: We have to be very careful about what mime-types we allow into blobs,
// as for performance reasons these are now rendered via URL.createObjectURL()
@ -54,48 +53,46 @@ import {MatrixClientPeg} from '../MatrixClientPeg';
// For the record, mime-types which must NEVER enter this list below include:
// text/html, text/xhtml, image/svg, image/svg+xml, image/pdf, and similar.
const ALLOWED_BLOB_MIMETYPES = {
'image/jpeg': true,
'image/gif': true,
'image/png': true,
const ALLOWED_BLOB_MIMETYPES = [
'image/jpeg',
'image/gif',
'image/png',
'video/mp4': true,
'video/webm': true,
'video/ogg': true,
'video/mp4',
'video/webm',
'video/ogg',
'audio/mp4': true,
'audio/webm': true,
'audio/aac': true,
'audio/mpeg': true,
'audio/ogg': true,
'audio/wave': true,
'audio/wav': true,
'audio/x-wav': true,
'audio/x-pn-wav': true,
'audio/flac': true,
'audio/x-flac': true,
};
'audio/mp4',
'audio/webm',
'audio/aac',
'audio/mpeg',
'audio/ogg',
'audio/wave',
'audio/wav',
'audio/x-wav',
'audio/x-pn-wav',
'audio/flac',
'audio/x-flac',
];
/**
* Decrypt a file attached to a matrix event.
* @param {Object} file The json taken from the matrix event.
* @param {IEncryptedFile} file The json taken from the matrix event.
* This passed to [link]{@link https://github.com/matrix-org/browser-encrypt-attachments}
* as the encryption info object, so will also have the those keys in addition to
* the keys below.
* @param {string} file.url An mxc:// URL for the encrypted file.
* @param {string} file.mimetype The MIME-type of the plaintext file.
* @returns {Promise}
* @returns {Promise<Blob>} Resolves to a Blob of the file.
*/
export function decryptFile(file) {
const url = MatrixClientPeg.get().mxcUrlToHttp(file.url);
export function decryptFile(file: IEncryptedFile): Promise<Blob> {
const media = mediaFromContent({file});
// Download the encrypted file as an array buffer.
return Promise.resolve(fetch(url)).then(function(response) {
return media.downloadSource().then((response) => {
return response.arrayBuffer();
}).then(function(responseData) {
}).then((responseData) => {
// Decrypt the array buffer using the information taken from
// the event content.
return encrypt.decryptAttachment(responseData, file);
}).then(function(dataArray) {
}).then((dataArray) => {
// Turn the array into a Blob and give it the correct MIME-type.
// IMPORTANT: we must not allow scriptable mime-types into Blobs otherwise
@ -103,11 +100,10 @@ export function decryptFile(file) {
// browser (e.g. by copying the URI into a new tab or window.)
// See warning at top of file.
let mimetype = file.mimetype ? file.mimetype.split(";")[0].trim() : '';
if (!ALLOWED_BLOB_MIMETYPES[mimetype]) {
if (!ALLOWED_BLOB_MIMETYPES.includes(mimetype)) {
mimetype = 'application/octet-stream';
}
const blob = new Blob([dataArray], {type: mimetype});
return blob;
return new Blob([dataArray], {type: mimetype});
});
}

View file

@ -62,6 +62,7 @@ export function messageForSyncError(err) {
err.data.admin_contact,
{
'monthly_active_user': _td("This homeserver has hit its Monthly Active User limit."),
'hs_blocked': _td("This homeserver has been blocked by its administrator."),
'': _td("This homeserver has exceeded one of its resource limits."),
},
);

View file

@ -15,8 +15,6 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
"use strict";
// polyfill textencoder if necessary
import * as TextEncodingUtf8 from 'text-encoding-utf-8';
let TextEncoder = window.TextEncoder;

View file

@ -53,13 +53,15 @@ export default class MultiInviter {
* instance of the class.
*
* @param {array} addrs Array of addresses to invite
* @param {string} reason Reason for inviting (optional)
* @returns {Promise} Resolved when all invitations in the queue are complete
*/
invite(addrs) {
invite(addrs, reason) {
if (this.addrs.length > 0) {
throw new Error("Already inviting/invited");
}
this.addrs.push(...addrs);
this.reason = reason;
for (const addr of this.addrs) {
if (getAddressType(addr) === null) {
@ -123,7 +125,7 @@ export default class MultiInviter {
}
}
return MatrixClientPeg.get().invite(roomId, addr);
return MatrixClientPeg.get().invite(roomId, addr, undefined, this.reason);
} else {
throw new Error('Unsupported address');
}

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { arrayDiff, arrayHasDiff, arrayMerge, arrayUnion } from "./arrays";
import { arrayDiff, arrayMerge, arrayUnion } from "./arrays";
type ObjectExcluding<O extends {}, P extends (keyof O)[]> = {[k in Exclude<keyof O, P[number]>]: O[k]};
@ -86,11 +86,14 @@ export function objectShallowClone<O extends {}>(a: O, propertyCloner?: (k: keyo
* @returns True if there's a difference between the objects, false otherwise
*/
export function objectHasDiff<O extends {}>(a: O, b: O): boolean {
if (a === b) return false;
const aKeys = Object.keys(a);
const bKeys = Object.keys(b);
if (arrayHasDiff(aKeys, bKeys)) return true;
if (aKeys.length !== bKeys.length) return true;
const possibleChanges = arrayUnion(aKeys, bKeys);
// if the amalgamation of both sets of keys has the a different length to the inputs then there must be a change
if (possibleChanges.length !== aKeys.length) return true;
return possibleChanges.some(k => a[k] !== b[k]);
}

View file

@ -14,9 +14,11 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {MatrixClientPeg} from "../../MatrixClientPeg";
import isIp from "is-ip";
import * as utils from 'matrix-js-sdk/src/utils';
import * as utils from "matrix-js-sdk/src/utils";
import {Room} from "matrix-js-sdk/src/models/room";
import {MatrixClientPeg} from "../../MatrixClientPeg";
import SpecPermalinkConstructor, {baseUrl as matrixtoBaseUrl} from "./SpecPermalinkConstructor";
import PermalinkConstructor, {PermalinkParts} from "./PermalinkConstructor";
import ElementPermalinkConstructor from "./ElementPermalinkConstructor";
@ -121,6 +123,10 @@ export class RoomPermalinkCreator {
this._started = false;
}
get serverCandidates() {
return this._serverCandidates;
}
isStarted() {
return this._started;
}
@ -451,3 +457,9 @@ function isHostnameIpAddress(hostname) {
return isIp(hostname);
}
export const calculateRoomVia = (room: Room) => {
const permalinkCreator = new RoomPermalinkCreator(room);
permalinkCreator.load();
return permalinkCreator.serverCandidates;
};

View file

@ -30,9 +30,11 @@ import * as sdk from '../index';
* @param {string} name The dot-path name of the component being replaced.
* @param {React.Component} origComponent The component that can be replaced
* with a skinned version. If no skinned version is available, this component
* will be used.
* will be used. Note that this is automatically provided to the function and
* thus is optional for purposes of types.
* @returns {ClassDecorator} The decorator.
*/
export function replaceableComponent(name: string, origComponent: React.Component) {
export function replaceableComponent(name: string, origComponent?: React.Component): ClassDecorator {
// Decorators return a function to override the class (origComponent). This
// ultimately assumes that `getComponent()` won't throw an error and instead
// return a falsey value like `null` when the skin doesn't have a component.

34
src/utils/sets.ts Normal file
View file

@ -0,0 +1,34 @@
/*
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.
*/
/**
* Determines if two sets are different through a shallow comparison.
* @param a The first set. Must be defined.
* @param b The second set. Must be defined.
* @returns True if they are different, false otherwise.
*/
export function setHasDiff<T>(a: Set<T>, b: Set<T>): boolean {
if (a.size === b.size) {
// When the lengths are equal, check to see if either set is missing an element from the other.
if (Array.from(b).some(i => !a.has(i))) return true;
if (Array.from(a).some(i => !b.has(i))) return true;
// if all the keys are common, say so
return false;
} else {
return true; // different lengths means they are naturally diverged
}
}

81
src/utils/space.ts Normal file
View file

@ -0,0 +1,81 @@
/*
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 {Room} from "matrix-js-sdk/src/models/room";
import {MatrixClient} from "matrix-js-sdk/src/client";
import {EventType} from "matrix-js-sdk/src/@types/event";
import {calculateRoomVia} from "../utils/permalinks/Permalinks";
import Modal from "../Modal";
import SpaceSettingsDialog from "../components/views/dialogs/SpaceSettingsDialog";
import AddExistingToSpaceDialog from "../components/views/dialogs/AddExistingToSpaceDialog";
import CreateRoomDialog from "../components/views/dialogs/CreateRoomDialog";
import createRoom, {IOpts} from "../createRoom";
export const shouldShowSpaceSettings = (cli: MatrixClient, space: Room) => {
const userId = cli.getUserId();
return space.getMyMembership() === "join"
&& (space.currentState.maySendStateEvent(EventType.RoomAvatar, userId)
|| space.currentState.maySendStateEvent(EventType.RoomName, userId)
|| space.currentState.maySendStateEvent(EventType.RoomTopic, userId)
|| space.currentState.maySendStateEvent(EventType.RoomJoinRules, userId));
};
export const makeSpaceParentEvent = (room: Room, canonical = false) => ({
type: EventType.SpaceParent,
content: {
"via": calculateRoomVia(room),
"canonical": canonical,
},
state_key: room.roomId,
});
export const showSpaceSettings = (cli: MatrixClient, space: Room) => {
Modal.createTrackedDialog("Space Settings", "", SpaceSettingsDialog, {
matrixClient: cli,
space,
}, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true);
};
export const showAddExistingRooms = async (cli: MatrixClient, space: Room) => {
return Modal.createTrackedDialog(
"Space Landing",
"Add Existing",
AddExistingToSpaceDialog,
{
matrixClient: cli,
onCreateRoomClick: showCreateNewRoom,
space,
},
"mx_AddExistingToSpaceDialog_wrapper",
).finished;
};
export const showCreateNewRoom = async (cli: MatrixClient, space: Room) => {
const modal = Modal.createTrackedDialog<[boolean, IOpts]>(
"Space Landing",
"Create Room",
CreateRoomDialog,
{
defaultPublic: space.getJoinRule() === "public",
parentSpace: space,
},
);
const [shouldCreate, opts] = await modal.finished;
if (shouldCreate) {
await createRoom(opts);
}
};