Merge pull request #2671 from matrix-org/bwindels/permalinkperf
Improve permalink performance
This commit is contained in:
commit
68ba14909b
13 changed files with 628 additions and 530 deletions
|
@ -525,6 +525,7 @@ module.exports = React.createClass({
|
||||||
eventSendStatus={mxEv.status}
|
eventSendStatus={mxEv.status}
|
||||||
tileShape={this.props.tileShape}
|
tileShape={this.props.tileShape}
|
||||||
isTwelveHour={this.props.isTwelveHour}
|
isTwelveHour={this.props.isTwelveHour}
|
||||||
|
permalinkCreator={this.props.permalinkCreator}
|
||||||
last={last} isSelectedEvent={highlight} />
|
last={last} isSelectedEvent={highlight} />
|
||||||
</li>,
|
</li>,
|
||||||
);
|
);
|
||||||
|
|
|
@ -30,6 +30,7 @@ import Promise from 'bluebird';
|
||||||
import filesize from 'filesize';
|
import filesize from 'filesize';
|
||||||
const classNames = require("classnames");
|
const classNames = require("classnames");
|
||||||
import { _t } from '../../languageHandler';
|
import { _t } from '../../languageHandler';
|
||||||
|
import {RoomPermalinkCreator} from "../../matrix-to";
|
||||||
|
|
||||||
const MatrixClientPeg = require("../../MatrixClientPeg");
|
const MatrixClientPeg = require("../../MatrixClientPeg");
|
||||||
const ContentMessages = require("../../ContentMessages");
|
const ContentMessages = require("../../ContentMessages");
|
||||||
|
@ -441,6 +442,11 @@ module.exports = React.createClass({
|
||||||
RoomScrollStateStore.setScrollState(this.state.roomId, this._getScrollState());
|
RoomScrollStateStore.setScrollState(this.state.roomId, this._getScrollState());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// stop tracking room changes to format permalinks
|
||||||
|
if (this.state.permalinkCreator) {
|
||||||
|
this.state.permalinkCreator.stop();
|
||||||
|
}
|
||||||
|
|
||||||
if (this.refs.roomView) {
|
if (this.refs.roomView) {
|
||||||
// disconnect the D&D event listeners from the room view. This
|
// disconnect the D&D event listeners from the room view. This
|
||||||
// is really just for hygiene - we're going to be
|
// is really just for hygiene - we're going to be
|
||||||
|
@ -652,6 +658,11 @@ module.exports = React.createClass({
|
||||||
this._loadMembersIfJoined(room);
|
this._loadMembersIfJoined(room);
|
||||||
this._calculateRecommendedVersion(room);
|
this._calculateRecommendedVersion(room);
|
||||||
this._updateE2EStatus(room);
|
this._updateE2EStatus(room);
|
||||||
|
if (!this.state.permalinkCreator) {
|
||||||
|
const permalinkCreator = new RoomPermalinkCreator(room);
|
||||||
|
permalinkCreator.start();
|
||||||
|
this.setState({permalinkCreator});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_calculateRecommendedVersion: async function(room) {
|
_calculateRecommendedVersion: async function(room) {
|
||||||
|
@ -1219,6 +1230,7 @@ module.exports = React.createClass({
|
||||||
searchResult={result}
|
searchResult={result}
|
||||||
searchHighlights={this.state.searchHighlights}
|
searchHighlights={this.state.searchHighlights}
|
||||||
resultLink={resultLink}
|
resultLink={resultLink}
|
||||||
|
permalinkCreator={this.state.permalinkCreator}
|
||||||
onWidgetLoad={onWidgetLoad} />);
|
onWidgetLoad={onWidgetLoad} />);
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
|
@ -1725,6 +1737,7 @@ module.exports = React.createClass({
|
||||||
showApps={this.state.showApps}
|
showApps={this.state.showApps}
|
||||||
uploadAllowed={this.isFileUploadAllowed}
|
uploadAllowed={this.isFileUploadAllowed}
|
||||||
e2eStatus={this.state.e2eStatus}
|
e2eStatus={this.state.e2eStatus}
|
||||||
|
permalinkCreator={this.state.permalinkCreator}
|
||||||
/>;
|
/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1826,6 +1839,7 @@ module.exports = React.createClass({
|
||||||
showUrlPreview = {this.state.showUrlPreview}
|
showUrlPreview = {this.state.showUrlPreview}
|
||||||
className="mx_RoomView_messagePanel"
|
className="mx_RoomView_messagePanel"
|
||||||
membersLoaded={this.state.membersLoaded}
|
membersLoaded={this.state.membersLoaded}
|
||||||
|
permalinkCreator={this.state.permalinkCreator}
|
||||||
/>);
|
/>);
|
||||||
|
|
||||||
let topUnreadMessagesBar = null;
|
let topUnreadMessagesBar = null;
|
||||||
|
|
|
@ -1202,6 +1202,7 @@ var TimelinePanel = React.createClass({
|
||||||
return (
|
return (
|
||||||
<MessagePanel ref="messagePanel"
|
<MessagePanel ref="messagePanel"
|
||||||
room={this.props.timelineSet.room}
|
room={this.props.timelineSet.room}
|
||||||
|
permalinkCreator={this.props.permalinkCreator}
|
||||||
hidden={this.props.hidden}
|
hidden={this.props.hidden}
|
||||||
backPaginating={this.state.backPaginating}
|
backPaginating={this.state.backPaginating}
|
||||||
forwardPaginating={forwardPaginating}
|
forwardPaginating={forwardPaginating}
|
||||||
|
|
|
@ -26,7 +26,6 @@ import { _t } from '../../../languageHandler';
|
||||||
import Modal from '../../../Modal';
|
import Modal from '../../../Modal';
|
||||||
import Resend from '../../../Resend';
|
import Resend from '../../../Resend';
|
||||||
import SettingsStore from '../../../settings/SettingsStore';
|
import SettingsStore from '../../../settings/SettingsStore';
|
||||||
import {makeEventPermalink} from '../../../matrix-to';
|
|
||||||
import { isUrlPermitted } from '../../../HtmlUtils';
|
import { isUrlPermitted } from '../../../HtmlUtils';
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
|
@ -197,6 +196,7 @@ module.exports = React.createClass({
|
||||||
const ShareDialog = sdk.getComponent("dialogs.ShareDialog");
|
const ShareDialog = sdk.getComponent("dialogs.ShareDialog");
|
||||||
Modal.createTrackedDialog('share room message dialog', '', ShareDialog, {
|
Modal.createTrackedDialog('share room message dialog', '', ShareDialog, {
|
||||||
target: this.props.mxEvent,
|
target: this.props.mxEvent,
|
||||||
|
permalinkCreator: this.props.permalinkCreator,
|
||||||
});
|
});
|
||||||
this.closeMenu();
|
this.closeMenu();
|
||||||
},
|
},
|
||||||
|
@ -305,10 +305,17 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let permalink;
|
||||||
|
if (this.props.permalinkCreator) {
|
||||||
|
permalink = this.props.permalinkCreator.forEvent(
|
||||||
|
this.props.mxEvent.getRoomId(),
|
||||||
|
this.props.mxEvent.getId(),
|
||||||
|
);
|
||||||
|
}
|
||||||
// XXX: if we use room ID, we should also include a server where the event can be found (other than in the domain of the event ID)
|
// XXX: if we use room ID, we should also include a server where the event can be found (other than in the domain of the event ID)
|
||||||
const permalinkButton = (
|
const permalinkButton = (
|
||||||
<div className="mx_MessageContextMenu_field">
|
<div className="mx_MessageContextMenu_field">
|
||||||
<a href={makeEventPermalink(mxEvent.getRoomId(), mxEvent.getId())}
|
<a href={permalink}
|
||||||
target="_blank" rel="noopener" onClick={this.onPermalinkClick}>
|
target="_blank" rel="noopener" onClick={this.onPermalinkClick}>
|
||||||
{ mxEvent.isRedacted() || mxEvent.getType() !== 'm.room.message'
|
{ mxEvent.isRedacted() || mxEvent.getType() !== 'm.room.message'
|
||||||
? _t('Share Permalink') : _t('Share Message') }
|
? _t('Share Permalink') : _t('Share Message') }
|
||||||
|
|
|
@ -20,7 +20,7 @@ import {Room, User, Group, RoomMember, MatrixEvent} from 'matrix-js-sdk';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import QRCode from 'qrcode-react';
|
import QRCode from 'qrcode-react';
|
||||||
import {makeEventPermalink, makeGroupPermalink, makeRoomPermalink, makeUserPermalink} from "../../../matrix-to";
|
import {RoomPermalinkCreator, makeGroupPermalink, makeUserPermalink} from "../../../matrix-to";
|
||||||
import * as ContextualMenu from "../../structures/ContextualMenu";
|
import * as ContextualMenu from "../../structures/ContextualMenu";
|
||||||
|
|
||||||
const socials = [
|
const socials = [
|
||||||
|
@ -123,6 +123,14 @@ export default class ShareDialog extends React.Component {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillMount() {
|
||||||
|
if (this.props.target instanceof Room) {
|
||||||
|
const permalinkCreator = new RoomPermalinkCreator(this.props.target);
|
||||||
|
permalinkCreator.load();
|
||||||
|
this.setState({permalinkCreator});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let title;
|
let title;
|
||||||
let matrixToUrl;
|
let matrixToUrl;
|
||||||
|
@ -146,9 +154,9 @@ export default class ShareDialog extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.state.linkSpecificEvent) {
|
if (this.state.linkSpecificEvent) {
|
||||||
matrixToUrl = makeEventPermalink(this.props.target.roomId, events[events.length - 1].getId());
|
matrixToUrl = this.state.permalinkCreator.forEvent(events[events.length - 1].getId());
|
||||||
} else {
|
} else {
|
||||||
matrixToUrl = makeRoomPermalink(this.props.target.roomId);
|
matrixToUrl = this.state.permalinkCreator.forRoom();
|
||||||
}
|
}
|
||||||
} else if (this.props.target instanceof User || this.props.target instanceof RoomMember) {
|
} else if (this.props.target instanceof User || this.props.target instanceof RoomMember) {
|
||||||
title = _t('Share User');
|
title = _t('Share User');
|
||||||
|
@ -169,9 +177,9 @@ export default class ShareDialog extends React.Component {
|
||||||
</div>;
|
</div>;
|
||||||
|
|
||||||
if (this.state.linkSpecificEvent) {
|
if (this.state.linkSpecificEvent) {
|
||||||
matrixToUrl = makeEventPermalink(this.props.target.getRoomId(), this.props.target.getId());
|
matrixToUrl = this.props.permalinkCreator.forEvent(this.props.target.getId());
|
||||||
} else {
|
} else {
|
||||||
matrixToUrl = makeRoomPermalink(this.props.target.getRoomId());
|
matrixToUrl = this.props.permalinkCreator.forRoom();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ import PropTypes from 'prop-types';
|
||||||
import dis from '../../../dispatcher';
|
import dis from '../../../dispatcher';
|
||||||
import {wantsDateSeparator} from '../../../DateUtils';
|
import {wantsDateSeparator} from '../../../DateUtils';
|
||||||
import {MatrixEvent, MatrixClient} from 'matrix-js-sdk';
|
import {MatrixEvent, MatrixClient} from 'matrix-js-sdk';
|
||||||
import {makeEventPermalink, makeUserPermalink} from "../../../matrix-to";
|
import {makeUserPermalink} from "../../../matrix-to";
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
|
|
||||||
// This component does no cycle detection, simply because the only way to make such a cycle would be to
|
// This component does no cycle detection, simply because the only way to make such a cycle would be to
|
||||||
|
@ -32,6 +32,7 @@ export default class ReplyThread extends React.Component {
|
||||||
parentEv: PropTypes.instanceOf(MatrixEvent),
|
parentEv: PropTypes.instanceOf(MatrixEvent),
|
||||||
// called when the ReplyThread contents has changed, including EventTiles thereof
|
// called when the ReplyThread contents has changed, including EventTiles thereof
|
||||||
onWidgetLoad: PropTypes.func.isRequired,
|
onWidgetLoad: PropTypes.func.isRequired,
|
||||||
|
permalinkCreator: PropTypes.object.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
|
@ -85,7 +86,7 @@ export default class ReplyThread extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Part of Replies fallback support
|
// Part of Replies fallback support
|
||||||
static getNestedReplyText(ev) {
|
static getNestedReplyText(ev, permalinkCreator) {
|
||||||
if (!ev) return null;
|
if (!ev) return null;
|
||||||
|
|
||||||
let {body, formatted_body: html} = ev.getContent();
|
let {body, formatted_body: html} = ev.getContent();
|
||||||
|
@ -94,7 +95,7 @@ export default class ReplyThread extends React.Component {
|
||||||
if (html) html = this.stripHTMLReply(html);
|
if (html) html = this.stripHTMLReply(html);
|
||||||
}
|
}
|
||||||
|
|
||||||
const evLink = makeEventPermalink(ev.getRoomId(), ev.getId());
|
const evLink = permalinkCreator.forEvent(ev.getId());
|
||||||
const userLink = makeUserPermalink(ev.getSender());
|
const userLink = makeUserPermalink(ev.getSender());
|
||||||
const mxid = ev.getSender();
|
const mxid = ev.getSender();
|
||||||
|
|
||||||
|
@ -159,11 +160,12 @@ export default class ReplyThread extends React.Component {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static makeThread(parentEv, onWidgetLoad, ref) {
|
static makeThread(parentEv, onWidgetLoad, permalinkCreator, ref) {
|
||||||
if (!ReplyThread.getParentEventId(parentEv)) {
|
if (!ReplyThread.getParentEventId(parentEv)) {
|
||||||
return <div />;
|
return <div />;
|
||||||
}
|
}
|
||||||
return <ReplyThread parentEv={parentEv} onWidgetLoad={onWidgetLoad} ref={ref} />;
|
return <ReplyThread parentEv={parentEv} onWidgetLoad={onWidgetLoad}
|
||||||
|
ref={ref} permalinkCreator={permalinkCreator} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
|
@ -294,6 +296,7 @@ export default class ReplyThread extends React.Component {
|
||||||
<EventTile mxEvent={ev}
|
<EventTile mxEvent={ev}
|
||||||
tileShape="reply"
|
tileShape="reply"
|
||||||
onWidgetLoad={this.props.onWidgetLoad}
|
onWidgetLoad={this.props.onWidgetLoad}
|
||||||
|
permalinkCreator={this.props.permalinkCreator}
|
||||||
isTwelveHour={SettingsStore.getValue("showTwelveHourTimestamps")} />
|
isTwelveHour={SettingsStore.getValue("showTwelveHourTimestamps")} />
|
||||||
</blockquote>;
|
</blockquote>;
|
||||||
});
|
});
|
||||||
|
|
|
@ -18,8 +18,9 @@ import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import dis from '../../../dispatcher';
|
import dis from '../../../dispatcher';
|
||||||
import { makeEventPermalink } from '../../../matrix-to';
|
import { RoomPermalinkCreator } from '../../../matrix-to';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
|
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'RoomCreate',
|
displayName: 'RoomCreate',
|
||||||
|
@ -47,13 +48,17 @@ module.exports = React.createClass({
|
||||||
if (predecessor === undefined) {
|
if (predecessor === undefined) {
|
||||||
return <div />; // We should never have been instaniated in this case
|
return <div />; // We should never have been instaniated in this case
|
||||||
}
|
}
|
||||||
|
const prevRoom = MatrixClientPeg.get().getRoom(predecessor['room_id']);
|
||||||
|
const permalinkCreator = new RoomPermalinkCreator(prevRoom);
|
||||||
|
permalinkCreator.load();
|
||||||
|
const predecessorPermalink = permalinkCreator.forEvent(predecessor['event_id']);
|
||||||
return <div className="mx_CreateEvent">
|
return <div className="mx_CreateEvent">
|
||||||
<img className="mx_CreateEvent_image" src={require("../../../../res/img/room-continuation.svg")} />
|
<img className="mx_CreateEvent_image" src={require("../../../../res/img/room-continuation.svg")} />
|
||||||
<div className="mx_CreateEvent_header">
|
<div className="mx_CreateEvent_header">
|
||||||
{_t("This room is a continuation of another conversation.")}
|
{_t("This room is a continuation of another conversation.")}
|
||||||
</div>
|
</div>
|
||||||
<a className="mx_CreateEvent_link"
|
<a className="mx_CreateEvent_link"
|
||||||
href={makeEventPermalink(predecessor['room_id'], predecessor['event_id'])}
|
href={predecessorPermalink}
|
||||||
onClick={this._onLinkClicked}
|
onClick={this._onLinkClicked}
|
||||||
>
|
>
|
||||||
{_t("Click here to see older messages.")}
|
{_t("Click here to see older messages.")}
|
||||||
|
|
|
@ -32,7 +32,6 @@ import withMatrixClient from '../../../wrappers/withMatrixClient';
|
||||||
|
|
||||||
const ContextualMenu = require('../../structures/ContextualMenu');
|
const ContextualMenu = require('../../structures/ContextualMenu');
|
||||||
import dis from '../../../dispatcher';
|
import dis from '../../../dispatcher';
|
||||||
import {makeEventPermalink} from "../../../matrix-to";
|
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
import {EventStatus} from 'matrix-js-sdk';
|
import {EventStatus} from 'matrix-js-sdk';
|
||||||
|
|
||||||
|
@ -329,6 +328,7 @@ module.exports = withMatrixClient(React.createClass({
|
||||||
mxEvent: this.props.mxEvent,
|
mxEvent: this.props.mxEvent,
|
||||||
left: x,
|
left: x,
|
||||||
top: y,
|
top: y,
|
||||||
|
permalinkCreator: this.props.permalinkCreator,
|
||||||
eventTileOps: tile && tile.getEventTileOps ? tile.getEventTileOps() : undefined,
|
eventTileOps: tile && tile.getEventTileOps ? tile.getEventTileOps() : undefined,
|
||||||
collapseReplyThread: replyThread && replyThread.canCollapse() ? replyThread.collapse : undefined,
|
collapseReplyThread: replyThread && replyThread.canCollapse() ? replyThread.collapse : undefined,
|
||||||
e2eInfoCallback: e2eInfoCallback,
|
e2eInfoCallback: e2eInfoCallback,
|
||||||
|
@ -544,7 +544,10 @@ module.exports = withMatrixClient(React.createClass({
|
||||||
mx_EventTile_redacted: isRedacted,
|
mx_EventTile_redacted: isRedacted,
|
||||||
});
|
});
|
||||||
|
|
||||||
const permalink = makeEventPermalink(this.props.mxEvent.getRoomId(), this.props.mxEvent.getId());
|
let permalink = "#";
|
||||||
|
if (this.props.permalinkCreator) {
|
||||||
|
permalink = this.props.permalinkCreator.forEvent(this.props.mxEvent.getId());
|
||||||
|
}
|
||||||
|
|
||||||
const readAvatars = this.getReadAvatars();
|
const readAvatars = this.getReadAvatars();
|
||||||
|
|
||||||
|
@ -697,6 +700,15 @@ module.exports = withMatrixClient(React.createClass({
|
||||||
|
|
||||||
case 'reply':
|
case 'reply':
|
||||||
case 'reply_preview': {
|
case 'reply_preview': {
|
||||||
|
let thread;
|
||||||
|
if (this.props.tileShape === 'reply_preview') {
|
||||||
|
thread = ReplyThread.makeThread(
|
||||||
|
this.props.mxEvent,
|
||||||
|
this.props.onWidgetLoad,
|
||||||
|
this.props.permalinkCreator,
|
||||||
|
'replyThread',
|
||||||
|
);
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<div className={classes}>
|
<div className={classes}>
|
||||||
{ avatar }
|
{ avatar }
|
||||||
|
@ -706,10 +718,7 @@ module.exports = withMatrixClient(React.createClass({
|
||||||
{ timestamp }
|
{ timestamp }
|
||||||
</a>
|
</a>
|
||||||
{ this._renderE2EPadlock() }
|
{ this._renderE2EPadlock() }
|
||||||
{
|
{ thread }
|
||||||
this.props.tileShape === 'reply_preview'
|
|
||||||
&& ReplyThread.makeThread(this.props.mxEvent, this.props.onWidgetLoad, 'replyThread')
|
|
||||||
}
|
|
||||||
<EventTileType ref="tile"
|
<EventTileType ref="tile"
|
||||||
mxEvent={this.props.mxEvent}
|
mxEvent={this.props.mxEvent}
|
||||||
highlights={this.props.highlights}
|
highlights={this.props.highlights}
|
||||||
|
@ -721,6 +730,12 @@ module.exports = withMatrixClient(React.createClass({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
|
const thread = ReplyThread.makeThread(
|
||||||
|
this.props.mxEvent,
|
||||||
|
this.props.onWidgetLoad,
|
||||||
|
this.props.permalinkCreator,
|
||||||
|
'replyThread',
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
<div className={classes}>
|
<div className={classes}>
|
||||||
<div className="mx_EventTile_msgOption">
|
<div className="mx_EventTile_msgOption">
|
||||||
|
@ -732,7 +747,7 @@ module.exports = withMatrixClient(React.createClass({
|
||||||
{ timestamp }
|
{ timestamp }
|
||||||
</a>
|
</a>
|
||||||
{ this._renderE2EPadlock() }
|
{ this._renderE2EPadlock() }
|
||||||
{ ReplyThread.makeThread(this.props.mxEvent, this.props.onWidgetLoad, 'replyThread') }
|
{ thread }
|
||||||
<EventTileType ref="tile"
|
<EventTileType ref="tile"
|
||||||
mxEvent={this.props.mxEvent}
|
mxEvent={this.props.mxEvent}
|
||||||
highlights={this.props.highlights}
|
highlights={this.props.highlights}
|
||||||
|
|
|
@ -415,7 +415,8 @@ export default class MessageComposer extends React.Component {
|
||||||
room={this.props.room}
|
room={this.props.room}
|
||||||
placeholder={placeholderText}
|
placeholder={placeholderText}
|
||||||
onFilesPasted={this.uploadFiles}
|
onFilesPasted={this.uploadFiles}
|
||||||
onInputStateChanged={this.onInputStateChanged} />,
|
onInputStateChanged={this.onInputStateChanged}
|
||||||
|
permalinkCreator={this.props.permalinkCreator} />,
|
||||||
formattingButton,
|
formattingButton,
|
||||||
stickerpickerButton,
|
stickerpickerButton,
|
||||||
uploadButton,
|
uploadButton,
|
||||||
|
|
|
@ -1195,7 +1195,7 @@ export default class MessageComposerInput extends React.Component {
|
||||||
|
|
||||||
// Part of Replies fallback support - prepend the text we're sending
|
// Part of Replies fallback support - prepend the text we're sending
|
||||||
// with the text we're replying to
|
// with the text we're replying to
|
||||||
const nestedReply = ReplyThread.getNestedReplyText(replyingToEv);
|
const nestedReply = ReplyThread.getNestedReplyText(replyingToEv, this.props.permalinkCreator);
|
||||||
if (nestedReply) {
|
if (nestedReply) {
|
||||||
if (content.formatted_body) {
|
if (content.formatted_body) {
|
||||||
content.formatted_body = nestedReply.html + content.formatted_body;
|
content.formatted_body = nestedReply.html + content.formatted_body;
|
||||||
|
|
|
@ -56,6 +56,7 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
if (EventTile.haveTileForEvent(ev)) {
|
if (EventTile.haveTileForEvent(ev)) {
|
||||||
ret.push(<EventTile key={eventId+"+"+j} mxEvent={ev} contextual={contextual} highlights={highlights}
|
ret.push(<EventTile key={eventId+"+"+j} mxEvent={ev} contextual={contextual} highlights={highlights}
|
||||||
|
permalinkCreator={this.props.permalinkCreator}
|
||||||
highlightLink={this.props.resultLink}
|
highlightLink={this.props.resultLink}
|
||||||
onWidgetLoad={this.props.onWidgetLoad} />);
|
onWidgetLoad={this.props.onWidgetLoad} />);
|
||||||
}
|
}
|
||||||
|
|
326
src/matrix-to.js
326
src/matrix-to.js
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2017 New Vector Ltd
|
Copyright 2019 New Vector Ltd
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -25,17 +25,213 @@ export const baseUrl = `https://${host}`;
|
||||||
// to add to permalinks. The servers are appended as ?via=example.org
|
// to add to permalinks. The servers are appended as ?via=example.org
|
||||||
const MAX_SERVER_CANDIDATES = 3;
|
const MAX_SERVER_CANDIDATES = 3;
|
||||||
|
|
||||||
export function makeEventPermalink(roomId, eventId) {
|
|
||||||
const permalinkBase = `${baseUrl}/#/${roomId}/${eventId}`;
|
|
||||||
|
|
||||||
// If the roomId isn't actually a room ID, don't try to list the servers.
|
// Permalinks can have servers appended to them so that the user
|
||||||
// Aliases are already routable, and don't need extra information.
|
// receiving them can have a fighting chance at joining the room.
|
||||||
if (roomId[0] !== '!') return permalinkBase;
|
// These servers are called "candidates" at this point because
|
||||||
|
// it is unclear whether they are going to be useful to actually
|
||||||
|
// join in the future.
|
||||||
|
//
|
||||||
|
// We pick 3 servers based on the following criteria:
|
||||||
|
//
|
||||||
|
// Server 1: The highest power level user in the room, provided
|
||||||
|
// they are at least PL 50. We don't calculate "what is a moderator"
|
||||||
|
// here because it is less relevant for the vast majority of rooms.
|
||||||
|
// We also want to ensure that we get an admin or high-ranking mod
|
||||||
|
// as they are less likely to leave the room. If no user happens
|
||||||
|
// to meet this criteria, we'll pick the most popular server in the
|
||||||
|
// room.
|
||||||
|
//
|
||||||
|
// Server 2: The next most popular server in the room (in user
|
||||||
|
// distribution). This cannot be the same as Server 1. If no other
|
||||||
|
// servers are available then we'll only return Server 1.
|
||||||
|
//
|
||||||
|
// Server 3: The next most popular server by user distribution. This
|
||||||
|
// has the same rules as Server 2, with the added exception that it
|
||||||
|
// must be unique from Server 1 and 2.
|
||||||
|
|
||||||
const serverCandidates = pickServerCandidates(roomId);
|
// Rationale for popular servers: It's hard to get rid of people when
|
||||||
return `${permalinkBase}${encodeServerCandidates(serverCandidates)}`;
|
// they keep flocking in from a particular server. Sure, the server could
|
||||||
|
// be ACL'd in the future or for some reason be evicted from the room
|
||||||
|
// however an event like that is unlikely the larger the room gets. If
|
||||||
|
// the server is ACL'd at the time of generating the link however, we
|
||||||
|
// shouldn't pick them. We also don't pick IP addresses.
|
||||||
|
|
||||||
|
// Note: we don't pick the server the room was created on because the
|
||||||
|
// homeserver should already be using that server as a last ditch attempt
|
||||||
|
// and there's less of a guarantee that the server is a resident server.
|
||||||
|
// Instead, we actively figure out which servers are likely to be residents
|
||||||
|
// in the future and try to use those.
|
||||||
|
|
||||||
|
// Note: Users receiving permalinks that happen to have all 3 potential
|
||||||
|
// servers fail them (in terms of joining) are somewhat expected to hunt
|
||||||
|
// down the person who gave them the link to ask for a participating server.
|
||||||
|
// The receiving user can then manually append the known-good server to
|
||||||
|
// the list and magically have the link work.
|
||||||
|
|
||||||
|
export class RoomPermalinkCreator {
|
||||||
|
constructor(room) {
|
||||||
|
this._room = room;
|
||||||
|
this._highestPlUserId = null;
|
||||||
|
this._populationMap = null;
|
||||||
|
this._bannedHostsRegexps = null;
|
||||||
|
this._allowedHostsRegexps = null;
|
||||||
|
this._serverCandidates = null;
|
||||||
|
|
||||||
|
this.onMembership = this.onMembership.bind(this);
|
||||||
|
this.onRoomState = this.onRoomState.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
load() {
|
||||||
|
this._updateAllowedServers();
|
||||||
|
this._updateHighestPlUser();
|
||||||
|
this._updatePopulationMap();
|
||||||
|
this._updateServerCandidates();
|
||||||
|
}
|
||||||
|
|
||||||
|
start() {
|
||||||
|
this.load();
|
||||||
|
this._room.on("RoomMember.membership", this.onMembership);
|
||||||
|
this._room.on("RoomState.events", this.onRoomState);
|
||||||
|
}
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
this._room.removeListener("RoomMember.membership", this.onMembership);
|
||||||
|
this._room.removeListener("RoomState.events", this.onRoomState);
|
||||||
|
}
|
||||||
|
|
||||||
|
forEvent(eventId) {
|
||||||
|
const roomId = this._room.roomId;
|
||||||
|
const permalinkBase = `${baseUrl}/#/${roomId}/${eventId}`;
|
||||||
|
return `${permalinkBase}${encodeServerCandidates(this._serverCandidates)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
forRoom() {
|
||||||
|
const roomId = this._room.roomId;
|
||||||
|
const permalinkBase = `${baseUrl}/#/${roomId}`;
|
||||||
|
return `${permalinkBase}${encodeServerCandidates(this._serverCandidates)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
onRoomState(event) {
|
||||||
|
switch (event.getType()) {
|
||||||
|
case "m.room.server_acl":
|
||||||
|
this._updateAllowedServers();
|
||||||
|
this._updateHighestPlUser();
|
||||||
|
this._updatePopulationMap();
|
||||||
|
this._updateServerCandidates();
|
||||||
|
return;
|
||||||
|
case "m.room.power_levels":
|
||||||
|
this._updateHighestPlUser();
|
||||||
|
this._updateServerCandidates();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMembership(evt, member, oldMembership) {
|
||||||
|
const userId = member.userId;
|
||||||
|
const membership = member.membership;
|
||||||
|
const serverName = getServerName(userId);
|
||||||
|
const hasJoined = oldMembership !== "join" && membership === "join";
|
||||||
|
const hasLeft = oldMembership === "join" && membership !== "join";
|
||||||
|
|
||||||
|
if (hasLeft) {
|
||||||
|
this._populationMap[serverName]--;
|
||||||
|
} else if (hasJoined) {
|
||||||
|
this._populationMap[serverName]++;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._updateHighestPlUser();
|
||||||
|
this._updateServerCandidates();
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateHighestPlUser() {
|
||||||
|
const plEvent = this._room.currentState.getStateEvents("m.room.power_levels", "");
|
||||||
|
if (plEvent) {
|
||||||
|
const content = plEvent.getContent();
|
||||||
|
if (content) {
|
||||||
|
const users = content.users;
|
||||||
|
if (users) {
|
||||||
|
const entries = Object.entries(users);
|
||||||
|
const allowedEntries = entries.filter(([userId]) => {
|
||||||
|
const member = this._room.getMember(userId);
|
||||||
|
if (!member || member.membership !== "join") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const serverName = getServerName(userId);
|
||||||
|
return !isHostnameIpAddress(serverName) &&
|
||||||
|
!isHostInRegex(serverName, this._bannedHostsRegexps) &&
|
||||||
|
isHostInRegex(serverName, this._allowedHostsRegexps);
|
||||||
|
});
|
||||||
|
const maxEntry = allowedEntries.reduce((max, entry) => {
|
||||||
|
return (entry[1] > max[1]) ? entry : max;
|
||||||
|
}, [null, 0]);
|
||||||
|
const [userId, powerLevel] = maxEntry;
|
||||||
|
// object wasn't empty, and max entry wasn't a demotion from the default
|
||||||
|
if (userId !== null && powerLevel >= 50) {
|
||||||
|
this._highestPlUserId = userId;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this._highestPlUserId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateAllowedServers() {
|
||||||
|
const bannedHostsRegexps = [];
|
||||||
|
let allowedHostsRegexps = [new RegExp(".*")]; // default allow everyone
|
||||||
|
if (this._room.currentState) {
|
||||||
|
const aclEvent = this._room.currentState.getStateEvents("m.room.server_acl", "");
|
||||||
|
if (aclEvent && aclEvent.getContent()) {
|
||||||
|
const getRegex = (hostname) => new RegExp("^" + utils.globToRegexp(hostname, false) + "$");
|
||||||
|
|
||||||
|
const denied = aclEvent.getContent().deny || [];
|
||||||
|
denied.forEach(h => bannedHostsRegexps.push(getRegex(h)));
|
||||||
|
|
||||||
|
const allowed = aclEvent.getContent().allow || [];
|
||||||
|
allowedHostsRegexps = []; // we don't want to use the default rule here
|
||||||
|
allowed.forEach(h => allowedHostsRegexps.push(getRegex(h)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this._bannedHostsRegexps = bannedHostsRegexps;
|
||||||
|
this._allowedHostsRegexps = allowedHostsRegexps;
|
||||||
|
}
|
||||||
|
|
||||||
|
_updatePopulationMap() {
|
||||||
|
const populationMap: {[server:string]:number} = {};
|
||||||
|
for (const member of this._room.getJoinedMembers()) {
|
||||||
|
const serverName = getServerName(member.userId);
|
||||||
|
if (!populationMap[serverName]) {
|
||||||
|
populationMap[serverName] = 0;
|
||||||
|
}
|
||||||
|
populationMap[serverName]++;
|
||||||
|
}
|
||||||
|
this._populationMap = populationMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateServerCandidates() {
|
||||||
|
let candidates = [];
|
||||||
|
if (this._highestPlUserId) {
|
||||||
|
candidates.push(getServerName(this._highestPlUserId));
|
||||||
|
}
|
||||||
|
|
||||||
|
const serversByPopulation = Object.keys(this._populationMap)
|
||||||
|
.sort((a, b) => this._populationMap[b] - this._populationMap[a])
|
||||||
|
.filter(a => {
|
||||||
|
return !candidates.includes(a) &&
|
||||||
|
!isHostnameIpAddress(a) &&
|
||||||
|
!isHostInRegex(a, this._bannedHostsRegexps) &&
|
||||||
|
isHostInRegex(a, this._allowedHostsRegexps);
|
||||||
|
});
|
||||||
|
|
||||||
|
const remainingServers = serversByPopulation.slice(0, MAX_SERVER_CANDIDATES - candidates.length);
|
||||||
|
candidates = candidates.concat(remainingServers);
|
||||||
|
|
||||||
|
this._serverCandidates = candidates;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function makeUserPermalink(userId) {
|
export function makeUserPermalink(userId) {
|
||||||
return `${baseUrl}/#/${userId}`;
|
return `${baseUrl}/#/${userId}`;
|
||||||
}
|
}
|
||||||
|
@ -47,8 +243,14 @@ export function makeRoomPermalink(roomId) {
|
||||||
// Aliases are already routable, and don't need extra information.
|
// Aliases are already routable, and don't need extra information.
|
||||||
if (roomId[0] !== '!') return permalinkBase;
|
if (roomId[0] !== '!') return permalinkBase;
|
||||||
|
|
||||||
const serverCandidates = pickServerCandidates(roomId);
|
const client = MatrixClientPeg.get();
|
||||||
return `${permalinkBase}${encodeServerCandidates(serverCandidates)}`;
|
const room = client.getRoom(roomId);
|
||||||
|
if (!room) {
|
||||||
|
return permalinkBase;
|
||||||
|
}
|
||||||
|
const permalinkCreator = new RoomPermalinkCreator(room);
|
||||||
|
permalinkCreator.load();
|
||||||
|
return permalinkCreator.forRoom();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function makeGroupPermalink(groupId) {
|
export function makeGroupPermalink(groupId) {
|
||||||
|
@ -60,111 +262,13 @@ export function encodeServerCandidates(candidates) {
|
||||||
return `?via=${candidates.map(c => encodeURIComponent(c)).join("&via=")}`;
|
return `?via=${candidates.map(c => encodeURIComponent(c)).join("&via=")}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function pickServerCandidates(roomId) {
|
function getServerName(userId) {
|
||||||
const client = MatrixClientPeg.get();
|
return userId.split(":").splice(1).join(":");
|
||||||
const room = client.getRoom(roomId);
|
|
||||||
if (!room) return [];
|
|
||||||
|
|
||||||
// Permalinks can have servers appended to them so that the user
|
|
||||||
// receiving them can have a fighting chance at joining the room.
|
|
||||||
// These servers are called "candidates" at this point because
|
|
||||||
// it is unclear whether they are going to be useful to actually
|
|
||||||
// join in the future.
|
|
||||||
//
|
|
||||||
// We pick 3 servers based on the following criteria:
|
|
||||||
//
|
|
||||||
// Server 1: The highest power level user in the room, provided
|
|
||||||
// they are at least PL 50. We don't calculate "what is a moderator"
|
|
||||||
// here because it is less relevant for the vast majority of rooms.
|
|
||||||
// We also want to ensure that we get an admin or high-ranking mod
|
|
||||||
// as they are less likely to leave the room. If no user happens
|
|
||||||
// to meet this criteria, we'll pick the most popular server in the
|
|
||||||
// room.
|
|
||||||
//
|
|
||||||
// Server 2: The next most popular server in the room (in user
|
|
||||||
// distribution). This cannot be the same as Server 1. If no other
|
|
||||||
// servers are available then we'll only return Server 1.
|
|
||||||
//
|
|
||||||
// Server 3: The next most popular server by user distribution. This
|
|
||||||
// has the same rules as Server 2, with the added exception that it
|
|
||||||
// must be unique from Server 1 and 2.
|
|
||||||
|
|
||||||
// Rationale for popular servers: It's hard to get rid of people when
|
|
||||||
// they keep flocking in from a particular server. Sure, the server could
|
|
||||||
// be ACL'd in the future or for some reason be evicted from the room
|
|
||||||
// however an event like that is unlikely the larger the room gets. If
|
|
||||||
// the server is ACL'd at the time of generating the link however, we
|
|
||||||
// shouldn't pick them. We also don't pick IP addresses.
|
|
||||||
|
|
||||||
// Note: we don't pick the server the room was created on because the
|
|
||||||
// homeserver should already be using that server as a last ditch attempt
|
|
||||||
// and there's less of a guarantee that the server is a resident server.
|
|
||||||
// Instead, we actively figure out which servers are likely to be residents
|
|
||||||
// in the future and try to use those.
|
|
||||||
|
|
||||||
// Note: Users receiving permalinks that happen to have all 3 potential
|
|
||||||
// servers fail them (in terms of joining) are somewhat expected to hunt
|
|
||||||
// down the person who gave them the link to ask for a participating server.
|
|
||||||
// The receiving user can then manually append the known-good server to
|
|
||||||
// the list and magically have the link work.
|
|
||||||
|
|
||||||
const bannedHostsRegexps = [];
|
|
||||||
let allowedHostsRegexps = [new RegExp(".*")]; // default allow everyone
|
|
||||||
if (room.currentState) {
|
|
||||||
const aclEvent = room.currentState.getStateEvents("m.room.server_acl", "");
|
|
||||||
if (aclEvent && aclEvent.getContent()) {
|
|
||||||
const getRegex = (hostname) => new RegExp("^" + utils.globToRegexp(hostname, false) + "$");
|
|
||||||
|
|
||||||
const denied = aclEvent.getContent().deny || [];
|
|
||||||
denied.forEach(h => bannedHostsRegexps.push(getRegex(h)));
|
|
||||||
|
|
||||||
const allowed = aclEvent.getContent().allow || [];
|
|
||||||
allowedHostsRegexps = []; // we don't want to use the default rule here
|
|
||||||
allowed.forEach(h => allowedHostsRegexps.push(getRegex(h)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const populationMap: {[server:string]:number} = {};
|
|
||||||
const highestPlUser = {userId: null, powerLevel: 0, serverName: null};
|
|
||||||
|
|
||||||
for (const member of room.getJoinedMembers()) {
|
|
||||||
const serverName = member.userId.split(":").splice(1).join(":");
|
|
||||||
if (member.powerLevel > highestPlUser.powerLevel && !isHostnameIpAddress(serverName)
|
|
||||||
&& !isHostInRegex(serverName, bannedHostsRegexps) && isHostInRegex(serverName, allowedHostsRegexps)) {
|
|
||||||
highestPlUser.userId = member.userId;
|
|
||||||
highestPlUser.powerLevel = member.powerLevel;
|
|
||||||
highestPlUser.serverName = serverName;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!populationMap[serverName]) populationMap[serverName] = 0;
|
|
||||||
populationMap[serverName]++;
|
|
||||||
}
|
|
||||||
|
|
||||||
const candidates = [];
|
|
||||||
if (highestPlUser.powerLevel >= 50) candidates.push(highestPlUser.serverName);
|
|
||||||
|
|
||||||
const beforePopulation = candidates.length;
|
|
||||||
const serversByPopulation = Object.keys(populationMap)
|
|
||||||
.sort((a, b) => populationMap[b] - populationMap[a])
|
|
||||||
.filter(a => !candidates.includes(a) && !isHostnameIpAddress(a)
|
|
||||||
&& !isHostInRegex(a, bannedHostsRegexps) && isHostInRegex(a, allowedHostsRegexps));
|
|
||||||
for (let i = beforePopulation; i < MAX_SERVER_CANDIDATES; i++) {
|
|
||||||
const idx = i - beforePopulation;
|
|
||||||
if (idx >= serversByPopulation.length) break;
|
|
||||||
candidates.push(serversByPopulation[idx]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return candidates;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getHostnameFromMatrixDomain(domain) {
|
function getHostnameFromMatrixDomain(domain) {
|
||||||
if (!domain) return null;
|
if (!domain) return null;
|
||||||
|
return new URL(`https://${domain}`).hostname;
|
||||||
// The hostname might have a port, so we convert it to a URL and
|
|
||||||
// split out the real hostname.
|
|
||||||
const parser = document.createElement('a');
|
|
||||||
parser.href = "https://" + domain;
|
|
||||||
return parser.hostname;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function isHostInRegex(hostname, regexps) {
|
function isHostInRegex(hostname, regexps) {
|
||||||
|
|
|
@ -14,14 +14,51 @@ limitations under the License.
|
||||||
import expect from 'expect';
|
import expect from 'expect';
|
||||||
import peg from '../src/MatrixClientPeg';
|
import peg from '../src/MatrixClientPeg';
|
||||||
import {
|
import {
|
||||||
makeEventPermalink,
|
|
||||||
makeGroupPermalink,
|
makeGroupPermalink,
|
||||||
makeRoomPermalink,
|
makeRoomPermalink,
|
||||||
makeUserPermalink,
|
makeUserPermalink,
|
||||||
pickServerCandidates,
|
RoomPermalinkCreator,
|
||||||
} from "../src/matrix-to";
|
} from "../src/matrix-to";
|
||||||
import * as testUtils from "./test-utils";
|
import * as testUtils from "./test-utils";
|
||||||
|
|
||||||
|
function mockRoom(roomId, members, serverACL) {
|
||||||
|
members.forEach(m => m.membership = "join");
|
||||||
|
const powerLevelsUsers = members.reduce((pl, member) => {
|
||||||
|
if (Number.isFinite(member.powerLevel)) {
|
||||||
|
pl[member.userId] = member.powerLevel;
|
||||||
|
}
|
||||||
|
return pl;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
return {
|
||||||
|
roomId,
|
||||||
|
getJoinedMembers: () => members,
|
||||||
|
getMember: (userId) => members.find(m => m.userId === userId),
|
||||||
|
currentState: {
|
||||||
|
getStateEvents: (type, key) => {
|
||||||
|
if (key) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
let content;
|
||||||
|
switch (type) {
|
||||||
|
case "m.room.server_acl":
|
||||||
|
content = serverACL;
|
||||||
|
break;
|
||||||
|
case "m.room.power_levels":
|
||||||
|
content = {users: powerLevelsUsers, users_default: 0};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (content) {
|
||||||
|
return {
|
||||||
|
getContent: () => content,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
describe('matrix-to', function() {
|
describe('matrix-to', function() {
|
||||||
let sandbox;
|
let sandbox;
|
||||||
|
@ -36,444 +73,347 @@ describe('matrix-to', function() {
|
||||||
sandbox.restore();
|
sandbox.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should pick no candidate servers when the room is not found', function() {
|
|
||||||
peg.get().getRoom = () => null;
|
|
||||||
const pickedServers = pickServerCandidates("!somewhere:example.org");
|
|
||||||
expect(pickedServers).toBeTruthy();
|
|
||||||
expect(pickedServers.length).toBe(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should pick no candidate servers when the room has no members', function() {
|
it('should pick no candidate servers when the room has no members', function() {
|
||||||
peg.get().getRoom = () => {
|
const room = mockRoom(null, []);
|
||||||
return {
|
const creator = new RoomPermalinkCreator(room);
|
||||||
getJoinedMembers: () => [],
|
creator.load();
|
||||||
};
|
expect(creator._serverCandidates).toBeTruthy();
|
||||||
};
|
expect(creator._serverCandidates.length).toBe(0);
|
||||||
const pickedServers = pickServerCandidates("!somewhere:example.org");
|
|
||||||
expect(pickedServers).toBeTruthy();
|
|
||||||
expect(pickedServers.length).toBe(0);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should pick a candidate server for the highest power level user in the room', function() {
|
it('should pick a candidate server for the highest power level user in the room', function() {
|
||||||
peg.get().getRoom = () => {
|
const room = mockRoom(null, [
|
||||||
return {
|
{
|
||||||
getJoinedMembers: () => [
|
userId: "@alice:pl_50",
|
||||||
{
|
powerLevel: 50,
|
||||||
userId: "@alice:pl_50",
|
},
|
||||||
powerLevel: 50,
|
{
|
||||||
},
|
userId: "@alice:pl_75",
|
||||||
{
|
powerLevel: 75,
|
||||||
userId: "@alice:pl_75",
|
},
|
||||||
powerLevel: 75,
|
{
|
||||||
},
|
userId: "@alice:pl_95",
|
||||||
{
|
powerLevel: 95,
|
||||||
userId: "@alice:pl_95",
|
},
|
||||||
powerLevel: 95,
|
]);
|
||||||
},
|
const creator = new RoomPermalinkCreator(room);
|
||||||
],
|
creator.load();
|
||||||
};
|
expect(creator._serverCandidates).toBeTruthy();
|
||||||
};
|
expect(creator._serverCandidates.length).toBe(3);
|
||||||
const pickedServers = pickServerCandidates("!somewhere:example.org");
|
expect(creator._serverCandidates[0]).toBe("pl_95");
|
||||||
expect(pickedServers).toBeTruthy();
|
|
||||||
expect(pickedServers.length).toBe(3);
|
|
||||||
expect(pickedServers[0]).toBe("pl_95");
|
|
||||||
// we don't check the 2nd and 3rd servers because that is done by the next test
|
// we don't check the 2nd and 3rd servers because that is done by the next test
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should pick candidate servers based on user population', function() {
|
it('should change candidate server when highest power level user leaves the room', function() {
|
||||||
peg.get().getRoom = () => {
|
const member95 = {
|
||||||
return {
|
userId: "@alice:pl_95",
|
||||||
getJoinedMembers: () => [
|
powerLevel: 95,
|
||||||
{
|
|
||||||
userId: "@alice:first",
|
|
||||||
powerLevel: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
userId: "@bob:first",
|
|
||||||
powerLevel: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
userId: "@charlie:first",
|
|
||||||
powerLevel: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
userId: "@alice:second",
|
|
||||||
powerLevel: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
userId: "@bob:second",
|
|
||||||
powerLevel: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
userId: "@charlie:third",
|
|
||||||
powerLevel: 0,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
const pickedServers = pickServerCandidates("!somewhere:example.org");
|
const room = mockRoom(null, [
|
||||||
expect(pickedServers).toBeTruthy();
|
{
|
||||||
expect(pickedServers.length).toBe(3);
|
userId: "@alice:pl_50",
|
||||||
expect(pickedServers[0]).toBe("first");
|
powerLevel: 50,
|
||||||
expect(pickedServers[1]).toBe("second");
|
},
|
||||||
expect(pickedServers[2]).toBe("third");
|
{
|
||||||
|
userId: "@alice:pl_75",
|
||||||
|
powerLevel: 75,
|
||||||
|
},
|
||||||
|
member95,
|
||||||
|
]);
|
||||||
|
const creator = new RoomPermalinkCreator(room);
|
||||||
|
creator.load();
|
||||||
|
expect(creator._serverCandidates[0]).toBe("pl_95");
|
||||||
|
member95.membership = "left";
|
||||||
|
creator.onMembership({}, member95, "join");
|
||||||
|
expect(creator._serverCandidates[0]).toBe("pl_75");
|
||||||
|
member95.membership = "join";
|
||||||
|
creator.onMembership({}, member95, "left");
|
||||||
|
expect(creator._serverCandidates[0]).toBe("pl_95");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pick candidate servers based on user population', function() {
|
||||||
|
const room = mockRoom(null, [
|
||||||
|
{
|
||||||
|
userId: "@alice:first",
|
||||||
|
powerLevel: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
userId: "@bob:first",
|
||||||
|
powerLevel: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
userId: "@charlie:first",
|
||||||
|
powerLevel: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
userId: "@alice:second",
|
||||||
|
powerLevel: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
userId: "@bob:second",
|
||||||
|
powerLevel: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
userId: "@charlie:third",
|
||||||
|
powerLevel: 0,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
const creator = new RoomPermalinkCreator(room);
|
||||||
|
creator.load();
|
||||||
|
expect(creator._serverCandidates).toBeTruthy();
|
||||||
|
expect(creator._serverCandidates.length).toBe(3);
|
||||||
|
expect(creator._serverCandidates[0]).toBe("first");
|
||||||
|
expect(creator._serverCandidates[1]).toBe("second");
|
||||||
|
expect(creator._serverCandidates[2]).toBe("third");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should pick prefer candidate servers with higher power levels', function() {
|
it('should pick prefer candidate servers with higher power levels', function() {
|
||||||
peg.get().getRoom = () => {
|
const room = mockRoom(null, [
|
||||||
return {
|
{
|
||||||
getJoinedMembers: () => [
|
userId: "@alice:first",
|
||||||
{
|
powerLevel: 100,
|
||||||
userId: "@alice:first",
|
},
|
||||||
powerLevel: 100,
|
{
|
||||||
},
|
userId: "@alice:second",
|
||||||
{
|
powerLevel: 0,
|
||||||
userId: "@alice:second",
|
},
|
||||||
powerLevel: 0,
|
{
|
||||||
},
|
userId: "@bob:second",
|
||||||
{
|
powerLevel: 0,
|
||||||
userId: "@bob:second",
|
},
|
||||||
powerLevel: 0,
|
{
|
||||||
},
|
userId: "@charlie:third",
|
||||||
{
|
powerLevel: 0,
|
||||||
userId: "@charlie:third",
|
},
|
||||||
powerLevel: 0,
|
]);
|
||||||
},
|
const creator = new RoomPermalinkCreator(room);
|
||||||
],
|
creator.load();
|
||||||
};
|
expect(creator._serverCandidates.length).toBe(3);
|
||||||
};
|
expect(creator._serverCandidates[0]).toBe("first");
|
||||||
const pickedServers = pickServerCandidates("!somewhere:example.org");
|
expect(creator._serverCandidates[1]).toBe("second");
|
||||||
expect(pickedServers).toBeTruthy();
|
expect(creator._serverCandidates[2]).toBe("third");
|
||||||
expect(pickedServers.length).toBe(3);
|
|
||||||
expect(pickedServers[0]).toBe("first");
|
|
||||||
expect(pickedServers[1]).toBe("second");
|
|
||||||
expect(pickedServers[2]).toBe("third");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should pick a maximum of 3 candidate servers', function() {
|
it('should pick a maximum of 3 candidate servers', function() {
|
||||||
peg.get().getRoom = () => {
|
const room = mockRoom(null, [
|
||||||
return {
|
{
|
||||||
getJoinedMembers: () => [
|
userId: "@alice:alpha",
|
||||||
{
|
powerLevel: 100,
|
||||||
userId: "@alice:alpha",
|
},
|
||||||
powerLevel: 100,
|
{
|
||||||
},
|
userId: "@alice:bravo",
|
||||||
{
|
powerLevel: 0,
|
||||||
userId: "@alice:bravo",
|
},
|
||||||
powerLevel: 0,
|
{
|
||||||
},
|
userId: "@alice:charlie",
|
||||||
{
|
powerLevel: 0,
|
||||||
userId: "@alice:charlie",
|
},
|
||||||
powerLevel: 0,
|
{
|
||||||
},
|
userId: "@alice:delta",
|
||||||
{
|
powerLevel: 0,
|
||||||
userId: "@alice:delta",
|
},
|
||||||
powerLevel: 0,
|
{
|
||||||
},
|
userId: "@alice:echo",
|
||||||
{
|
powerLevel: 0,
|
||||||
userId: "@alice:echo",
|
},
|
||||||
powerLevel: 0,
|
]);
|
||||||
},
|
const creator = new RoomPermalinkCreator(room);
|
||||||
],
|
creator.load();
|
||||||
};
|
expect(creator._serverCandidates).toBeTruthy();
|
||||||
};
|
expect(creator._serverCandidates.length).toBe(3);
|
||||||
const pickedServers = pickServerCandidates("!somewhere:example.org");
|
|
||||||
expect(pickedServers).toBeTruthy();
|
|
||||||
expect(pickedServers.length).toBe(3);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not consider IPv4 hosts', function() {
|
it('should not consider IPv4 hosts', function() {
|
||||||
peg.get().getRoom = () => {
|
const room = mockRoom(null, [
|
||||||
return {
|
{
|
||||||
getJoinedMembers: () => [
|
userId: "@alice:127.0.0.1",
|
||||||
{
|
powerLevel: 100,
|
||||||
userId: "@alice:127.0.0.1",
|
},
|
||||||
powerLevel: 100,
|
]);
|
||||||
},
|
const creator = new RoomPermalinkCreator(room);
|
||||||
],
|
creator.load();
|
||||||
};
|
expect(creator._serverCandidates).toBeTruthy();
|
||||||
};
|
expect(creator._serverCandidates.length).toBe(0);
|
||||||
const pickedServers = pickServerCandidates("!somewhere:example.org");
|
|
||||||
expect(pickedServers).toBeTruthy();
|
|
||||||
expect(pickedServers.length).toBe(0);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not consider IPv6 hosts', function() {
|
it('should not consider IPv6 hosts', function() {
|
||||||
peg.get().getRoom = () => {
|
const room = mockRoom(null, [
|
||||||
return {
|
{
|
||||||
getJoinedMembers: () => [
|
userId: "@alice:[::1]",
|
||||||
{
|
powerLevel: 100,
|
||||||
userId: "@alice:[::1]",
|
},
|
||||||
powerLevel: 100,
|
]);
|
||||||
},
|
const creator = new RoomPermalinkCreator(room);
|
||||||
],
|
creator.load();
|
||||||
};
|
expect(creator._serverCandidates).toBeTruthy();
|
||||||
};
|
expect(creator._serverCandidates.length).toBe(0);
|
||||||
const pickedServers = pickServerCandidates("!somewhere:example.org");
|
|
||||||
expect(pickedServers).toBeTruthy();
|
|
||||||
expect(pickedServers.length).toBe(0);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not consider IPv4 hostnames with ports', function() {
|
it('should not consider IPv4 hostnames with ports', function() {
|
||||||
peg.get().getRoom = () => {
|
const room = mockRoom(null, [
|
||||||
return {
|
{
|
||||||
getJoinedMembers: () => [
|
userId: "@alice:127.0.0.1:8448",
|
||||||
{
|
powerLevel: 100,
|
||||||
userId: "@alice:127.0.0.1:8448",
|
},
|
||||||
powerLevel: 100,
|
]);
|
||||||
},
|
const creator = new RoomPermalinkCreator(room);
|
||||||
],
|
creator.load();
|
||||||
};
|
expect(creator._serverCandidates).toBeTruthy();
|
||||||
};
|
expect(creator._serverCandidates.length).toBe(0);
|
||||||
const pickedServers = pickServerCandidates("!somewhere:example.org");
|
|
||||||
expect(pickedServers).toBeTruthy();
|
|
||||||
expect(pickedServers.length).toBe(0);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not consider IPv6 hostnames with ports', function() {
|
it('should not consider IPv6 hostnames with ports', function() {
|
||||||
peg.get().getRoom = () => {
|
const room = mockRoom(null, [
|
||||||
return {
|
{
|
||||||
getJoinedMembers: () => [
|
userId: "@alice:[::1]:8448",
|
||||||
{
|
powerLevel: 100,
|
||||||
userId: "@alice:[::1]:8448",
|
},
|
||||||
powerLevel: 100,
|
]);
|
||||||
},
|
const creator = new RoomPermalinkCreator(room);
|
||||||
],
|
creator.load();
|
||||||
};
|
expect(creator._serverCandidates).toBeTruthy();
|
||||||
};
|
expect(creator._serverCandidates.length).toBe(0);
|
||||||
const pickedServers = pickServerCandidates("!somewhere:example.org");
|
|
||||||
expect(pickedServers).toBeTruthy();
|
|
||||||
expect(pickedServers.length).toBe(0);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should work with hostnames with ports', function() {
|
it('should work with hostnames with ports', function() {
|
||||||
peg.get().getRoom = () => {
|
const room = mockRoom(null, [
|
||||||
return {
|
{
|
||||||
getJoinedMembers: () => [
|
userId: "@alice:example.org:8448",
|
||||||
{
|
powerLevel: 100,
|
||||||
userId: "@alice:example.org:8448",
|
},
|
||||||
powerLevel: 100,
|
]);
|
||||||
},
|
|
||||||
],
|
const creator = new RoomPermalinkCreator(room);
|
||||||
};
|
creator.load();
|
||||||
};
|
expect(creator._serverCandidates).toBeTruthy();
|
||||||
const pickedServers = pickServerCandidates("!somewhere:example.org");
|
expect(creator._serverCandidates.length).toBe(1);
|
||||||
expect(pickedServers).toBeTruthy();
|
expect(creator._serverCandidates[0]).toBe("example.org:8448");
|
||||||
expect(pickedServers.length).toBe(1);
|
|
||||||
expect(pickedServers[0]).toBe("example.org:8448");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not consider servers explicitly denied by ACLs', function() {
|
it('should not consider servers explicitly denied by ACLs', function() {
|
||||||
peg.get().getRoom = () => {
|
const room = mockRoom(null, [
|
||||||
return {
|
{
|
||||||
getJoinedMembers: () => [
|
userId: "@alice:evilcorp.com",
|
||||||
{
|
powerLevel: 100,
|
||||||
userId: "@alice:evilcorp.com",
|
},
|
||||||
powerLevel: 100,
|
{
|
||||||
},
|
userId: "@bob:chat.evilcorp.com",
|
||||||
{
|
powerLevel: 0,
|
||||||
userId: "@bob:chat.evilcorp.com",
|
},
|
||||||
powerLevel: 0,
|
], {
|
||||||
},
|
deny: ["evilcorp.com", "*.evilcorp.com"],
|
||||||
],
|
allow: ["*"],
|
||||||
currentState: {
|
});
|
||||||
getStateEvents: (type, key) => {
|
const creator = new RoomPermalinkCreator(room);
|
||||||
if (type !== "m.room.server_acl" || key !== "") return null;
|
creator.load();
|
||||||
return {
|
expect(creator._serverCandidates).toBeTruthy();
|
||||||
getContent: () => {
|
expect(creator._serverCandidates.length).toBe(0);
|
||||||
return {
|
|
||||||
deny: ["evilcorp.com", "*.evilcorp.com"],
|
|
||||||
allow: ["*"],
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
const pickedServers = pickServerCandidates("!somewhere:example.org");
|
|
||||||
expect(pickedServers).toBeTruthy();
|
|
||||||
expect(pickedServers.length).toBe(0);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not consider servers not allowed by ACLs', function() {
|
it('should not consider servers not allowed by ACLs', function() {
|
||||||
peg.get().getRoom = () => {
|
const room = mockRoom(null, [
|
||||||
return {
|
{
|
||||||
getJoinedMembers: () => [
|
userId: "@alice:evilcorp.com",
|
||||||
{
|
powerLevel: 100,
|
||||||
userId: "@alice:evilcorp.com",
|
},
|
||||||
powerLevel: 100,
|
{
|
||||||
},
|
userId: "@bob:chat.evilcorp.com",
|
||||||
{
|
powerLevel: 0,
|
||||||
userId: "@bob:chat.evilcorp.com",
|
},
|
||||||
powerLevel: 0,
|
], {
|
||||||
},
|
deny: [],
|
||||||
],
|
allow: [], // implies "ban everyone"
|
||||||
currentState: {
|
});
|
||||||
getStateEvents: (type, key) => {
|
const creator = new RoomPermalinkCreator(room);
|
||||||
if (type !== "m.room.server_acl" || key !== "") return null;
|
creator.load();
|
||||||
return {
|
expect(creator._serverCandidates).toBeTruthy();
|
||||||
getContent: () => {
|
expect(creator._serverCandidates.length).toBe(0);
|
||||||
return {
|
|
||||||
deny: [],
|
|
||||||
allow: [], // implies "ban everyone"
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
const pickedServers = pickServerCandidates("!somewhere:example.org");
|
|
||||||
expect(pickedServers).toBeTruthy();
|
|
||||||
expect(pickedServers.length).toBe(0);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should consider servers not explicitly banned by ACLs', function() {
|
it('should consider servers not explicitly banned by ACLs', function() {
|
||||||
peg.get().getRoom = () => {
|
const room = mockRoom(null, [
|
||||||
return {
|
{
|
||||||
getJoinedMembers: () => [
|
userId: "@alice:evilcorp.com",
|
||||||
{
|
powerLevel: 100,
|
||||||
userId: "@alice:evilcorp.com",
|
},
|
||||||
powerLevel: 100,
|
{
|
||||||
},
|
userId: "@bob:chat.evilcorp.com",
|
||||||
{
|
powerLevel: 0,
|
||||||
userId: "@bob:chat.evilcorp.com",
|
},
|
||||||
powerLevel: 0,
|
], {
|
||||||
},
|
deny: ["*.evilcorp.com"], // evilcorp.com is still good though
|
||||||
],
|
allow: ["*"],
|
||||||
currentState: {
|
});
|
||||||
getStateEvents: (type, key) => {
|
const creator = new RoomPermalinkCreator(room);
|
||||||
if (type !== "m.room.server_acl" || key !== "") return null;
|
creator.load();
|
||||||
return {
|
expect(creator._serverCandidates).toBeTruthy();
|
||||||
getContent: () => {
|
expect(creator._serverCandidates.length).toBe(1);
|
||||||
return {
|
expect(creator._serverCandidates[0]).toEqual("evilcorp.com");
|
||||||
deny: ["*.evilcorp.com"], // evilcorp.com is still good though
|
|
||||||
allow: ["*"],
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
const pickedServers = pickServerCandidates("!somewhere:example.org");
|
|
||||||
expect(pickedServers).toBeTruthy();
|
|
||||||
expect(pickedServers.length).toBe(1);
|
|
||||||
expect(pickedServers[0]).toEqual("evilcorp.com");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should consider servers not disallowed by ACLs', function() {
|
it('should consider servers not disallowed by ACLs', function() {
|
||||||
peg.get().getRoom = () => {
|
const room = mockRoom(null, [
|
||||||
return {
|
{
|
||||||
getJoinedMembers: () => [
|
userId: "@alice:evilcorp.com",
|
||||||
{
|
powerLevel: 100,
|
||||||
userId: "@alice:evilcorp.com",
|
},
|
||||||
powerLevel: 100,
|
{
|
||||||
},
|
userId: "@bob:chat.evilcorp.com",
|
||||||
{
|
powerLevel: 0,
|
||||||
userId: "@bob:chat.evilcorp.com",
|
},
|
||||||
powerLevel: 0,
|
], {
|
||||||
},
|
deny: [],
|
||||||
],
|
allow: ["evilcorp.com"], // implies "ban everyone else"
|
||||||
currentState: {
|
});
|
||||||
getStateEvents: (type, key) => {
|
const creator = new RoomPermalinkCreator(room);
|
||||||
if (type !== "m.room.server_acl" || key !== "") return null;
|
creator.load();
|
||||||
return {
|
expect(creator._serverCandidates).toBeTruthy();
|
||||||
getContent: () => {
|
expect(creator._serverCandidates.length).toBe(1);
|
||||||
return {
|
expect(creator._serverCandidates[0]).toEqual("evilcorp.com");
|
||||||
deny: [],
|
|
||||||
allow: ["evilcorp.com"], // implies "ban everyone else"
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
const pickedServers = pickServerCandidates("!somewhere:example.org");
|
|
||||||
expect(pickedServers).toBeTruthy();
|
|
||||||
expect(pickedServers.length).toBe(1);
|
|
||||||
expect(pickedServers[0]).toEqual("evilcorp.com");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should generate an event permalink for room IDs with no candidate servers', function() {
|
it('should generate an event permalink for room IDs with no candidate servers', function() {
|
||||||
peg.get().getRoom = () => null;
|
const room = mockRoom("!somewhere:example.org", []);
|
||||||
const result = makeEventPermalink("!somewhere:example.org", "$something:example.com");
|
const creator = new RoomPermalinkCreator(room);
|
||||||
|
creator.load();
|
||||||
|
const result = creator.forEvent("$something:example.com");
|
||||||
expect(result).toBe("https://matrix.to/#/!somewhere:example.org/$something:example.com");
|
expect(result).toBe("https://matrix.to/#/!somewhere:example.org/$something:example.com");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should generate an event permalink for room IDs with some candidate servers', function() {
|
it('should generate an event permalink for room IDs with some candidate servers', function() {
|
||||||
peg.get().getRoom = () => {
|
const room = mockRoom("!somewhere:example.org", [
|
||||||
return {
|
{
|
||||||
getJoinedMembers: () => [
|
userId: "@alice:first",
|
||||||
{
|
powerLevel: 100,
|
||||||
userId: "@alice:first",
|
},
|
||||||
powerLevel: 100,
|
{
|
||||||
},
|
userId: "@bob:second",
|
||||||
{
|
powerLevel: 0,
|
||||||
userId: "@bob:second",
|
},
|
||||||
powerLevel: 0,
|
]);
|
||||||
},
|
const creator = new RoomPermalinkCreator(room);
|
||||||
],
|
creator.load();
|
||||||
};
|
const result = creator.forEvent("$something:example.com");
|
||||||
};
|
|
||||||
const result = makeEventPermalink("!somewhere:example.org", "$something:example.com");
|
|
||||||
expect(result).toBe("https://matrix.to/#/!somewhere:example.org/$something:example.com?via=first&via=second");
|
expect(result).toBe("https://matrix.to/#/!somewhere:example.org/$something:example.com?via=first&via=second");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should generate a room permalink for room IDs with no candidate servers', function() {
|
|
||||||
peg.get().getRoom = () => null;
|
|
||||||
const result = makeRoomPermalink("!somewhere:example.org");
|
|
||||||
expect(result).toBe("https://matrix.to/#/!somewhere:example.org");
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should generate a room permalink for room IDs with some candidate servers', function() {
|
it('should generate a room permalink for room IDs with some candidate servers', function() {
|
||||||
peg.get().getRoom = () => {
|
peg.get().getRoom = (roomId) => {
|
||||||
return {
|
return mockRoom(roomId, [
|
||||||
getJoinedMembers: () => [
|
{
|
||||||
{
|
userId: "@alice:first",
|
||||||
userId: "@alice:first",
|
powerLevel: 100,
|
||||||
powerLevel: 100,
|
},
|
||||||
},
|
{
|
||||||
{
|
userId: "@bob:second",
|
||||||
userId: "@bob:second",
|
powerLevel: 0,
|
||||||
powerLevel: 0,
|
},
|
||||||
},
|
]);
|
||||||
],
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
const result = makeRoomPermalink("!somewhere:example.org");
|
const result = makeRoomPermalink("!somewhere:example.org");
|
||||||
expect(result).toBe("https://matrix.to/#/!somewhere:example.org?via=first&via=second");
|
expect(result).toBe("https://matrix.to/#/!somewhere:example.org?via=first&via=second");
|
||||||
});
|
});
|
||||||
|
|
||||||
// Technically disallowed but we'll test it anyways
|
|
||||||
it('should generate an event permalink for room aliases with no candidate servers', function() {
|
|
||||||
peg.get().getRoom = () => null;
|
|
||||||
const result = makeEventPermalink("#somewhere:example.org", "$something:example.com");
|
|
||||||
expect(result).toBe("https://matrix.to/#/#somewhere:example.org/$something:example.com");
|
|
||||||
});
|
|
||||||
|
|
||||||
// Technically disallowed but we'll test it anyways
|
|
||||||
it('should generate an event permalink for room aliases without candidate servers', function() {
|
|
||||||
peg.get().getRoom = () => {
|
|
||||||
return {
|
|
||||||
getJoinedMembers: () => [
|
|
||||||
{
|
|
||||||
userId: "@alice:first",
|
|
||||||
powerLevel: 100,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
userId: "@bob:second",
|
|
||||||
powerLevel: 0,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
};
|
|
||||||
const result = makeEventPermalink("#somewhere:example.org", "$something:example.com");
|
|
||||||
expect(result).toBe("https://matrix.to/#/#somewhere:example.org/$something:example.com");
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should generate a room permalink for room aliases with no candidate servers', function() {
|
it('should generate a room permalink for room aliases with no candidate servers', function() {
|
||||||
peg.get().getRoom = () => null;
|
peg.get().getRoom = () => null;
|
||||||
const result = makeRoomPermalink("#somewhere:example.org");
|
const result = makeRoomPermalink("#somewhere:example.org");
|
||||||
|
@ -481,19 +421,17 @@ describe('matrix-to', function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should generate a room permalink for room aliases without candidate servers', function() {
|
it('should generate a room permalink for room aliases without candidate servers', function() {
|
||||||
peg.get().getRoom = () => {
|
peg.get().getRoom = (roomId) => {
|
||||||
return {
|
return mockRoom(roomId, [
|
||||||
getJoinedMembers: () => [
|
{
|
||||||
{
|
userId: "@alice:first",
|
||||||
userId: "@alice:first",
|
powerLevel: 100,
|
||||||
powerLevel: 100,
|
},
|
||||||
},
|
{
|
||||||
{
|
userId: "@bob:second",
|
||||||
userId: "@bob:second",
|
powerLevel: 0,
|
||||||
powerLevel: 0,
|
},
|
||||||
},
|
]);
|
||||||
],
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
const result = makeRoomPermalink("#somewhere:example.org");
|
const result = makeRoomPermalink("#somewhere:example.org");
|
||||||
expect(result).toBe("https://matrix.to/#/#somewhere:example.org");
|
expect(result).toBe("https://matrix.to/#/#somewhere:example.org");
|
||||||
|
|
Loading…
Reference in a new issue