matrix-react-sdk
Version:
SDK for matrix.org using React
1,173 lines (961 loc) • 166 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.getHandlerTile = getHandlerTile;
exports.haveTileForEvent = haveTileForEvent;
exports.default = void 0;
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var _react = _interopRequireDefault(require("react"));
var _classnames = _interopRequireDefault(require("classnames"));
var _event = require("matrix-js-sdk/src/@types/event");
var _event2 = require("matrix-js-sdk/src/models/event");
var _ReplyThread = _interopRequireDefault(require("../elements/ReplyThread"));
var _languageHandler = require("../../../languageHandler");
var TextForEvent = _interopRequireWildcard(require("../../../TextForEvent"));
var sdk = _interopRequireWildcard(require("../../../index"));
var _dispatcher = _interopRequireDefault(require("../../../dispatcher/dispatcher"));
var _SettingsStore = _interopRequireDefault(require("../../../settings/SettingsStore"));
var _Layout = require("../../../settings/Layout");
var _DateUtils = require("../../../DateUtils");
var _MatrixClientPeg = require("../../../MatrixClientPeg");
var _BanList = require("../../../mjolnir/BanList");
var _MatrixClientContext = _interopRequireDefault(require("../../../contexts/MatrixClientContext"));
var _E2EIcon = require("./E2EIcon");
var _units = require("../../../utils/units");
var _WidgetType = require("../../../widgets/WidgetType");
var _RoomAvatar = _interopRequireDefault(require("../avatars/RoomAvatar"));
var _WidgetLayoutStore = require("../../../stores/widgets/WidgetLayoutStore");
var _objects = require("../../../utils/objects");
var _replaceableComponent = require("../../../utils/replaceableComponent");
var _Tooltip = _interopRequireDefault(require("../elements/Tooltip"));
var _StaticNotificationState = require("../../../stores/notifications/StaticNotificationState");
var _NotificationBadge = _interopRequireDefault(require("./NotificationBadge"));
var _dec, _class, _class2, _temp;
const eventTileTypes = {
[_event.EventType.RoomMessage]: 'messages.MessageEvent',
[_event.EventType.Sticker]: 'messages.MessageEvent',
[_event.EventType.KeyVerificationCancel]: 'messages.MKeyVerificationConclusion',
[_event.EventType.KeyVerificationDone]: 'messages.MKeyVerificationConclusion',
[_event.EventType.CallInvite]: 'messages.TextualEvent',
[_event.EventType.CallAnswer]: 'messages.TextualEvent',
[_event.EventType.CallHangup]: 'messages.TextualEvent',
[_event.EventType.CallReject]: 'messages.TextualEvent'
};
const stateEventTileTypes = {
[_event.EventType.RoomEncryption]: 'messages.EncryptionEvent',
[_event.EventType.RoomCanonicalAlias]: 'messages.TextualEvent',
[_event.EventType.RoomCreate]: 'messages.RoomCreate',
[_event.EventType.RoomMember]: 'messages.TextualEvent',
[_event.EventType.RoomName]: 'messages.TextualEvent',
[_event.EventType.RoomAvatar]: 'messages.RoomAvatarEvent',
[_event.EventType.RoomThirdPartyInvite]: 'messages.TextualEvent',
[_event.EventType.RoomHistoryVisibility]: 'messages.TextualEvent',
[_event.EventType.RoomTopic]: 'messages.TextualEvent',
[_event.EventType.RoomPowerLevels]: 'messages.TextualEvent',
[_event.EventType.RoomPinnedEvents]: 'messages.TextualEvent',
[_event.EventType.RoomServerAcl]: 'messages.TextualEvent',
// TODO: Enable support for m.widget event type (https://github.com/vector-im/element-web/issues/13111)
'im.vector.modular.widgets': 'messages.TextualEvent',
[_WidgetLayoutStore.WIDGET_LAYOUT_EVENT_TYPE]: 'messages.TextualEvent',
[_event.EventType.RoomTombstone]: 'messages.TextualEvent',
[_event.EventType.RoomJoinRules]: 'messages.TextualEvent',
[_event.EventType.RoomGuestAccess]: 'messages.TextualEvent',
'm.room.related_groups': 'messages.TextualEvent' // legacy communities flair
};
const stateEventSingular = new Set([_event.EventType.RoomEncryption, _event.EventType.RoomCanonicalAlias, _event.EventType.RoomCreate, _event.EventType.RoomName, _event.EventType.RoomAvatar, _event.EventType.RoomHistoryVisibility, _event.EventType.RoomTopic, _event.EventType.RoomPowerLevels, _event.EventType.RoomPinnedEvents, _event.EventType.RoomServerAcl, _WidgetLayoutStore.WIDGET_LAYOUT_EVENT_TYPE, _event.EventType.RoomTombstone, _event.EventType.RoomJoinRules, _event.EventType.RoomGuestAccess, 'm.room.related_groups']); // Add all the Mjolnir stuff to the renderer
for (const evType of _BanList.ALL_RULE_TYPES) {
stateEventTileTypes[evType] = 'messages.TextualEvent';
}
function getHandlerTile(ev) {
const type = ev.getType(); // don't show verification requests we're not involved in,
// not even when showing hidden events
if (type === "m.room.message") {
const content = ev.getContent();
if (content && content.msgtype === "m.key.verification.request") {
const client = _MatrixClientPeg.MatrixClientPeg.get();
const me = client && client.getUserId();
if (ev.getSender() !== me && content.to !== me) {
return undefined;
} else {
return "messages.MKeyVerificationRequest";
}
}
} // these events are sent by both parties during verification, but we only want to render one
// tile once the verification concludes, so filter out the one from the other party.
if (type === "m.key.verification.done") {
const client = _MatrixClientPeg.MatrixClientPeg.get();
const me = client && client.getUserId();
if (ev.getSender() !== me) {
return undefined;
}
} // sometimes MKeyVerificationConclusion declines to render. Jankily decline to render and
// fall back to showing hidden events, if we're viewing hidden events
// XXX: This is extremely a hack. Possibly these components should have an interface for
// declining to render?
if (type === "m.key.verification.cancel" || type === "m.key.verification.done") {
const MKeyVerificationConclusion = sdk.getComponent("messages.MKeyVerificationConclusion");
if (!MKeyVerificationConclusion.prototype._shouldRender.call(null, ev, ev.request)) {
return;
}
} // TODO: Enable support for m.widget event type (https://github.com/vector-im/element-web/issues/13111)
if (type === "im.vector.modular.widgets") {
let type = ev.getContent()['type'];
if (!type) {
// deleted/invalid widget - try the past widget type
type = ev.getPrevContent()['type'];
}
if (_WidgetType.WidgetType.JITSI.matches(type)) {
return "messages.MJitsiWidgetEvent";
}
}
if (ev.isState()) {
if (stateEventSingular.has(type) && ev.getStateKey() !== "") return undefined;
return stateEventTileTypes[type];
}
return eventTileTypes[type];
}
const MAX_READ_AVATARS = 5; // Our component structure for EventTiles on the timeline is:
//
// .-EventTile------------------------------------------------.
// | MemberAvatar (SenderProfile) TimeStamp |
// | .-{Message,Textual}Event---------------. Read Avatars |
// | | .-MFooBody-------------------. | |
// | | | (only if MessageEvent) | | |
// | | '----------------------------' | |
// | '--------------------------------------' |
// '----------------------------------------------------------'
let EventTile = (_dec = (0, _replaceableComponent.replaceableComponent)("views.rooms.EventTile"), _dec(_class = (_temp = _class2 = class EventTile extends _react.default.Component
/*:: <IProps, IState>*/
{
constructor(props, context) {
super(props, context);
(0, _defineProperty2.default)(this, "suppressReadReceiptAnimation", void 0);
(0, _defineProperty2.default)(this, "isListeningForReceipts", void 0);
(0, _defineProperty2.default)(this, "tile", /*#__PURE__*/_react.default.createRef());
(0, _defineProperty2.default)(this, "replyThread", /*#__PURE__*/_react.default.createRef());
(0, _defineProperty2.default)(this, "onRoomReceipt", (ev, room) => {
// ignore events for other rooms
const tileRoom = _MatrixClientPeg.MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId());
if (room !== tileRoom) return;
if (!this.shouldShowSentReceipt && !this.shouldShowSendingReceipt && !this.isListeningForReceipts) {
return;
} // We force update because we have no state or prop changes to queue up, instead relying on
// the getters we use here to determine what needs rendering.
this.forceUpdate(() => {
// Per elsewhere in this file, we can remove the listener once we will have no further purpose for it.
if (!this.shouldShowSentReceipt && !this.shouldShowSendingReceipt) {
this.context.removeListener("Room.receipt", this.onRoomReceipt);
this.isListeningForReceipts = false;
}
});
});
(0, _defineProperty2.default)(this, "onDecrypted", () => {
// we need to re-verify the sending device.
// (we call onHeightChanged in verifyEvent to handle the case where decryption
// has caused a change in size of the event tile)
this.verifyEvent(this.props.mxEvent);
this.forceUpdate();
});
(0, _defineProperty2.default)(this, "onDeviceVerificationChanged", (userId, device) => {
if (userId === this.props.mxEvent.getSender()) {
this.verifyEvent(this.props.mxEvent);
}
});
(0, _defineProperty2.default)(this, "onUserVerificationChanged", (userId, _trustStatus) => {
if (userId === this.props.mxEvent.getSender()) {
this.verifyEvent(this.props.mxEvent);
}
});
(0, _defineProperty2.default)(this, "toggleAllReadAvatars", () => {
this.setState({
allReadAvatars: !this.state.allReadAvatars
});
});
(0, _defineProperty2.default)(this, "onSenderProfileClick", event => {
const mxEvent = this.props.mxEvent;
_dispatcher.default.dispatch({
action: 'insert_mention',
user_id: mxEvent.getSender()
});
});
(0, _defineProperty2.default)(this, "onRequestKeysClick", () => {
this.setState({
// Indicate in the UI that the keys have been requested (this is expected to
// be reset if the component is mounted in the future).
previouslyRequestedKeys: true
}); // Cancel any outgoing key request for this event and resend it. If a response
// is received for the request with the required keys, the event could be
// decrypted successfully.
this.context.cancelAndResendEventRoomKeyRequest(this.props.mxEvent);
});
(0, _defineProperty2.default)(this, "onPermalinkClicked", e => {
// This allows the permalink to be opened in a new tab/window or copied as
// matrix.to, but also for it to enable routing within Element when clicked.
e.preventDefault();
_dispatcher.default.dispatch({
action: 'view_room',
event_id: this.props.mxEvent.getId(),
highlighted: true,
room_id: this.props.mxEvent.getRoomId()
});
});
(0, _defineProperty2.default)(this, "onActionBarFocusChange", focused => {
this.setState({
actionBarFocused: focused
});
});
(0, _defineProperty2.default)(this, "getTile", () => this.tile.current);
(0, _defineProperty2.default)(this, "getReplyThread", () => this.replyThread.current);
(0, _defineProperty2.default)(this, "getReactions", () => {
if (!this.props.showReactions || !this.props.getRelationsForEvent) {
return null;
}
const eventId = this.props.mxEvent.getId();
if (!eventId) {
// XXX: Temporary diagnostic logging for https://github.com/vector-im/element-web/issues/11120
console.error("EventTile attempted to get relations for an event without an ID"); // Use event's special `toJSON` method to log key data.
console.log(JSON.stringify(this.props.mxEvent, null, 4));
console.trace("Stacktrace for https://github.com/vector-im/element-web/issues/11120");
}
return this.props.getRelationsForEvent(eventId, "m.annotation", "m.reaction");
});
(0, _defineProperty2.default)(this, "onReactionsCreated", (relationType, eventType) => {
if (relationType !== "m.annotation" || eventType !== "m.reaction") {
return;
}
this.props.mxEvent.removeListener("Event.relationsCreated", this.onReactionsCreated);
this.setState({
reactions: this.getReactions()
});
});
this.state = {
// Whether the action bar is focused.
actionBarFocused: false,
// Whether all read receipts are being displayed. If not, only display
// a truncation of them.
allReadAvatars: false,
// Whether the event's sender has been verified.
verified: null,
// Whether onRequestKeysClick has been called since mounting.
previouslyRequestedKeys: false,
// The Relations model from the JS SDK for reactions to `mxEvent`
reactions: this.getReactions()
}; // don't do RR animations until we are mounted
this.suppressReadReceiptAnimation = true; // Throughout the component we manage a read receipt listener to see if our tile still
// qualifies for a "sent" or "sending" state (based on their relevant conditions). We
// don't want to over-subscribe to the read receipt events being fired, so we use a flag
// to determine if we've already subscribed and use a combination of other flags to find
// out if we should even be subscribed at all.
this.isListeningForReceipts = false;
}
/**
* When true, the tile qualifies for some sort of special read receipt. This could be a 'sending'
* or 'sent' receipt, for example.
* @returns {boolean}
*/
get isEligibleForSpecialReceipt() {
// First, if there are other read receipts then just short-circuit this.
if (this.props.readReceipts && this.props.readReceipts.length > 0) return false;
if (!this.props.mxEvent) return false; // Sanity check (should never happen, but we shouldn't explode if it does)
const room = this.context.getRoom(this.props.mxEvent.getRoomId());
if (!room) return false; // Quickly check to see if the event was sent by us. If it wasn't, it won't qualify for
// special read receipts.
const myUserId = _MatrixClientPeg.MatrixClientPeg.get().getUserId();
if (this.props.mxEvent.getSender() !== myUserId) return false; // Finally, determine if the type is relevant to the user. This notably excludes state
// events and pretty much anything that can't be sent by the composer as a message. For
// those we rely on local echo giving the impression of things changing, and expect them
// to be quick.
const simpleSendableEvents = [_event.EventType.Sticker, _event.EventType.RoomMessage, _event.EventType.RoomMessageEncrypted];
if (!simpleSendableEvents.includes(this.props.mxEvent.getType())) return false; // Default case
return true;
}
get shouldShowSentReceipt() {
// If we're not even eligible, don't show the receipt.
if (!this.isEligibleForSpecialReceipt) return false; // We only show the 'sent' receipt on the last successful event.
if (!this.props.lastSuccessful) return false; // Check to make sure the sending state is appropriate. A null/undefined send status means
// that the message is 'sent', so we're just double checking that it's explicitly not sent.
if (this.props.eventSendStatus && this.props.eventSendStatus !== 'sent') return false; // If anyone has read the event besides us, we don't want to show a sent receipt.
const receipts = this.props.readReceipts || [];
const myUserId = _MatrixClientPeg.MatrixClientPeg.get().getUserId();
if (receipts.some(r => r.userId !== myUserId)) return false; // Finally, we should show a receipt.
return true;
}
get shouldShowSendingReceipt() {
// If we're not even eligible, don't show the receipt.
if (!this.isEligibleForSpecialReceipt) return false; // Check the event send status to see if we are pending. Null/undefined status means the
// message was sent, so check for that and 'sent' explicitly.
if (!this.props.eventSendStatus || this.props.eventSendStatus === 'sent') return false; // Default to showing - there's no other event properties/behaviours we care about at
// this point.
return true;
} // TODO: [REACT-WARNING] Move into constructor
// eslint-disable-next-line camelcase
UNSAFE_componentWillMount() {
this.verifyEvent(this.props.mxEvent);
}
componentDidMount() {
this.suppressReadReceiptAnimation = false;
const client = this.context;
client.on("deviceVerificationChanged", this.onDeviceVerificationChanged);
client.on("userTrustStatusChanged", this.onUserVerificationChanged);
this.props.mxEvent.on("Event.decrypted", this.onDecrypted);
if (this.props.showReactions) {
this.props.mxEvent.on("Event.relationsCreated", this.onReactionsCreated);
}
if (this.shouldShowSentReceipt || this.shouldShowSendingReceipt) {
client.on("Room.receipt", this.onRoomReceipt);
this.isListeningForReceipts = true;
}
} // TODO: [REACT-WARNING] Replace with appropriate lifecycle event
// eslint-disable-next-line camelcase
UNSAFE_componentWillReceiveProps(nextProps) {
// re-check the sender verification as outgoing events progress through
// the send process.
if (nextProps.eventSendStatus !== this.props.eventSendStatus) {
this.verifyEvent(nextProps.mxEvent);
}
}
shouldComponentUpdate(nextProps, nextState) {
if ((0, _objects.objectHasDiff)(this.state, nextState)) {
return true;
}
return !this.propsEqual(this.props, nextProps);
}
componentWillUnmount() {
const client = this.context;
client.removeListener("deviceVerificationChanged", this.onDeviceVerificationChanged);
client.removeListener("userTrustStatusChanged", this.onUserVerificationChanged);
client.removeListener("Room.receipt", this.onRoomReceipt);
this.isListeningForReceipts = false;
this.props.mxEvent.removeListener("Event.decrypted", this.onDecrypted);
if (this.props.showReactions) {
this.props.mxEvent.removeListener("Event.relationsCreated", this.onReactionsCreated);
}
}
componentDidUpdate(prevProps, prevState, snapshot) {
// If we're not listening for receipts and expect to be, register a listener.
if (!this.isListeningForReceipts && (this.shouldShowSentReceipt || this.shouldShowSendingReceipt)) {
this.context.on("Room.receipt", this.onRoomReceipt);
this.isListeningForReceipts = true;
}
}
async verifyEvent(mxEvent) {
if (!mxEvent.isEncrypted()) {
return;
}
const encryptionInfo = this.context.getEventEncryptionInfo(mxEvent);
const senderId = mxEvent.getSender();
const userTrust = this.context.checkUserTrust(senderId);
if (encryptionInfo.mismatchedSender) {
// something definitely wrong is going on here
this.setState({
verified: _E2EIcon.E2E_STATE.WARNING
}, this.props.onHeightChanged); // Decryption may have caused a change in size
return;
}
if (!userTrust.isCrossSigningVerified()) {
// user is not verified, so default to everything is normal
this.setState({
verified: _E2EIcon.E2E_STATE.NORMAL
}, this.props.onHeightChanged); // Decryption may have caused a change in size
return;
}
const eventSenderTrust = encryptionInfo.sender && this.context.checkDeviceTrust(senderId, encryptionInfo.sender.deviceId);
if (!eventSenderTrust) {
this.setState({
verified: _E2EIcon.E2E_STATE.UNKNOWN
}, this.props.onHeightChanged); // Decryption may have caused a change in size
return;
}
if (!eventSenderTrust.isVerified()) {
this.setState({
verified: _E2EIcon.E2E_STATE.WARNING
}, this.props.onHeightChanged); // Decryption may have caused a change in size
return;
}
if (!encryptionInfo.authenticated) {
this.setState({
verified: _E2EIcon.E2E_STATE.UNAUTHENTICATED
}, this.props.onHeightChanged); // Decryption may have caused a change in size
return;
}
this.setState({
verified: _E2EIcon.E2E_STATE.VERIFIED
}, this.props.onHeightChanged); // Decryption may have caused a change in size
}
propsEqual(objA, objB) {
const keysA = Object.keys(objA);
const keysB = Object.keys(objB);
if (keysA.length !== keysB.length) {
return false;
}
for (let i = 0; i < keysA.length; i++) {
const key = keysA[i];
if (!objB.hasOwnProperty(key)) {
return false;
} // need to deep-compare readReceipts
if (key === 'readReceipts') {
const rA = objA[key];
const rB = objB[key];
if (rA === rB) {
continue;
}
if (!rA || !rB) {
return false;
}
if (rA.length !== rB.length) {
return false;
}
for (let j = 0; j < rA.length; j++) {
if (rA[j].userId !== rB[j].userId) {
return false;
} // one has a member set and the other doesn't?
if (rA[j].roomMember !== rB[j].roomMember) {
return false;
}
}
} else {
if (objA[key] !== objB[key]) {
return false;
}
}
}
return true;
}
shouldHighlight() {
const actions = this.context.getPushActionsForEvent(this.props.mxEvent.replacingEvent() || this.props.mxEvent);
if (!actions || !actions.tweaks) {
return false;
} // don't show self-highlights from another of our clients
if (this.props.mxEvent.getSender() === this.context.credentials.userId) {
return false;
}
return actions.tweaks.highlight;
}
getReadAvatars() {
if (this.shouldShowSentReceipt || this.shouldShowSendingReceipt) {
return /*#__PURE__*/_react.default.createElement(SentReceipt, {
messageState: this.props.mxEvent.getAssociatedStatus()
});
} // return early if there are no read receipts
if (!this.props.readReceipts || this.props.readReceipts.length === 0) {
return /*#__PURE__*/_react.default.createElement("span", {
className: "mx_EventTile_readAvatars"
});
}
const ReadReceiptMarker = sdk.getComponent('rooms.ReadReceiptMarker');
const avatars = [];
const receiptOffset = 15;
let left = 0;
const receipts = this.props.readReceipts || [];
for (let i = 0; i < receipts.length; ++i) {
const receipt = receipts[i];
let hidden = true;
if (i < MAX_READ_AVATARS || this.state.allReadAvatars) {
hidden = false;
} // TODO: we keep the extra read avatars in the dom to make animation simpler
// we could optimise this to reduce the dom size.
// If hidden, set offset equal to the offset of the final visible avatar or
// else set it proportional to index
left = (hidden ? MAX_READ_AVATARS - 1 : i) * -receiptOffset;
const userId = receipt.userId;
let readReceiptInfo;
if (this.props.readReceiptMap) {
readReceiptInfo = this.props.readReceiptMap[userId];
if (!readReceiptInfo) {
readReceiptInfo = {};
this.props.readReceiptMap[userId] = readReceiptInfo;
}
} // add to the start so the most recent is on the end (ie. ends up rightmost)
avatars.unshift( /*#__PURE__*/_react.default.createElement(ReadReceiptMarker, {
key: userId,
member: receipt.roomMember,
fallbackUserId: userId,
leftOffset: left,
hidden: hidden,
readReceiptInfo: readReceiptInfo,
checkUnmounting: this.props.checkUnmounting,
suppressAnimation: this.suppressReadReceiptAnimation,
onClick: this.toggleAllReadAvatars,
timestamp: receipt.ts,
showTwelveHour: this.props.isTwelveHour
}));
}
let remText;
if (!this.state.allReadAvatars) {
const remainder = receipts.length - MAX_READ_AVATARS;
if (remainder > 0) {
remText = /*#__PURE__*/_react.default.createElement("span", {
className: "mx_EventTile_readAvatarRemainder",
onClick: this.toggleAllReadAvatars,
style: {
right: "calc(" + (0, _units.toRem)(-left) + " + " + receiptOffset + "px)"
}
}, remainder, "+");
}
}
return /*#__PURE__*/_react.default.createElement("span", {
className: "mx_EventTile_readAvatars"
}, remText, avatars);
}
renderE2EPadlock() {
const ev = this.props.mxEvent; // event could not be decrypted
if (ev.getContent().msgtype === 'm.bad.encrypted') {
return /*#__PURE__*/_react.default.createElement(E2ePadlockUndecryptable, null);
} // event is encrypted, display padlock corresponding to whether or not it is verified
if (ev.isEncrypted()) {
if (this.state.verified === _E2EIcon.E2E_STATE.NORMAL) {
return; // no icon if we've not even cross-signed the user
} else if (this.state.verified === _E2EIcon.E2E_STATE.VERIFIED) {
return; // no icon for verified
} else if (this.state.verified === _E2EIcon.E2E_STATE.UNAUTHENTICATED) {
return /*#__PURE__*/_react.default.createElement(E2ePadlockUnauthenticated, null);
} else if (this.state.verified === _E2EIcon.E2E_STATE.UNKNOWN) {
return /*#__PURE__*/_react.default.createElement(E2ePadlockUnknown, null);
} else {
return /*#__PURE__*/_react.default.createElement(E2ePadlockUnverified, null);
}
}
if (this.context.isRoomEncrypted(ev.getRoomId())) {
// else if room is encrypted
// and event is being encrypted or is not_sent (Unknown Devices/Network Error)
if (ev.status === _event2.EventStatus.ENCRYPTING) {
return;
}
if (ev.status === _event2.EventStatus.NOT_SENT) {
return;
}
if (ev.isState()) {
return; // we expect this to be unencrypted
} // if the event is not encrypted, but it's an e2e room, show the open padlock
return /*#__PURE__*/_react.default.createElement(E2ePadlockUnencrypted, null);
} // no padlock needed
return null;
}
render() {
const MessageTimestamp = sdk.getComponent('messages.MessageTimestamp');
const SenderProfile = sdk.getComponent('messages.SenderProfile');
const MemberAvatar = sdk.getComponent('avatars.MemberAvatar'); //console.info("EventTile showUrlPreview for %s is %s", this.props.mxEvent.getId(), this.props.showUrlPreview);
const content = this.props.mxEvent.getContent();
const msgtype = content.msgtype;
const eventType = this.props.mxEvent.getType();
let tileHandler = getHandlerTile(this.props.mxEvent); // Info messages are basically information about commands processed on a room
const isBubbleMessage = eventType.startsWith("m.key.verification") || eventType === _event.EventType.RoomMessage && msgtype && msgtype.startsWith("m.key.verification") || eventType === _event.EventType.RoomCreate || eventType === _event.EventType.RoomEncryption || tileHandler === "messages.MJitsiWidgetEvent";
let isInfoMessage = !isBubbleMessage && eventType !== _event.EventType.RoomMessage && eventType !== _event.EventType.Sticker && eventType !== _event.EventType.RoomCreate; // If we're showing hidden events in the timeline, we should use the
// source tile when there's no regular tile for an event and also for
// replace relations (which otherwise would display as a confusing
// duplicate of the thing they are replacing).
if (_SettingsStore.default.getValue("showHiddenEventsInTimeline") && !haveTileForEvent(this.props.mxEvent)) {
tileHandler = "messages.ViewSourceEvent"; // Reuse info message avatar and sender profile styling
isInfoMessage = true;
} // This shouldn't happen: the caller should check we support this type
// before trying to instantiate us
if (!tileHandler) {
const {
mxEvent
} = this.props;
console.warn(`Event type not supported: type:${mxEvent.getType()} isState:${mxEvent.isState()}`);
return /*#__PURE__*/_react.default.createElement("div", {
className: "mx_EventTile mx_EventTile_info mx_MNoticeBody"
}, /*#__PURE__*/_react.default.createElement("div", {
className: "mx_EventTile_line"
}, (0, _languageHandler._t)('This event could not be displayed')));
}
const EventTileType = sdk.getComponent(tileHandler);
const isSending = ['sending', 'queued', 'encrypting'].indexOf(this.props.eventSendStatus) !== -1;
const isRedacted = isMessageEvent(this.props.mxEvent) && this.props.isRedacted;
const isEncryptionFailure = this.props.mxEvent.isDecryptionFailure();
const isEditing = !!this.props.editState;
const classes = (0, _classnames.default)({
mx_EventTile_bubbleContainer: isBubbleMessage,
mx_EventTile: true,
mx_EventTile_isEditing: isEditing,
mx_EventTile_info: isInfoMessage,
mx_EventTile_12hr: this.props.isTwelveHour,
// Note: we keep the `sending` state class for tests, not for our styles
mx_EventTile_sending: !isEditing && isSending,
mx_EventTile_highlight: this.props.tileShape === 'notif' ? false : this.shouldHighlight(),
mx_EventTile_selected: this.props.isSelectedEvent,
mx_EventTile_continuation: this.props.tileShape ? '' : this.props.continuation,
mx_EventTile_last: this.props.last,
mx_EventTile_lastInSection: this.props.lastInSection,
mx_EventTile_contextual: this.props.contextual,
mx_EventTile_actionBarFocused: this.state.actionBarFocused,
mx_EventTile_verified: !isBubbleMessage && this.state.verified === _E2EIcon.E2E_STATE.VERIFIED,
mx_EventTile_unverified: !isBubbleMessage && this.state.verified === _E2EIcon.E2E_STATE.WARNING,
mx_EventTile_unknown: !isBubbleMessage && this.state.verified === _E2EIcon.E2E_STATE.UNKNOWN,
mx_EventTile_bad: isEncryptionFailure,
mx_EventTile_emote: msgtype === 'm.emote'
}); // If the tile is in the Sending state, don't speak the message.
const ariaLive = this.props.eventSendStatus !== null ? 'off' : undefined;
let permalink = "#";
if (this.props.permalinkCreator) {
permalink = this.props.permalinkCreator.forEvent(this.props.mxEvent.getId());
}
let avatar;
let sender;
let avatarSize;
let needsSenderProfile;
if (this.props.tileShape === "notif") {
avatarSize = 24;
needsSenderProfile = true;
} else if (tileHandler === 'messages.RoomCreate' || isBubbleMessage) {
avatarSize = 0;
needsSenderProfile = false;
} else if (isInfoMessage) {
// a small avatar, with no sender profile, for
// joins/parts/etc
avatarSize = 14;
needsSenderProfile = false;
} else if (this.props.layout == _Layout.Layout.IRC) {
avatarSize = 14;
needsSenderProfile = true;
} else if (this.props.continuation && this.props.tileShape !== "file_grid") {
// no avatar or sender profile for continuation messages
avatarSize = 0;
needsSenderProfile = false;
} else {
avatarSize = 30;
needsSenderProfile = true;
}
if (this.props.mxEvent.sender && avatarSize) {
let member; // set member to receiver (target) if it is a 3PID invite
// so that the correct avatar is shown as the text is
// `$target accepted the invitation for $email`
if (this.props.mxEvent.getContent().third_party_invite) {
member = this.props.mxEvent.target;
} else {
member = this.props.mxEvent.sender;
}
avatar = /*#__PURE__*/_react.default.createElement("div", {
className: "mx_EventTile_avatar"
}, /*#__PURE__*/_react.default.createElement(MemberAvatar, {
member: member,
width: avatarSize,
height: avatarSize,
viewUserOnClick: true
}));
}
if (needsSenderProfile) {
if (!this.props.tileShape || this.props.tileShape === 'reply' || this.props.tileShape === 'reply_preview') {
sender = /*#__PURE__*/_react.default.createElement(SenderProfile, {
onClick: this.onSenderProfileClick,
mxEvent: this.props.mxEvent,
enableFlair: this.props.enableFlair
});
} else {
sender = /*#__PURE__*/_react.default.createElement(SenderProfile, {
mxEvent: this.props.mxEvent,
enableFlair: this.props.enableFlair
});
}
}
const MessageActionBar = sdk.getComponent('messages.MessageActionBar');
const actionBar = !isEditing ? /*#__PURE__*/_react.default.createElement(MessageActionBar, {
mxEvent: this.props.mxEvent,
reactions: this.state.reactions,
permalinkCreator: this.props.permalinkCreator,
getTile: this.getTile,
getReplyThread: this.getReplyThread,
onFocusChange: this.onActionBarFocusChange
}) : undefined;
const timestamp = this.props.mxEvent.getTs() ? /*#__PURE__*/_react.default.createElement(MessageTimestamp, {
showTwelveHour: this.props.isTwelveHour,
ts: this.props.mxEvent.getTs()
}) : null;
const keyRequestHelpText = /*#__PURE__*/_react.default.createElement("div", {
className: "mx_EventTile_keyRequestInfo_tooltip_contents"
}, /*#__PURE__*/_react.default.createElement("p", null, this.state.previouslyRequestedKeys ? (0, _languageHandler._t)('Your key share request has been sent - please check your other sessions ' + 'for key share requests.') : (0, _languageHandler._t)('Key share requests are sent to your other sessions automatically. If you ' + 'rejected or dismissed the key share request on your other sessions, click ' + 'here to request the keys for this session again.')), /*#__PURE__*/_react.default.createElement("p", null, (0, _languageHandler._t)('If your other sessions do not have the key for this message you will not ' + 'be able to decrypt them.')));
const keyRequestInfoContent = this.state.previouslyRequestedKeys ? (0, _languageHandler._t)('Key request sent.') : (0, _languageHandler._t)('<requestLink>Re-request encryption keys</requestLink> from your other sessions.', {}, {
'requestLink': sub => /*#__PURE__*/_react.default.createElement("a", {
onClick: this.onRequestKeysClick
}, sub)
});
const TooltipButton = sdk.getComponent('elements.TooltipButton');
const keyRequestInfo = isEncryptionFailure && !isRedacted ? /*#__PURE__*/_react.default.createElement("div", {
className: "mx_EventTile_keyRequestInfo"
}, /*#__PURE__*/_react.default.createElement("span", {
className: "mx_EventTile_keyRequestInfo_text"
}, keyRequestInfoContent), /*#__PURE__*/_react.default.createElement(TooltipButton, {
helpText: keyRequestHelpText
})) : null;
let reactionsRow;
if (!isRedacted) {
const ReactionsRow = sdk.getComponent('messages.ReactionsRow');
reactionsRow = /*#__PURE__*/_react.default.createElement(ReactionsRow, {
mxEvent: this.props.mxEvent,
reactions: this.state.reactions
});
}
const linkedTimestamp = /*#__PURE__*/_react.default.createElement("a", {
href: permalink,
onClick: this.onPermalinkClicked,
"aria-label": (0, _DateUtils.formatTime)(new Date(this.props.mxEvent.getTs()), this.props.isTwelveHour)
}, timestamp);
const useIRCLayout = this.props.layout == _Layout.Layout.IRC;
const groupTimestamp = !useIRCLayout ? linkedTimestamp : null;
const ircTimestamp = useIRCLayout ? linkedTimestamp : null;
const groupPadlock = !useIRCLayout && !isBubbleMessage && this.renderE2EPadlock();
const ircPadlock = useIRCLayout && !isBubbleMessage && this.renderE2EPadlock();
let msgOption;
if (this.props.showReadReceipts) {
const readAvatars = this.getReadAvatars();
msgOption = /*#__PURE__*/_react.default.createElement("div", {
className: "mx_EventTile_msgOption"
}, readAvatars);
}
switch (this.props.tileShape) {
case 'notif':
{
const room = this.context.getRoom(this.props.mxEvent.getRoomId());
return /*#__PURE__*/_react.default.createElement("div", {
className: classes,
"aria-live": ariaLive,
"aria-atomic": "true"
}, /*#__PURE__*/_react.default.createElement("div", {
className: "mx_EventTile_roomName"
}, /*#__PURE__*/_react.default.createElement(_RoomAvatar.default, {
room: room,
width: 28,
height: 28
}), /*#__PURE__*/_react.default.createElement("a", {
href: permalink,
onClick: this.onPermalinkClicked
}, room ? room.name : '')), /*#__PURE__*/_react.default.createElement("div", {
className: "mx_EventTile_senderDetails"
}, avatar, /*#__PURE__*/_react.default.createElement("a", {
href: permalink,
onClick: this.onPermalinkClicked
}, sender, timestamp)), /*#__PURE__*/_react.default.createElement("div", {
className: "mx_EventTile_line"
}, /*#__PURE__*/_react.default.createElement(EventTileType, {
ref: this.tile,
mxEvent: this.props.mxEvent,
highlights: this.props.highlights,
highlightLink: this.props.highlightLink,
showUrlPreview: this.props.showUrlPreview,
onHeightChanged: this.props.onHeightChanged
})));
}
case 'file_grid':
{
return /*#__PURE__*/_react.default.createElement("div", {
className: classes,
"aria-live": ariaLive,
"aria-atomic": "true"
}, /*#__PURE__*/_react.default.createElement("div", {
className: "mx_EventTile_line"
}, /*#__PURE__*/_react.default.createElement(EventTileType, {
ref: this.tile,
mxEvent: this.props.mxEvent,
highlights: this.props.highlights,
highlightLink: this.props.highlightLink,
showUrlPreview: this.props.showUrlPreview,
tileShape: this.props.tileShape,
onHeightChanged: this.props.onHeightChanged
})), /*#__PURE__*/_react.default.createElement("a", {
className: "mx_EventTile_senderDetailsLink",
href: permalink,
onClick: this.onPermalinkClicked
}, /*#__PURE__*/_react.default.createElement("div", {
className: "mx_EventTile_senderDetails"
}, sender, timestamp)));
}
case 'reply':
case 'reply_preview':
{
let thread;
if (this.props.tileShape === 'reply_preview') {
thread = _ReplyThread.default.makeThread(this.props.mxEvent, this.props.onHeightChanged, this.props.permalinkCreator, this.replyThread);
}
return /*#__PURE__*/_react.default.createElement("div", {
className: classes,
"aria-live": ariaLive,
"aria-atomic": "true"
}, ircTimestamp, avatar, sender, ircPadlock, /*#__PURE__*/_react.default.createElement("div", {
className: "mx_EventTile_reply"
}, groupTimestamp, groupPadlock, thread, /*#__PURE__*/_react.default.createElement(EventTileType, {
ref: this.tile,
mxEvent: this.props.mxEvent,
highlights: this.props.highlights,
highlightLink: this.props.highlightLink,
onHeightChanged: this.props.onHeightChanged,
replacingEventId: this.props.replacingEventId,
showUrlPreview: false
})));
}
default:
{
const thread = _ReplyThread.default.makeThread(this.props.mxEvent, this.props.onHeightChanged, this.props.permalinkCreator, this.replyThread, this.props.layout); // tab-index=-1 to allow it to be focusable but do not add tab stop for it, primarily for screen readers
return /*#__PURE__*/_react.default.createElement("div", {
className: classes,
tabIndex: -1,
"aria-live": ariaLive,
"aria-atomic": "true"
}, ircTimestamp, sender, ircPadlock, /*#__PURE__*/_react.default.createElement("div", {
className: "mx_EventTile_line"
}, groupTimestamp, groupPadlock, thread, /*#__PURE__*/_react.default.createElement(EventTileType, {
ref: this.tile,
mxEvent: this.props.mxEvent,
replacingEventId: this.props.replacingEventId,
editState: this.props.editState,
highlights: this.props.highlights,
highlightLink: this.props.highlightLink,
showUrlPreview: this.props.showUrlPreview,
permalinkCreator: this.props.permalinkCreator,
onHeightChanged: this.props.onHeightChanged
}), keyRequestInfo, reactionsRow, actionBar), msgOption, avatar);
}
}
}
}, (0, _defineProperty2.default)(_class2, "defaultProps", {
// no-op function because onHeightChanged is optional yet some sub-components assume its existence
onHeightChanged: function () {}
}), (0, _defineProperty2.default)(_class2, "contextType", _MatrixClientContext.default), _temp)) || _class);
exports.default = EventTile;
// XXX this'll eventually be dynamic based on the fields once we have extensible event types
const messageTypes = ['m.room.message', 'm.sticker'];
function isMessageEvent(ev) {
return messageTypes.includes(ev.getType());
}
function haveTileForEvent(e) {
// Only messages have a tile (black-rectangle) if redacted
if (e.isRedacted() && !isMessageEvent(e)) return false; // No tile for replacement events since they update the original tile
if (e.isRelation("m.replace")) return false;
const handler = getHandlerTile(e);
if (handler === undefined) return false;
if (handler === 'messages.TextualEvent') {
return TextForEvent.textForEvent(e) !== '';
} else if (handler === 'messages.RoomCreate') {
return Boolean(e.getContent()['predecessor']);
} else {
return true;
}
}
function E2ePadlockUndecryptable(props) {
return /*#__PURE__*/_react.default.createElement(E2ePadlock, (0, _extends2.default)({
title: (0, _languageHandler._t)("This message cannot be decrypted"),
icon: "undecryptable"
}, props));
}
function E2ePadlockUnverified(props) {
return /*#__PURE__*/_react.default.createElement(E2ePadlock, (0, _extends2.default)({
title: (0, _languageHandler._t)("Encrypted by an unverified session"),
icon: "unverified"
}, props));
}
function E2ePadlockUnencrypted(props) {
return /*#__PURE__*/_react.default.createElement(E2ePadlock, (0, _extends2.default)({
title: (0, _languageHandler._t)("Unencrypted"),
icon: "unencrypted"
}, props));
}
function E2ePadlockUnknown(props) {
return /*#__PURE__*/_react.default.createElement(E2ePadlock, (0, _extends2.default)({
title: (0, _languageHandler._t)("Encrypted by a deleted session"),
icon: "unknown"
}, props));
}
function E2ePadlockUnauthenticated(props) {
return /*#__PURE__*/_react.default.createElement(E2ePadlock, (0, _extends2.default)({
title: (0, _languageHandler._t)("The authenticity of this encrypted message can't be guaranteed on this device."),
icon: "unauthenticated"
}, props));
}
class E2ePadlock extends _react.default.Component
/*:: <IE2ePadlockProps, IE2ePadlockState>*/
{
constructor(props) {
super(props);
(0, _defineProperty2.default)(this, "onHoverStart", () => {
this.setState({
hover: true
});
});
(0, _defineProperty2.default)(this, "onHoverEnd", () => {
this.setState({
hover: false
});
});
this.state = {
hover: false
};
}
render() {
let tooltip = null;
if (this.state.hover) {
tooltip = /*#__PURE__*/_react.default.createElement(_Tooltip.default, {
className: "mx_EventTile_e2eIcon_tooltip",
label: this.props.title
});
}
const classes = `mx_EventTile_e2eIcon mx_EventTile_e2eIcon_${this.props.icon}`;
return /*#__PURE__*/_react.default.createElement("div", {
className: classes,
onMouseEnter: this.onHoverStart,
onMouseLeave: this.onHoverEnd
}, tooltip);
}
}
class SentReceipt extends _react.default.PureComponent
/*:: <ISentReceiptProps, ISentReceiptState>*/
{
constructor(props) {
super(props);
(0, _defineProperty2.default)(this, "onHoverStart", () => {
this.setState({
hover: true
});
});
(0, _defineProperty2.default)(this, "onHoverEnd", () => {
this.setState({
hover: false
});
});
this.state = {
hover: false
};
}
render() {
const isSent = !this.props.messageState || this.props.messageState === 'sent';
const isFailed = this.props.messageState === 'not_sent';
const receiptClasses = (0, _classnames.default)({
'mx_EventTile_receiptSent': isSent,
'mx_EventTile_receiptSending': !isSent && !isFailed
});
let nonCssBadge = null;
if (isFailed) {
nonCssBadge = /*#__PURE__*/_react.default.createElement(_NotificationBadge.default, {
notification: _StaticNotificationState.StaticNotificationState.RED_EXCLAMATION
});
}
let tooltip = null;
if (this.state.hover) {
let label = (0, _languageHandler._t)("Sending your message...");
if (this.props.messageState === 'encrypting') {
label = (0, _languageHandler._t)("Encrypting your message...");
} else if (isSent) {
label = (0, _languageHandler._t)("Your message was sent");
} else if (isFailed) {
label = (0, _languageHandler._t)("Failed to send");
} // The yOffset is somewhat arbitrary - it just brings the tooltip down to be more associated
// with the read receipt.
tooltip = /*#__PURE__*/_react.default.createElement(_Tooltip.default, {
className: "mx_EventTile_readAvatars_receiptTooltip",
label: label,
yOffset: 20
});
}
return /*#__PURE__*/_react.default.createElement("span", {
className: "mx_EventTile_readAvatars"
}, /*#__PURE__*/_react.default.createElement("span", {
className: receiptClasses,
onMouseEnter: this.onHoverStart,
onMouseLeave: this.onHoverEnd
}, nonCssBadge, tooltip));
}
}
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9jb21wb25lbnRzL3ZpZXdzL3Jvb21zL0V2ZW50VGlsZS50c3giXSwibmFtZXMiOlsiZXZlbnRUaWxlVHlwZXMiLCJFdmVudFR5cGUiLCJSb29tTWVzc2FnZSIsIlN0aWNrZXIiLCJLZXlWZXJpZmljYXRpb25DYW5jZWwiLCJLZXlWZXJpZmljYXRpb25Eb25lIiwiQ2FsbEludml0ZSIsIkNhbGxBbnN3ZXIiLCJDYWxsSGFuZ3VwIiwiQ2FsbFJlamVjdCIsInN0YXRlRXZlbnRUaWxlVHlwZXMiLCJSb29tRW5jcnlwdGlvbiIsIlJvb21DYW5vbmljYWxBbGlhcyIsIlJvb21DcmVhdGUiLCJSb29tTWVtYmVyIiwiUm9vbU5hbWUiLCJSb29tQXZhdGFyIiwiUm9vbVRoaXJkUGFydHlJbnZpdGUiLCJSb29tSGlzdG9yeVZpc2liaWxpdHkiLCJSb29tVG9waWMiLCJSb29tUG93ZXJMZXZlbHMiLCJSb29tUGlubmVkRXZlbnRzIiwiUm9vbVNlcnZlckFjbCIsIldJREdFVF9MQVlPVVRfRVZFTlRfVFlQRSIsIlJvb21Ub21ic3RvbmUiLCJSb29tSm9pblJ1bGVzIiwiUm9vbUd1ZXN0QWNjZXNzIiwic3RhdGVFdmVudFNpbmd1bGFyIiwiU2V0IiwiZXZUeXBlIiwiQUxMX1JVTEVfVFlQRVMiLCJnZXRIYW5kbGVyVGlsZSIsImV2IiwidHlwZSIsImdldFR5cGUiLCJjb250ZW50IiwiZ2V0Q29udGVudCIsIm1zZ3R5cGUiLCJjbGllbnQiLCJNYXRyaXhDbGllbnRQZWciLCJnZXQiLCJtZSIsImdldFVzZXJJZCIsImdldFNlbmRlciIsInRvIiwidW5kZWZpbmVkIiwiTUtleVZlcmlmaWNhdGlvbkNvbmNsdXNpb24iLCJzZGsiLCJnZXRDb21wb25lbnQiLCJwcm90b3R5cGUiLCJfc2hvdWxkUmVuZGVyIiwiY2FsbCIsInJlcXVlc3QiLCJnZXRQcmV2Q29udGVudCIsIldpZGdldFR5cGUiLCJKSVRTSSIsIm1hdGNoZXMiLCJpc1N0YXRlIiwiaGFzIiwiZ2V0U3RhdGVLZXkiLCJNQVhfUkVBRF9BVkFUQVJTIiwiRXZlbnRUaWxlIiwiUmVhY3QiLCJDb21wb25lbnQiLCJjb25zdHJ1Y3RvciIsInByb3BzIiwiY29udGV4dCIsImNyZWF0ZVJlZiIsInJvb20iLCJ0aWxlUm9vbSIsImdldFJvb20iLCJteEV2ZW50IiwiZ2V0Um9vbUlkIiwic2hvdWxkU2hvd1NlbnRSZWNlaXB0Iiwic2hvdWxkU2hvd1NlbmRpbmdSZWNlaXB0IiwiaXNMaXN0ZW5pbmdGb3JSZWNlaXB0cyIsImZvcmNlVXBkYXRlIiwicmVtb3ZlTGlzdGVuZXIiLCJvblJvb21SZWNlaXB0IiwidmVyaWZ5RXZlbnQiLCJ1c2VySWQiLCJkZXZpY2UiLCJfdHJ1c3RTdGF0dXMiLCJzZXRTdGF0ZSIsImFsbFJlYWRBdmF0YXJzIiwic3RhdGUiLCJldmVudCIsImRpcyIsImRpc3BhdGNoIiwiYWN0aW9uIiwidXNlcl9pZCIsInByZXZpb3VzbHlSZXF1ZXN0ZWRLZXlzIiwiY2FuY2VsQW5kUmVzZW5kRXZlbnRSb29tS2V5UmVxdWVzdCIsImUiLCJwcmV2ZW50RGVmYXVsdCIsImV2ZW50X2lkIiwiZ2V0SWQiLCJoaWdobGlnaHRlZCIsInJvb21faWQiLCJmb2N1c2VkIiwiYWN0aW9uQmFyRm9jdXNlZCIsInRpbGUiLCJjdXJyZW50IiwicmVwbHlUaHJlYWQiLCJzaG93UmVhY3Rpb25zIiwiZ2V0UmVsYXRpb25zRm9yRXZlbnQiLCJldmVudElkIiwiY29uc29sZSIsImVycm9yIiwibG9nIiwiSlNPTiIsInN0cmluZ2lmeSIsInRyYWNlIiwicmVsYXRpb25UeXBlIiwiZXZlbnRUeXBlIiwib25SZWFjdGlvbnNDcmVhdGVkIiwicmVhY3Rpb25zIiwiZ2V0UmVhY3Rpb25zIiwidmVyaWZpZWQiLCJzdXBwcmVzc1JlYWRSZWNlaXB0QW5pbWF0aW9uIiwiaXNFbGlnaWJsZUZvclNwZWNpYWxSZWNlaXB0IiwicmVhZFJlY2VpcHRzIiwibGVuZ3RoIiwibXlVc2VySWQiLCJzaW1wbGVTZW5kYWJsZUV2ZW50cyIsIlJvb21NZXNzYWdlRW5jcnlwdGVkIiwiaW5jbHVkZXMiLCJsYXN0U3VjY2Vzc2Z1bCIsImV2ZW50U2VuZFN0YXR1cyIsInJlY2VpcHRzIiwic29tZSIsInIiLCJVTlNBRkVfY29tcG9uZW50V2lsbE1vdW50IiwiY29tcG9uZW50RGlkTW91bnQiLCJvbiIsIm9uRGV2aWNlVmVyaWZpY2F0aW9uQ2hhbmdlZCIsIm9uVXNlclZlcmlmaWNhdGlvbkNoYW5nZWQiLCJvbkRlY3J5cHRlZCIsIlVOU0FGRV9jb21wb25lbnRXaWxsUmVjZWl2ZVByb3BzIiwibmV4dFByb3BzIiwic2hvdWxkQ29tcG9uZW50VXBkYXRlIiwibmV4dFN0YXRlIiwicHJvcHNFcXVhbCIsImNvbXBvbmVudFdpbGxVbm1vdW50IiwiY29tcG9uZW50RGlkVXBkYXRlIiwicHJldlByb3BzIiwicHJldlN0YXRlIiwic25hcHNob3QiLCJpc0VuY3J5cHRlZCIsImVuY3J5cHRpb25JbmZvIiwiZ2V0RXZlbnRFbmNyeXB0aW9uSW5mbyIsInNlbmRlcklkIiwidXNlclRydXN0IiwiY2hlY2tVc2VyVHJ1c3QiLCJtaXNtYXRjaGVkU2VuZGVyIiwiRTJFX1NUQVRFIiwiV0FSTklORyIsIm9uSGVpZ2h0Q2hhbmdlZCIsImlzQ3Jvc3NTaWduaW5nVmVyaWZpZWQiLCJOT1JNQUwiLCJldmVudFNlbmRlclRydXN0Iiwic2VuZGVyIiwiY2hlY2tEZXZpY2VUcnVzdCIsImRldmljZUlkIiwiVU5LTk9XTiIsImlzVmVyaWZpZWQiLCJhdXRoZW50aWNhdGVkIiw