Merge branch 'develop' into travis/welcome-login
This commit is contained in:
commit
25485edb3f
493 changed files with 13939 additions and 3387 deletions
|
@ -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});
|
||||
});
|
||||
}
|
|
@ -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."),
|
||||
},
|
||||
);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
|
|
|
@ -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]);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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
34
src/utils/sets.ts
Normal 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
81
src/utils/space.ts
Normal 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);
|
||||
}
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue