Merge branches 'develop' and 't3chguy/alpha_room_list' of github.com:matrix-org/matrix-react-sdk into t3chguy/alpha_room_list

This commit is contained in:
Michael Telatynski 2020-02-19 13:30:47 +00:00
commit 4278d44059
25 changed files with 1264 additions and 390 deletions

View File

@ -1,3 +1,379 @@
Changes in [2.1.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.1.0) (2020-02-17)
===================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.1.0-rc.2...v2.1.0)
* Automate SDK dep upgrades for release
[\#4076](https://github.com/matrix-org/matrix-react-sdk/pull/4076)
Changes in [2.1.0-rc.2](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.1.0-rc.2) (2020-02-13)
=============================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.1.0-rc.1...v2.1.0-rc.2)
* Fix error in previous attempt to upgrade JS SDK
Changes in [2.1.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.1.0-rc.1) (2020-02-13)
=============================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.0.0...v2.1.0-rc.1)
* Upgrade JS SDK to 5.0.0-rc.1
* don't show tooltips on big icons
[\#4067](https://github.com/matrix-org/matrix-react-sdk/pull/4067)
* Update from Weblate
[\#4069](https://github.com/matrix-org/matrix-react-sdk/pull/4069)
* Fix sending of visit variables to Matomo
[\#4068](https://github.com/matrix-org/matrix-react-sdk/pull/4068)
* Use embedded piwik script rather than piwik.js to respect CSP
[\#4066](https://github.com/matrix-org/matrix-react-sdk/pull/4066)
* remove methods arg to requestVerification(DM)
[\#4058](https://github.com/matrix-org/matrix-react-sdk/pull/4058)
* Check for null config settings a bit safer
[\#4061](https://github.com/matrix-org/matrix-react-sdk/pull/4061)
* Score user ID searches higher when they match nearly exactly
[\#4060](https://github.com/matrix-org/matrix-react-sdk/pull/4060)
* Fix uncentered letter inside avatar for currently typing users
[\#4051](https://github.com/matrix-org/matrix-react-sdk/pull/4051)
* Disable 'start' button after clicking in VerificationPanel
[\#4065](https://github.com/matrix-org/matrix-react-sdk/pull/4065)
* Fixed bug where key reset didn't always return the right key
[\#4057](https://github.com/matrix-org/matrix-react-sdk/pull/4057)
* Don't render avatars in pills for screen readers.
[\#4062](https://github.com/matrix-org/matrix-react-sdk/pull/4062)
* Make QR self-verification compatible with RiotX
[\#4044](https://github.com/matrix-org/matrix-react-sdk/pull/4044)
* Verify single device from other user in right panel & Not Trusted dialog
[\#4043](https://github.com/matrix-org/matrix-react-sdk/pull/4043)
* Disable verification buttons after clicking to avoid double submission
[\#4049](https://github.com/matrix-org/matrix-react-sdk/pull/4049)
* Verification toast fixes
[\#4048](https://github.com/matrix-org/matrix-react-sdk/pull/4048)
* Use EncryptionPanel everywhere, part I
[\#4042](https://github.com/matrix-org/matrix-react-sdk/pull/4042)
* quick fix for cross-signing reset bug
[\#4056](https://github.com/matrix-org/matrix-react-sdk/pull/4056)
* Fix error message rendering for key entry
[\#4055](https://github.com/matrix-org/matrix-react-sdk/pull/4055)
* Fix recaptcha blocked by CSP for non-SSL origins
[\#4052](https://github.com/matrix-org/matrix-react-sdk/pull/4052)
* Fix watcher for showTypingNotifications setting
[\#4054](https://github.com/matrix-org/matrix-react-sdk/pull/4054)
* Allow custom hs url submission on enter
[\#4053](https://github.com/matrix-org/matrix-react-sdk/pull/4053)
* Support keepSecretStoragePassphraseForSession at the config level too
[\#4045](https://github.com/matrix-org/matrix-react-sdk/pull/4045)
* Add setting to allow hiding of typing indicator
[\#4047](https://github.com/matrix-org/matrix-react-sdk/pull/4047)
* Button to reset cross-signing and SSSS keys
[\#4041](https://github.com/matrix-org/matrix-react-sdk/pull/4041)
* Use forms to wrap password fields so Chrome doesn't go wild
[\#3974](https://github.com/matrix-org/matrix-react-sdk/pull/3974)
* Update QR code rendering to support VerificationRequests
[\#4001](https://github.com/matrix-org/matrix-react-sdk/pull/4001)
* Differentiate AccessSecretStorageDialog dismiss dialog based on which key we
want to read
[\#4038](https://github.com/matrix-org/matrix-react-sdk/pull/4038)
* Only emit in RoomViewStore when state actually changes
[\#4039](https://github.com/matrix-org/matrix-react-sdk/pull/4039)
* Mark AccessSecretStorageDialog to not be closed by clicking background
[\#4029](https://github.com/matrix-org/matrix-react-sdk/pull/4029)
* Let pointer events fall through to scroll button
[\#4037](https://github.com/matrix-org/matrix-react-sdk/pull/4037)
* Improve event indexing status strings for translation
[\#4035](https://github.com/matrix-org/matrix-react-sdk/pull/4035)
* Button size reviewed for word consuming languages & Settings showing devices
are a bit too tight
[\#4024](https://github.com/matrix-org/matrix-react-sdk/pull/4024)
* Only enumerate settings handlers which are supported
[\#4034](https://github.com/matrix-org/matrix-react-sdk/pull/4034)
* Fix listener removal in verification tile
[\#4036](https://github.com/matrix-org/matrix-react-sdk/pull/4036)
* Do not show alarming red shields on large encrypted rooms for your own
device
[\#4028](https://github.com/matrix-org/matrix-react-sdk/pull/4028)
* Add a class for styling room directory permissions
[\#4007](https://github.com/matrix-org/matrix-react-sdk/pull/4007)
* double-check user verification
[\#4010](https://github.com/matrix-org/matrix-react-sdk/pull/4010)
* Use minimist instead of optimist as it is deprecated
[\#4031](https://github.com/matrix-org/matrix-react-sdk/pull/4031)
* SettingsStore, use a counter instead of wall clock for watcher ids
[\#4032](https://github.com/matrix-org/matrix-react-sdk/pull/4032)
* Don't crash immediately if the room directory chunk is null/empty
[\#4027](https://github.com/matrix-org/matrix-react-sdk/pull/4027)
* Fix verification toast to close at 0s
[\#3998](https://github.com/matrix-org/matrix-react-sdk/pull/3998)
* Fix listener leak in TagPanel
[\#4026](https://github.com/matrix-org/matrix-react-sdk/pull/4026)
* Update from Weblate
[\#4025](https://github.com/matrix-org/matrix-react-sdk/pull/4025)
* Honour the isLogin flag in theme.js
[\#4023](https://github.com/matrix-org/matrix-react-sdk/pull/4023)
* ManageEventIndexDialog: Show how many rooms are being currently crawled.
[\#4022](https://github.com/matrix-org/matrix-react-sdk/pull/4022)
* Advertise that we can scan QR codes even though we can't
[\#4021](https://github.com/matrix-org/matrix-react-sdk/pull/4021)
* Checkpoint addition fixes and return of the crawler sleep time setting.
[\#4020](https://github.com/matrix-org/matrix-react-sdk/pull/4020)
* Truncate SAS emoji labels to fit
[\#4018](https://github.com/matrix-org/matrix-react-sdk/pull/4018)
* Apply copy edits to security setup flow
[\#4017](https://github.com/matrix-org/matrix-react-sdk/pull/4017)
* Fix user trust text to match what was checked
[\#4016](https://github.com/matrix-org/matrix-react-sdk/pull/4016)
* Fix size of invite only icon
[\#4015](https://github.com/matrix-org/matrix-react-sdk/pull/4015)
* Add temporary feature flag to control padlocks
[\#4013](https://github.com/matrix-org/matrix-react-sdk/pull/4013)
* Add an override for the theme
[\#4014](https://github.com/matrix-org/matrix-react-sdk/pull/4014)
* Add title to complete security loading
[\#4011](https://github.com/matrix-org/matrix-react-sdk/pull/4011)
* Only display the first zxcvbn warning/suggestion
[\#4012](https://github.com/matrix-org/matrix-react-sdk/pull/4012)
* Log exceptions from accessSecretStorage
[\#4009](https://github.com/matrix-org/matrix-react-sdk/pull/4009)
* Add advanced option to keep secret storage in memory for session
[\#3995](https://github.com/matrix-org/matrix-react-sdk/pull/3995)
* Add shields to member list, move power label to text
[\#4006](https://github.com/matrix-org/matrix-react-sdk/pull/4006)
* Make encryption events into bubble-style tiles
[\#4005](https://github.com/matrix-org/matrix-react-sdk/pull/4005)
* Update copy when the user verifies their own devices
[\#4000](https://github.com/matrix-org/matrix-react-sdk/pull/4000)
* Use Sets instead of array scans and simplify hiding of invalid users when
inviting
[\#4004](https://github.com/matrix-org/matrix-react-sdk/pull/4004)
* Fix room completion for invited rooms and upgraded rooms
[\#4003](https://github.com/matrix-org/matrix-react-sdk/pull/4003)
* Make shields in UserInfo black if user isn't verified
[\#3999](https://github.com/matrix-org/matrix-react-sdk/pull/3999)
* Change verify user text
[\#3994](https://github.com/matrix-org/matrix-react-sdk/pull/3994)
* Disable all inputs in login form while busy, not just the submit button
[\#3996](https://github.com/matrix-org/matrix-react-sdk/pull/3996)
* fix SAS dialog width
[\#3993](https://github.com/matrix-org/matrix-react-sdk/pull/3993)
* Update placeholder in the composer when it gets changed
[\#3990](https://github.com/matrix-org/matrix-react-sdk/pull/3990)
* Send initial device display name on register
[\#3992](https://github.com/matrix-org/matrix-react-sdk/pull/3992)
* Update QR code handling for new spec
[\#3959](https://github.com/matrix-org/matrix-react-sdk/pull/3959)
* Apply the Olympic effect to SAS Emoji Verification
[\#3989](https://github.com/matrix-org/matrix-react-sdk/pull/3989)
* Pass an ID to the <Field/> as needed and fix div inside p nesting
[\#3988](https://github.com/matrix-org/matrix-react-sdk/pull/3988)
* Update user info for device and trust changes
[\#3987](https://github.com/matrix-org/matrix-react-sdk/pull/3987)
* Relax secret storage account data check
[\#3985](https://github.com/matrix-org/matrix-react-sdk/pull/3985)
* Fix various races that prevented the right panel being in the right state
for verifications
[\#3984](https://github.com/matrix-org/matrix-react-sdk/pull/3984)
* Fix verifying individual devices
[\#3986](https://github.com/matrix-org/matrix-react-sdk/pull/3986)
* Update from Weblate
[\#3982](https://github.com/matrix-org/matrix-react-sdk/pull/3982)
* Replace device with session in UI text
[\#3980](https://github.com/matrix-org/matrix-react-sdk/pull/3980)
* Add missing await causing promises to be leaked as room IDs
[\#3981](https://github.com/matrix-org/matrix-react-sdk/pull/3981)
* Change new session toast to unverified
[\#3978](https://github.com/matrix-org/matrix-react-sdk/pull/3978)
* Replace Verify button in UserInfo verification with "Learn more"
[\#3975](https://github.com/matrix-org/matrix-react-sdk/pull/3975)
* Don't peek until the matrix client is ready
[\#3979](https://github.com/matrix-org/matrix-react-sdk/pull/3979)
* Verification: don't block UI update on verification finishing
[\#3976](https://github.com/matrix-org/matrix-react-sdk/pull/3976)
* Adjust icons with in person with design
[\#3977](https://github.com/matrix-org/matrix-react-sdk/pull/3977)
* Update copy for right panel verification
[\#3973](https://github.com/matrix-org/matrix-react-sdk/pull/3973)
* Check for timeline in pre-join UISI path
[\#3972](https://github.com/matrix-org/matrix-react-sdk/pull/3972)
* Let users paste text if they've already started filtering invite targets
[\#3970](https://github.com/matrix-org/matrix-react-sdk/pull/3970)
* Filter event types when deciding on activity metrics for DM suggestions
[\#3969](https://github.com/matrix-org/matrix-react-sdk/pull/3969)
* Revert a change causing a login loop
[\#3971](https://github.com/matrix-org/matrix-react-sdk/pull/3971)
* Improve the docs for the event index and fix some type hints.
[\#3960](https://github.com/matrix-org/matrix-react-sdk/pull/3960)
* Automatically focus on the invite dialog input
[\#3968](https://github.com/matrix-org/matrix-react-sdk/pull/3968)
* Restore key backup in Complete Security dialog
[\#3966](https://github.com/matrix-org/matrix-react-sdk/pull/3966)
* Right Panel Verification improvements
[\#3967](https://github.com/matrix-org/matrix-react-sdk/pull/3967)
* Cross Signing Right Panel Verification Decoration
[\#3950](https://github.com/matrix-org/matrix-react-sdk/pull/3950)
* Passing refireParams actually prevented this from working
[\#3965](https://github.com/matrix-org/matrix-react-sdk/pull/3965)
* Start new key backup in security setup flow
[\#3964](https://github.com/matrix-org/matrix-react-sdk/pull/3964)
* Tweak styling of the unread indicator circle.
[\#3958](https://github.com/matrix-org/matrix-react-sdk/pull/3958)
* Add device IDs in user info tooltips
[\#3963](https://github.com/matrix-org/matrix-react-sdk/pull/3963)
* Improve encryption upgrade on login flow
[\#3962](https://github.com/matrix-org/matrix-react-sdk/pull/3962)
* Switch back to legacy decorators
[\#3961](https://github.com/matrix-org/matrix-react-sdk/pull/3961)
* Style bridge settings tab according to design
[\#3894](https://github.com/matrix-org/matrix-react-sdk/pull/3894)
* Fix skinning and babel targets
[\#3957](https://github.com/matrix-org/matrix-react-sdk/pull/3957)
* Enable cross-signing lab when key in storage
[\#3956](https://github.com/matrix-org/matrix-react-sdk/pull/3956)
* Add new session verification details dialog
[\#3953](https://github.com/matrix-org/matrix-react-sdk/pull/3953)
* Fix issue where we don't notice if our own devices shouldn't be trusted
[\#3949](https://github.com/matrix-org/matrix-react-sdk/pull/3949)
* Add separate component for post-auth security flows
[\#3951](https://github.com/matrix-org/matrix-react-sdk/pull/3951)
* Add more logging to settings watchers
[\#3952](https://github.com/matrix-org/matrix-react-sdk/pull/3952)
* Use https for recaptcha for all non-http protocols
[\#3944](https://github.com/matrix-org/matrix-react-sdk/pull/3944)
* Add status and management UI for the event indexer
[\#3672](https://github.com/matrix-org/matrix-react-sdk/pull/3672)
* Remove DM icons if `feature_cross_signing` is enabled; hide padlocks in DM
room headers
[\#3948](https://github.com/matrix-org/matrix-react-sdk/pull/3948)
* Stop rogue verification toast if you verify during login
[\#3943](https://github.com/matrix-org/matrix-react-sdk/pull/3943)
* Show incoming verification requests in the 'complete security' phase
[\#3942](https://github.com/matrix-org/matrix-react-sdk/pull/3942)
* Dismiss logged out device toasts
[\#3941](https://github.com/matrix-org/matrix-react-sdk/pull/3941)
* Verification nag toasts
[\#3940](https://github.com/matrix-org/matrix-react-sdk/pull/3940)
* Update from Weblate
[\#3947](https://github.com/matrix-org/matrix-react-sdk/pull/3947)
* Remember password for e2e bootstrapping
[\#3939](https://github.com/matrix-org/matrix-react-sdk/pull/3939)
* fix compound emoji
[\#3946](https://github.com/matrix-org/matrix-react-sdk/pull/3946)
* Setup flow for cross-signing on login / registration
[\#3937](https://github.com/matrix-org/matrix-react-sdk/pull/3937)
* Update profile avatar letter size
[\#3935](https://github.com/matrix-org/matrix-react-sdk/pull/3935)
* Hide default encryption algorithm
[\#3936](https://github.com/matrix-org/matrix-react-sdk/pull/3936)
* Resolve default export warnings from Webpack
[\#3938](https://github.com/matrix-org/matrix-react-sdk/pull/3938)
* Add null check for cross-signing info in verification panel
[\#3934](https://github.com/matrix-org/matrix-react-sdk/pull/3934)
* Add trace logging to figure out which component is causing weird events
[\#3926](https://github.com/matrix-org/matrix-react-sdk/pull/3926)
* Remove user lists feature flag, making it the default
[\#3906](https://github.com/matrix-org/matrix-react-sdk/pull/3906)
* Last bit of polish for user lists
[\#3925](https://github.com/matrix-org/matrix-react-sdk/pull/3925)
* QR code verification
[\#3871](https://github.com/matrix-org/matrix-react-sdk/pull/3871)
* Do less unnecessary work on CI
[\#3933](https://github.com/matrix-org/matrix-react-sdk/pull/3933)
* Re-enable stylelint on CI
[\#3932](https://github.com/matrix-org/matrix-react-sdk/pull/3932)
* Design pass for room icons
[\#3931](https://github.com/matrix-org/matrix-react-sdk/pull/3931)
* Populate the file panel using the event index if available.
[\#3858](https://github.com/matrix-org/matrix-react-sdk/pull/3858)
* Split AsyncWrapper out from Modal
[\#3928](https://github.com/matrix-org/matrix-react-sdk/pull/3928)
* Fix error in verification code on develop
[\#3930](https://github.com/matrix-org/matrix-react-sdk/pull/3930)
* Seperates out the padlock icon, and adds a tooltip
[\#3929](https://github.com/matrix-org/matrix-react-sdk/pull/3929)
* Cross Signing redesign for composer
[\#3910](https://github.com/matrix-org/matrix-react-sdk/pull/3910)
* Fix verifying your own devices with to_device messages
[\#3927](https://github.com/matrix-org/matrix-react-sdk/pull/3927)
* Room list reflects encryption state
[\#3908](https://github.com/matrix-org/matrix-react-sdk/pull/3908)
* Make the entire User Info scrollable, sticky close button
[\#3914](https://github.com/matrix-org/matrix-react-sdk/pull/3914)
* Remove riot logo from the security setup screens
[\#3916](https://github.com/matrix-org/matrix-react-sdk/pull/3916)
* Only say the session is verified if it is now verified
[\#3917](https://github.com/matrix-org/matrix-react-sdk/pull/3917)
* Hide password section if you can't change your password
[\#3924](https://github.com/matrix-org/matrix-react-sdk/pull/3924)
* Ensure a plaintext version of the composer ends up on the clipboard
[\#3922](https://github.com/matrix-org/matrix-react-sdk/pull/3922)
* Move & upgrade babel runtime into dependencies (like it wants)
[\#3920](https://github.com/matrix-org/matrix-react-sdk/pull/3920)
* Don't list every single alias when there's many
[\#3918](https://github.com/matrix-org/matrix-react-sdk/pull/3918)
* Try to populate user IDs even when the server's directory fails us
[\#3907](https://github.com/matrix-org/matrix-react-sdk/pull/3907)
* Remove .event property on verification request
[\#3912](https://github.com/matrix-org/matrix-react-sdk/pull/3912)
* Attempt to fix Safari + VoiceOver misunderstanding the timeline list
[\#3911](https://github.com/matrix-org/matrix-react-sdk/pull/3911)
* Enable encryption in DMs with device keys
[\#3913](https://github.com/matrix-org/matrix-react-sdk/pull/3913)
* Fix scrollable area and padding in user lists dialog
[\#3905](https://github.com/matrix-org/matrix-react-sdk/pull/3905)
* Add Reject & Ignore user button to invites view
[\#3909](https://github.com/matrix-org/matrix-react-sdk/pull/3909)
* Fix paragraph-awareness of the composer formatting features
[\#3891](https://github.com/matrix-org/matrix-react-sdk/pull/3891)
* Updated visuals for cross-signing bootstrap
[\#3903](https://github.com/matrix-org/matrix-react-sdk/pull/3903)
* Implement some parts of new cross signing bootstrap UI
[\#3897](https://github.com/matrix-org/matrix-react-sdk/pull/3897)
* Treat links as external in report content admin message
[\#3904](https://github.com/matrix-org/matrix-react-sdk/pull/3904)
* Be consistent about our settings svg, free the other one
[\#3902](https://github.com/matrix-org/matrix-react-sdk/pull/3902)
* Change prepublish script to prepare
[\#3899](https://github.com/matrix-org/matrix-react-sdk/pull/3899)
* Remove the react-sdk version
[\#3901](https://github.com/matrix-org/matrix-react-sdk/pull/3901)
* BuildKite: Retry end-to-end tests automatically once if they fail
[\#3900](https://github.com/matrix-org/matrix-react-sdk/pull/3900)
* Slash Command improvements around sending messages with leading slash
[\#3893](https://github.com/matrix-org/matrix-react-sdk/pull/3893)
* Support admin configurable message when reporting content
[\#3898](https://github.com/matrix-org/matrix-react-sdk/pull/3898)
* Don't warn on unverified users; ensured behavior stays the same with flags
off
[\#3896](https://github.com/matrix-org/matrix-react-sdk/pull/3896)
* Fix roving room list for resizer and ff tabstop a11y
[\#3895](https://github.com/matrix-org/matrix-react-sdk/pull/3895)
* Verify individual messages via cross-signing
[\#3875](https://github.com/matrix-org/matrix-react-sdk/pull/3875)
* Fix layering of dependencies in riot-web and e2e tests
[\#3882](https://github.com/matrix-org/matrix-react-sdk/pull/3882)
* Implement Roving Tab Index and Room List as TreeView
[\#3844](https://github.com/matrix-org/matrix-react-sdk/pull/3844)
* Move room header shields over the avatar for the room
[\#3888](https://github.com/matrix-org/matrix-react-sdk/pull/3888)
* Fix toast icon to prevent clipping
[\#3890](https://github.com/matrix-org/matrix-react-sdk/pull/3890)
* Only show devices and verify actions in E2EE rooms
[\#3889](https://github.com/matrix-org/matrix-react-sdk/pull/3889)
* Change user info verification checks to use cross-signing
[\#3887](https://github.com/matrix-org/matrix-react-sdk/pull/3887)
* Fix click-to-ping not inserting colon if composer non-empty
[\#3886](https://github.com/matrix-org/matrix-react-sdk/pull/3886)
* Fix emoticon space completion for upper case emoticons like :D xD
[\#3884](https://github.com/matrix-org/matrix-react-sdk/pull/3884)
* Repair cross-signing panel with async status
[\#3880](https://github.com/matrix-org/matrix-react-sdk/pull/3880)
* Remove temporary key backup button
[\#3878](https://github.com/matrix-org/matrix-react-sdk/pull/3878)
* Score users who have recently spoken higher in invite suggestions
[\#3866](https://github.com/matrix-org/matrix-react-sdk/pull/3866)
* Initial support for verification in right panel
[\#3796](https://github.com/matrix-org/matrix-react-sdk/pull/3796)
* Prevent the invite dialog from jumping around when elements change
[\#3868](https://github.com/matrix-org/matrix-react-sdk/pull/3868)
* Add prepublish script
[\#3876](https://github.com/matrix-org/matrix-react-sdk/pull/3876)
Changes in [2.0.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.0.0) (2020-01-27)
===================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.0.0-rc.2...v2.0.0)

27
docs/usercontent.md Normal file
View File

@ -0,0 +1,27 @@
# Usercontent
While decryption itself is safe to be done without a sandbox,
letting the browser and user interact with the resulting data may be dangerous,
previously `usercontent.riot.im` was used to act as a sandbox on a different origin to close the attack surface,
it is now possible to do by using a combination of a sandboxed iframe and some code written into the app which consumes this SDK.
Usercontent is an iframe sandbox target for allowing a user to safely download a decrypted attachment from a sandboxed origin where it cannot be used to XSS your riot session out from under you.
Its function is to create an Object URL for the user/browser to use but bound to an origin different to that of the riot instance to protect against XSS.
It exposes a function over a postMessage API, when sent an object with the matching fields to render a download link with the Object URL:
```json5
{
"imgSrc": "", // the src of the image to display in the download link
"imgStyle": "", // the style to apply to the image
"style": "", // the style to apply to the download link
"download": "", // download attribute to pass to the <a/> tag
"textContent": "", // the text to put inside the download link
"blob": "", // the data blob to wrap in an object url and allow the user to download
}
```
If only imgSrc, imgStyle and style are passed then just update the existing link without overwriting other things about it.
It is expected that this target be available at `usercontent/` relative to the root of the app, this can be seen in riot-web's webpack config.

View File

@ -1,6 +1,6 @@
{
"name": "matrix-react-sdk",
"version": "2.0.0",
"version": "2.1.0",
"description": "SDK for matrix.org using React",
"author": "matrix.org",
"repository": {

View File

@ -9,4 +9,51 @@ set -e
cd `dirname $0`
for i in matrix-js-sdk
do
depver=`cat package.json | jq -r .dependencies[\"$i\"]`
latestver=`yarn info -s $i dist-tags.next`
if [ "$depver" != "$latestver" ]
then
echo "The latest version of $i is $latestver but package.json depends on $depver."
echo -n "Type 'u' to auto-upgrade, 'c' to continue anyway, or 'a' to abort:"
read resp
if [ "$resp" != "u" ] && [ "$resp" != "c" ]
then
echo "Aborting."
exit 1
fi
if [ "$resp" == "u" ]
then
echo "Upgrading $i to $latestver..."
yarn add -E $i@$latestver
git add -u
# The `-e` flag opens the editor and gives you a chance to check
# the upgrade for correctness.
git commit -m "Upgrade $i to $latestver" -e
fi
fi
done
exec ./node_modules/matrix-js-sdk/release.sh -z "$@"
release="${1#v}"
prerelease=0
# We check if this build is a prerelease by looking to
# see if the version has a hyphen in it. Crude,
# but semver doesn't support postreleases so anything
# with a hyphen is a prerelease.
echo $release | grep -q '-' && prerelease=1
if [ $prerelease -eq 0 ]
then
# For a release, reset SDK deps back to the `develop` branch.
for i in matrix-js-sdk
do
echo "Resetting $i to develop branch..."
yarn add github:matrix-org/$i#develop
git add -u
git commit -m "Reset $i back to develop branch"
done
git push origin develop
fi

View File

@ -57,6 +57,8 @@ function getRedactedUrl() {
}
const customVariables = {
// The Matomo installation at https://matomo.riot.im is currently configured
// with a limit of 10 custom variables.
'App Platform': {
id: 1,
expl: _td('The platform you\'re on'),
@ -64,7 +66,7 @@ const customVariables = {
},
'App Version': {
id: 2,
expl: _td('The version of Riot.im'),
expl: _td('The version of Riot'),
example: '15.0.0',
},
'User Type': {
@ -87,20 +89,25 @@ const customVariables = {
expl: _td('Whether or not you\'re using the Richtext mode of the Rich Text Editor'),
example: 'off',
},
'Breadcrumbs': {
id: 9,
expl: _td("Whether or not you're using the 'breadcrumbs' feature (avatars above the room list)"),
example: 'disabled',
},
'Homeserver URL': {
id: 7,
expl: _td('Your homeserver\'s URL'),
example: 'https://matrix.org',
},
'Identity Server URL': {
'Touch Input': {
id: 8,
expl: _td('Your identity server\'s URL'),
example: 'https://vector.im',
expl: _td("Whether you're using Riot on a device where touch is the primary input mechanism"),
example: 'false',
},
'Breadcrumbs': {
id: 9,
expl: _td("Whether or not you're using the 'breadcrumbs' feature (avatars above the room list)"),
example: 'disabled',
},
'Installed PWA': {
id: 10,
expl: _td("Whether you're using Riot as an installed Progressive Web App"),
example: 'false',
},
};
@ -190,6 +197,20 @@ class Analytics {
this._setVisitVariable('Instance', window.location.pathname);
}
let installedPWA = "unknown";
try {
// Known to work at least for desktop Chrome
installedPWA = window.matchMedia('(display-mode: standalone)').matches;
} catch (e) { }
this._setVisitVariable('Installed PWA', installedPWA);
let touchInput = "unknown";
try {
// MDN claims broad support across browsers
touchInput = window.matchMedia('(pointer: coarse)').matches;
} catch (e) { }
this._setVisitVariable('Touch Input', touchInput);
// start heartbeat
this._heartbeatIntervalID = window.setInterval(this.ping.bind(this), HEARTBEAT_INTERVAL);
}
@ -291,11 +312,9 @@ class Analytics {
if (!config.piwik) return;
const whitelistedHSUrls = config.piwik.whitelistedHSUrls || [];
const whitelistedISUrls = config.piwik.whitelistedISUrls || [];
this._setVisitVariable('User Type', isGuest ? 'Guest' : 'Logged In');
this._setVisitVariable('Homeserver URL', whitelistRedact(whitelistedHSUrls, homeserverUrl));
this._setVisitVariable('Identity Server URL', whitelistRedact(whitelistedISUrls, identityServerUrl));
}
setBreadcrumbs(state) {
@ -328,7 +347,7 @@ class Analytics {
},
),
},
{ expl: _td('Your User Agent'), value: navigator.userAgent },
{ expl: _td('Your user agent'), value: navigator.userAgent },
{ expl: _td('Your device resolution'), value: resolution },
];
@ -337,7 +356,7 @@ class Analytics {
title: _t('Analytics'),
description: <div className="mx_AnalyticsModal">
<div>
{ _t('The information being sent to us to help make Riot.im better includes:') }
{ _t('The information being sent to us to help make Riot better includes:') }
</div>
<table>
{ rows.map((row) => <tr key={row[0]}>

View File

@ -435,7 +435,7 @@ async function _doSetLoggedIn(credentials, clearStorage) {
}
}
Analytics.setLoggedIn(credentials.guest, credentials.homeserverUrl, credentials.identityServerUrl);
Analytics.setLoggedIn(credentials.guest, credentials.homeserverUrl);
if (localStorage) {
try {

View File

@ -413,10 +413,6 @@ export default class MessagePanel extends React.Component {
};
_getEventTiles() {
const DateSeparator = sdk.getComponent('messages.DateSeparator');
const EventListSummary = sdk.getComponent('views.elements.EventListSummary');
const MemberEventListSummary = sdk.getComponent('views.elements.MemberEventListSummary');
this.eventNodes = {};
let i;
@ -458,199 +454,48 @@ export default class MessagePanel extends React.Component {
this._readReceiptsByEvent = this._getReadReceiptsByShownEvent();
}
let grouper = null;
for (i = 0; i < this.props.events.length; i++) {
const mxEv = this.props.events[i];
const eventId = mxEv.getId();
const last = (mxEv === lastShownEvent);
// Wrap initial room creation events into an EventListSummary
// Grouping only events sent by the same user that sent the `m.room.create` and only until
// the first non-state event or membership event which is not regarding the sender of the `m.room.create` event
const shouldGroup = (ev) => {
if (ev.getType() === "m.room.member"
&& (ev.getStateKey() !== mxEv.getSender() || ev.getContent()["membership"] !== "join")) {
return false;
}
if (ev.isState() && ev.getSender() === mxEv.getSender()) {
return true;
}
return false;
};
// events that we include in the group but then eject out and place
// above the group.
const shouldEject = (ev) => {
if (ev.getType() === "m.room.encryption") return true;
return false;
};
if (mxEv.getType() === "m.room.create") {
let summaryReadMarker = null;
const ts1 = mxEv.getTs();
if (this._wantsDateSeparator(prevEvent, mxEv.getDate())) {
const dateSeparator = <li key={ts1+'~'}><DateSeparator key={ts1+'~'} ts={ts1} /></li>;
ret.push(dateSeparator);
if (grouper) {
if (grouper.shouldGroup(mxEv)) {
grouper.add(mxEv);
continue;
} else {
// not part of group, so get the group tiles, close the
// group, and continue like a normal event
ret.push(...grouper.getTiles());
prevEvent = grouper.getNewPrevEvent();
grouper = null;
}
// If RM event is the first in the summary, append the RM after the summary
summaryReadMarker = summaryReadMarker || this._readMarkerForEvent(mxEv.getId());
// If this m.room.create event should be shown (room upgrade) then show it before the summary
if (this._shouldShowEvent(mxEv)) {
// pass in the mxEv as prevEvent as well so no extra DateSeparator is rendered
ret.push(...this._getTilesForEvent(mxEv, mxEv, false));
}
const summarisedEvents = []; // Don't add m.room.create here as we don't want it inside the summary
const ejectedEvents = [];
for (;i + 1 < this.props.events.length; i++) {
const collapsedMxEv = this.props.events[i + 1];
// Ignore redacted/hidden member events
if (!this._shouldShowEvent(collapsedMxEv)) {
// If this hidden event is the RM and in or at end of a summary put RM after the summary.
summaryReadMarker = summaryReadMarker || this._readMarkerForEvent(collapsedMxEv.getId());
continue;
}
if (!shouldGroup(collapsedMxEv) || this._wantsDateSeparator(mxEv, collapsedMxEv.getDate())) {
break;
}
// If RM event is in the summary, mark it as such and the RM will be appended after the summary.
summaryReadMarker = summaryReadMarker || this._readMarkerForEvent(collapsedMxEv.getId());
if (shouldEject(collapsedMxEv)) {
ejectedEvents.push(collapsedMxEv);
} else {
summarisedEvents.push(collapsedMxEv);
}
}
// At this point, i = the index of the last event in the summary sequence
const eventTiles = summarisedEvents.map((e) => {
// In order to prevent DateSeparators from appearing in the expanded form
// of EventListSummary, render each member event as if the previous
// one was itself. This way, the timestamp of the previous event === the
// timestamp of the current event, and no DateSeparator is inserted.
return this._getTilesForEvent(e, e, e === lastShownEvent);
}).reduce((a, b) => a.concat(b), []);
for (const ejected of ejectedEvents) {
ret.push(...this._getTilesForEvent(mxEv, ejected, last));
}
// Get sender profile from the latest event in the summary as the m.room.create doesn't contain one
const ev = this.props.events[i];
ret.push(<EventListSummary
key="roomcreationsummary"
events={summarisedEvents}
onToggle={this._onHeightChanged} // Update scroll state
summaryMembers={[ev.sender]}
summaryText={_t("%(creator)s created and configured the room.", {
creator: ev.sender ? ev.sender.name : ev.getSender(),
})}
>
{ eventTiles }
</EventListSummary>);
if (summaryReadMarker) {
ret.push(summaryReadMarker);
}
prevEvent = mxEv;
continue;
}
const wantTile = this._shouldShowEvent(mxEv);
// Wrap consecutive member events in a ListSummary, ignore if redacted
if (isMembershipChange(mxEv) && wantTile) {
let summaryReadMarker = null;
const ts1 = mxEv.getTs();
// Ensure that the key of the MemberEventListSummary does not change with new
// member events. This will prevent it from being re-created unnecessarily, and
// instead will allow new props to be provided. In turn, the shouldComponentUpdate
// method on MELS can be used to prevent unnecessary renderings.
//
// Whilst back-paginating with a MELS at the top of the panel, prevEvent will be null,
// so use the key "membereventlistsummary-initial". Otherwise, use the ID of the first
// membership event, which will not change during forward pagination.
const key = "membereventlistsummary-" + (prevEvent ? mxEv.getId() : "initial");
if (this._wantsDateSeparator(prevEvent, mxEv.getDate())) {
const dateSeparator = <li key={ts1+'~'}><DateSeparator key={ts1+'~'} ts={ts1} /></li>;
ret.push(dateSeparator);
for (const Grouper of groupers) {
if (Grouper.canStartGroup(this, mxEv)) {
grouper = new Grouper(this, mxEv, prevEvent, lastShownEvent);
}
// If RM event is the first in the MELS, append the RM after MELS
summaryReadMarker = summaryReadMarker || this._readMarkerForEvent(mxEv.getId());
const summarisedEvents = [mxEv];
for (;i + 1 < this.props.events.length; i++) {
const collapsedMxEv = this.props.events[i + 1];
// Ignore redacted/hidden member events
if (!this._shouldShowEvent(collapsedMxEv)) {
// If this hidden event is the RM and in or at end of a MELS put RM after MELS.
summaryReadMarker = summaryReadMarker || this._readMarkerForEvent(collapsedMxEv.getId());
continue;
}
if (!isMembershipChange(collapsedMxEv) ||
this._wantsDateSeparator(mxEv, collapsedMxEv.getDate())) {
break;
}
// If RM event is in MELS mark it as such and the RM will be appended after MELS.
summaryReadMarker = summaryReadMarker || this._readMarkerForEvent(collapsedMxEv.getId());
summarisedEvents.push(collapsedMxEv);
}
let highlightInMels = false;
// At this point, i = the index of the last event in the summary sequence
let eventTiles = summarisedEvents.map((e) => {
if (e.getId() === this.props.highlightedEventId) {
highlightInMels = true;
}
// In order to prevent DateSeparators from appearing in the expanded form
// of MemberEventListSummary, render each member event as if the previous
// one was itself. This way, the timestamp of the previous event === the
// timestamp of the current event, and no DateSeparator is inserted.
return this._getTilesForEvent(e, e, e === lastShownEvent);
}).reduce((a, b) => a.concat(b), []);
if (eventTiles.length === 0) {
eventTiles = null;
}
ret.push(<MemberEventListSummary key={key}
events={summarisedEvents}
onToggle={this._onHeightChanged} // Update scroll state
startExpanded={highlightInMels}
>
{ eventTiles }
</MemberEventListSummary>);
if (summaryReadMarker) {
ret.push(summaryReadMarker);
}
prevEvent = mxEv;
continue;
}
if (!grouper) {
const wantTile = this._shouldShowEvent(mxEv);
if (wantTile) {
// make sure we unpack the array returned by _getTilesForEvent,
// otherwise react will auto-generate keys and we will end up
// replacing all of the DOM elements every time we paginate.
ret.push(...this._getTilesForEvent(prevEvent, mxEv, last));
prevEvent = mxEv;
}
if (wantTile) {
// make sure we unpack the array returned by _getTilesForEvent,
// otherwise react will auto-generate keys and we will end up
// replacing all of the DOM elements every time we paginate.
ret.push(...this._getTilesForEvent(prevEvent, mxEv, last));
prevEvent = mxEv;
const readMarker = this._readMarkerForEvent(eventId, i >= lastShownNonLocalEchoIndex);
if (readMarker) ret.push(readMarker);
}
}
const readMarker = this._readMarkerForEvent(eventId, i >= lastShownNonLocalEchoIndex);
if (readMarker) ret.push(readMarker);
if (grouper) {
ret.push(...grouper.getTiles());
}
return ret;
@ -961,3 +806,222 @@ export default class MessagePanel extends React.Component {
);
}
}
/* Grouper classes determine when events can be grouped together in a summary.
* Groupers should have the following methods:
* - canStartGroup (static): determines if a new group should be started with the
* given event
* - shouldGroup: determines if the given event should be added to an existing group
* - add: adds an event to an existing group (should only be called if shouldGroup
* return true)
* - getTiles: returns the tiles that represent the group
* - getNewPrevEvent: returns the event that should be used as the new prevEvent
* when determining things such as whether a date separator is necessary
*/
// Wrap initial room creation events into an EventListSummary
// Grouping only events sent by the same user that sent the `m.room.create` and only until
// the first non-state event or membership event which is not regarding the sender of the `m.room.create` event
class CreationGrouper {
static canStartGroup = function(panel, ev) {
return ev.getType() === "m.room.create";
};
constructor(panel, createEvent, prevEvent, lastShownEvent) {
this.panel = panel;
this.createEvent = createEvent;
this.prevEvent = prevEvent;
this.lastShownEvent = lastShownEvent;
this.events = [];
// events that we include in the group but then eject out and place
// above the group.
this.ejectedEvents = [];
this.readMarker = panel._readMarkerForEvent(createEvent.getId());
}
shouldGroup(ev) {
const panel = this.panel;
const createEvent = this.createEvent;
if (!panel._shouldShowEvent(ev)) {
this.readMarker = this.readMarker || panel._readMarkerForEvent(ev.getId());
return true;
}
if (panel._wantsDateSeparator(this.createEvent, ev.getDate())) {
return false;
}
if (ev.getType() === "m.room.member"
&& (ev.getStateKey() !== createEvent.getSender() || ev.getContent()["membership"] !== "join")) {
return false;
}
if (ev.isState() && ev.getSender() === createEvent.getSender()) {
return true;
}
return false;
}
add(ev) {
const panel = this.panel;
this.readMarker = this.readMarker || panel._readMarkerForEvent(ev.getId());
if (!panel._shouldShowEvent(ev)) {
return;
}
if (ev.getType() === "m.room.encryption") {
this.ejectedEvents.push(ev);
} else {
this.events.push(ev);
}
}
getTiles() {
const DateSeparator = sdk.getComponent('messages.DateSeparator');
const EventListSummary = sdk.getComponent('views.elements.EventListSummary');
const panel = this.panel;
const ret = [];
const createEvent = this.createEvent;
const lastShownEvent = this.lastShownEvent;
if (panel._wantsDateSeparator(this.prevEvent, createEvent.getDate())) {
const ts = createEvent.getTs();
ret.push(
<li key={ts+'~'}><DateSeparator key={ts+'~'} ts={ts} /></li>,
);
}
// If this m.room.create event should be shown (room upgrade) then show it before the summary
if (panel._shouldShowEvent(createEvent)) {
// pass in the createEvent as prevEvent as well so no extra DateSeparator is rendered
ret.push(...panel._getTilesForEvent(createEvent, createEvent, false));
}
for (const ejected of this.ejectedEvents) {
ret.push(...panel._getTilesForEvent(
createEvent, ejected, createEvent === lastShownEvent,
));
}
const eventTiles = this.events.map((e) => {
// In order to prevent DateSeparators from appearing in the expanded form
// of EventListSummary, render each member event as if the previous
// one was itself. This way, the timestamp of the previous event === the
// timestamp of the current event, and no DateSeparator is inserted.
return panel._getTilesForEvent(e, e, e === lastShownEvent);
}).reduce((a, b) => a.concat(b), []);
// Get sender profile from the latest event in the summary as the m.room.create doesn't contain one
const ev = this.events[this.events.length - 1];
ret.push(
<EventListSummary
key="roomcreationsummary"
events={this.events}
onToggle={panel._onHeightChanged} // Update scroll state
summaryMembers={[ev.sender]}
summaryText={_t("%(creator)s created and configured the room.", {
creator: ev.sender ? ev.sender.name : ev.getSender(),
})}
>
{ eventTiles }
</EventListSummary>,
);
if (this.readMarker) {
ret.push(this.readMarker);
}
return ret;
}
getNewPrevEvent() {
return this.createEvent;
}
}
// Wrap consecutive member events in a ListSummary, ignore if redacted
class MemberGrouper {
static canStartGroup = function(panel, ev) {
return panel._shouldShowEvent(ev) && isMembershipChange(ev);
}
constructor(panel, ev, prevEvent, lastShownEvent) {
this.panel = panel;
this.readMarker = panel._readMarkerForEvent(ev.getId());
this.events = [ev];
this.prevEvent = prevEvent;
this.lastShownEvent = lastShownEvent;
}
shouldGroup(ev) {
return isMembershipChange(ev);
}
add(ev) {
this.readMarker = this.readMarker || this.panel._readMarkerForEvent(ev.getId());
this.events.push(ev);
}
getTiles() {
const DateSeparator = sdk.getComponent('messages.DateSeparator');
const MemberEventListSummary = sdk.getComponent('views.elements.MemberEventListSummary');
const panel = this.panel;
const lastShownEvent = this.lastShownEvent;
const ret = [];
if (panel._wantsDateSeparator(this.prevEvent, this.events[0].getDate())) {
const ts = this.events[0].getTs();
ret.push(
<li key={ts+'~'}><DateSeparator key={ts+'~'} ts={ts} /></li>,
);
}
// Ensure that the key of the MemberEventListSummary does not change with new
// member events. This will prevent it from being re-created unnecessarily, and
// instead will allow new props to be provided. In turn, the shouldComponentUpdate
// method on MELS can be used to prevent unnecessary renderings.
//
// Whilst back-paginating with a MELS at the top of the panel, prevEvent will be null,
// so use the key "membereventlistsummary-initial". Otherwise, use the ID of the first
// membership event, which will not change during forward pagination.
const key = "membereventlistsummary-" + (
this.prevEvent ? this.events[0].getId() : "initial"
);
let highlightInMels;
let eventTiles = this.events.map((e) => {
if (e.getId() === panel.props.highlightedEventId) {
highlightInMels = true;
}
// In order to prevent DateSeparators from appearing in the expanded form
// of MemberEventListSummary, render each member event as if the previous
// one was itself. This way, the timestamp of the previous event === the
// timestamp of the current event, and no DateSeparator is inserted.
return panel._getTilesForEvent(e, e, e === lastShownEvent);
}).reduce((a, b) => a.concat(b), []);
if (eventTiles.length === 0) {
eventTiles = null;
}
ret.push(
<MemberEventListSummary key={key}
events={this.events}
onToggle={panel._onHeightChanged} // Update scroll state
startExpanded={highlightInMels}
>
{ eventTiles }
</MemberEventListSummary>,
);
if (this.readMarker) {
ret.push(this.readMarker);
}
return ret;
}
getNewPrevEvent() {
return this.events[0];
}
}
// all the grouper classes that we use
const groupers = [CreationGrouper, MemberGrouper];

View File

@ -31,7 +31,7 @@ import dis from "../../../dispatcher";
import IdentityAuthClient from "../../../IdentityAuthClient";
import Modal from "../../../Modal";
import {humanizeTime} from "../../../utils/humanize";
import createRoom from "../../../createRoom";
import createRoom, {canEncryptToAllUsers} from "../../../createRoom";
import {inviteMultipleToRoom} from "../../../RoomInvite";
import SettingsStore from '../../../settings/SettingsStore';
@ -535,11 +535,7 @@ export default class InviteDialog extends React.PureComponent {
// Check whether all users have uploaded device keys before.
// If so, enable encryption in the new room.
const client = MatrixClientPeg.get();
const usersToDevicesMap = await client.downloadKeys(targetIds);
const allHaveDeviceKeys = Object.values(usersToDevicesMap).every(devices => {
// `devices` is an object of the form { deviceId: deviceInfo, ... }.
return Object.keys(devices).length > 0;
});
const allHaveDeviceKeys = await canEncryptToAllUsers(client, targetIds);
if (allHaveDeviceKeys) {
createRoomOptions.encryption = true;
}

View File

@ -26,7 +26,7 @@ import {decryptFile} from '../../../utils/DecryptFile';
import Tinter from '../../../Tinter';
import request from 'browser-request';
import Modal from '../../../Modal';
import SdkConfig from "../../../SdkConfig";
import AccessibleButton from "../elements/AccessibleButton";
// A cached tinted copy of require("../../../../res/img/download.svg")
@ -94,84 +94,6 @@ Tinter.registerTintable(updateTintedDownloadImage);
// The downside of using a second domain is that it complicates hosting,
// the downside of using a sandboxed iframe is that the browers are overly
// restrictive in what you are allowed to do with the generated URL.
//
// For now given how unusable the blobs generated in sandboxed iframes are we
// default to using a renderer hosted on "usercontent.riot.im". This is
// overridable so that people running their own version of the client can
// choose a different renderer.
//
// To that end the current version of the blob generation is the following
// html:
//
// <html><head><script>
// var params = window.location.search.substring(1).split('&');
// var lockOrigin;
// for (var i = 0; i < params.length; ++i) {
// var parts = params[i].split('=');
// if (parts[0] == 'origin') lockOrigin = decodeURIComponent(parts[1]);
// }
// window.onmessage=function(e){
// if (lockOrigin === undefined || e.origin === lockOrigin) eval("("+e.data.code+")")(e);
// }
// </script></head><body></body></html>
//
// This waits to receive a message event sent using the window.postMessage API.
// When it receives the event it evals a javascript function in data.code and
// runs the function passing the event as an argument. This version adds
// support for a query parameter controlling the origin from which messages
// will be processed as an extra layer of security (note that the default URL
// is still 'v1' since it is backwards compatible).
//
// In particular it means that the rendering function can be written as a
// ordinary javascript function which then is turned into a string using
// toString().
//
const DEFAULT_CROSS_ORIGIN_RENDERER = "https://usercontent.riot.im/v1.html";
/**
* Render the attachment inside the iframe.
* We can't use imported libraries here so this has to be vanilla JS.
*/
function remoteRender(event) {
const data = event.data;
const img = document.createElement("img");
img.id = "img";
img.src = data.imgSrc;
const a = document.createElement("a");
a.id = "a";
a.rel = data.rel;
a.target = data.target;
a.download = data.download;
a.style = data.style;
a.style.fontFamily = "Arial, Helvetica, Sans-Serif";
a.href = window.URL.createObjectURL(data.blob);
a.appendChild(img);
a.appendChild(document.createTextNode(data.textContent));
const body = document.body;
// Don't display scrollbars if the link takes more than one line
// to display.
body.style = "margin: 0px; overflow: hidden";
body.appendChild(a);
}
/**
* Update the tint inside the iframe.
* We can't use imported libraries here so this has to be vanilla JS.
*/
function remoteSetTint(event) {
const data = event.data;
const img = document.getElementById("img");
img.src = data.imgSrc;
img.style = data.imgStyle;
const a = document.getElementById("a");
a.style = data.style;
}
/**
* Get the current CSS style for a DOMElement.
@ -283,7 +205,6 @@ export default createReactClass({
// will be inside the iframe so we wont be able to update
// it directly.
this._iframe.current.contentWindow.postMessage({
code: remoteSetTint.toString(),
imgSrc: tintedDownloadImageURL,
style: computedStyle(this._dummyLink.current),
}, "*");
@ -306,7 +227,7 @@ export default createReactClass({
// Wait for the user to click on the link before downloading
// and decrypting the attachment.
let decrypting = false;
const decrypt = () => {
const decrypt = (e) => {
if (decrypting) {
return false;
}
@ -323,16 +244,15 @@ export default createReactClass({
});
}).finally(() => {
decrypting = false;
return;
});
};
return (
<span className="mx_MFileBody">
<div className="mx_MFileBody_download">
<a href="javascript:void(0)" onClick={decrypt}>
<AccessibleButton onClick={decrypt}>
{ _t("Decrypt %(text)s", { text: text }) }
</a>
</AccessibleButton>
</div>
</span>
);
@ -341,7 +261,6 @@ export default createReactClass({
// When the iframe loads we tell it to render a download link
const onIframeLoad = (ev) => {
ev.target.contentWindow.postMessage({
code: remoteRender.toString(),
imgSrc: tintedDownloadImageURL,
style: computedStyle(this._dummyLink.current),
blob: this.state.decryptedBlob,
@ -349,19 +268,13 @@ export default createReactClass({
// will have the correct name when the user tries to download it.
// We can't provide a Content-Disposition header like we would for HTTP.
download: fileName,
rel: "noopener",
target: "_blank",
textContent: _t("Download %(text)s", { text: text }),
}, "*");
};
// If the attachment is encryped then put the link inside an iframe.
let renderer_url = DEFAULT_CROSS_ORIGIN_RENDERER;
const appConfig = SdkConfig.get();
if (appConfig && appConfig.cross_origin_renderer_url) {
renderer_url = appConfig.cross_origin_renderer_url;
}
renderer_url += "?origin=" + encodeURIComponent(window.location.origin);
const url = "usercontent/"; // XXX: this path should probably be passed from the skin
// If the attachment is encrypted then put the link inside an iframe.
return (
<span className="mx_MFileBody">
<div className="mx_MFileBody_download">
@ -373,7 +286,11 @@ export default createReactClass({
*/ }
<a ref={this._dummyLink} />
</div>
<iframe src={renderer_url} onLoad={onIframeLoad} ref={this._iframe} />
<iframe
src={`${url}?origin=${encodeURIComponent(window.location.origin)}`}
onLoad={onIframeLoad}
ref={this._iframe}
sandbox="allow-scripts allow-downloads" />
</div>
</span>
);

View File

@ -59,7 +59,6 @@ export default class MKeyVerificationRequest extends React.Component {
};
_onAcceptClicked = async () => {
this.setState({acceptOrCancelClicked: true});
const request = this.props.mxEvent.verificationRequest;
if (request) {
try {
@ -72,7 +71,6 @@ export default class MKeyVerificationRequest extends React.Component {
};
_onRejectClicked = async () => {
this.setState({acceptOrCancelClicked: true});
const request = this.props.mxEvent.verificationRequest;
if (request) {
try {
@ -96,10 +94,20 @@ export default class MKeyVerificationRequest extends React.Component {
_cancelledLabel(userId) {
const client = MatrixClientPeg.get();
const myUserId = client.getUserId();
const {cancellationCode} = this.props.mxEvent.verificationRequest;
const declined = cancellationCode === "m.user";
if (userId === myUserId) {
return _t("You cancelled");
if (declined) {
return _t("You declined");
} else {
return _t("You cancelled");
}
} else {
return _t("%(name)s cancelled", {name: getNameForEventRoom(userId, this.props.mxEvent.getRoomId())});
if (declined) {
return _t("%(name)s declined", {name: getNameForEventRoom(userId, this.props.mxEvent.getRoomId())});
} else {
return _t("%(name)s cancelled", {name: getNameForEventRoom(userId, this.props.mxEvent.getRoomId())});
}
}
}
@ -118,15 +126,19 @@ export default class MKeyVerificationRequest extends React.Component {
let subtitle;
let stateNode;
const accepted = request.ready || request.started || request.done;
if (accepted || request.cancelled) {
if (!request.canAccept) {
let stateLabel;
const accepted = request.ready || request.started || request.done;
if (accepted) {
stateLabel = (<AccessibleButton onClick={this._openRequest}>
{this._acceptedLabel(request.receivingUserId)}
</AccessibleButton>);
} else {
} else if (request.cancelled) {
stateLabel = this._cancelledLabel(request.cancellingUserId);
} else if (request.accepting) {
stateLabel = _t("accepting …");
} else if (request.declining) {
stateLabel = _t("declining …");
}
stateNode = (<div className="mx_cryptoEvent_state">{stateLabel}</div>);
}
@ -137,11 +149,10 @@ export default class MKeyVerificationRequest extends React.Component {
_t("%(name)s wants to verify", {name})}</div>);
subtitle = (<div className="mx_cryptoEvent_subtitle">{
userLabelForEventRoom(request.requestingUserId, mxEvent.getRoomId())}</div>);
if (request.requested && !request.observeOnly) {
const disabled = this.state.acceptOrCancelClicked;
if (request.canAccept) {
stateNode = (<div className="mx_cryptoEvent_buttons">
<FormButton disabled={disabled} kind="danger" onClick={this._onRejectClicked} label={_t("Decline")} />
<FormButton disabled={disabled} onClick={this._onAcceptClicked} label={_t("Accept")} />
<FormButton kind="danger" onClick={this._onRejectClicked} label={_t("Decline")} />
<FormButton onClick={this._onAcceptClicked} label={_t("Accept")} />
</div>);
}
} else { // request sent by us

View File

@ -23,7 +23,7 @@ import {MatrixClientPeg} from "../../../MatrixClientPeg";
import {ensureDMExists} from "../../../createRoom";
import {useEventEmitter} from "../../../hooks/useEventEmitter";
import Modal from "../../../Modal";
import {PHASE_REQUESTED} from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
import {PHASE_REQUESTED, PHASE_UNSENT} from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
import * as sdk from "../../../index";
import {_t} from "../../../languageHandler";
@ -69,9 +69,10 @@ const EncryptionPanel = ({verificationRequest, member, onClose, layout}) => {
const roomId = await ensureDMExists(cli, member.userId);
const verificationRequest = await cli.requestVerificationDM(member.userId, roomId);
setRequest(verificationRequest);
setPhase(verificationRequest.phase);
}, [member.userId]);
const requested = request && (phase === PHASE_REQUESTED || phase === undefined);
const requested = request && (phase === PHASE_REQUESTED || phase === PHASE_UNSENT || phase === undefined);
if (!request || requested) {
return <EncryptionInfo onStartVerification={onStartVerification} member={member} pending={requested} />;
} else {

View File

@ -25,7 +25,7 @@ import dis from '../../../dispatcher';
import Modal from '../../../Modal';
import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
import createRoom from '../../../createRoom';
import createRoom, {findDMForUser} from '../../../createRoom';
import DMRoomMap from '../../../utils/DMRoomMap';
import AccessibleButton from '../elements/AccessibleButton';
import SdkConfig from '../../../SdkConfig';
@ -169,10 +169,19 @@ async function verifyDevice(userId, device) {
}
function verifyUser(user) {
const cli = MatrixClientPeg.get();
const dmRoom = findDMForUser(cli, user.userId);
let existingRequest;
if (dmRoom) {
existingRequest = cli.findVerificationRequestDMInProgress(dmRoom.roomId);
}
dis.dispatch({
action: "set_right_panel_phase",
phase: RIGHT_PANEL_PHASES.EncryptionPanel,
refireParams: {member: user},
refireParams: {
member: user,
verificationRequest: existingRequest,
},
});
}

View File

@ -19,6 +19,8 @@ import PropTypes from "prop-types";
import * as sdk from '../../../index';
import {verificationMethods} from 'matrix-js-sdk/src/crypto';
import {SCAN_QR_CODE_METHOD} from "matrix-js-sdk/src/crypto/verification/QRCode";
import VerificationQRCode from "../elements/crypto/VerificationQRCode";
import {_t} from "../../../languageHandler";
import E2EIcon from "../rooms/E2EIcon";
@ -54,7 +56,9 @@ export default class VerificationPanel extends React.PureComponent {
qrCodeProps: null, // generated by the VerificationQRCode component itself
};
this._hasVerifier = false;
this._generateQRCodeProps(props.request);
if (this.props.request.otherPartySupportsMethod(SCAN_QR_CODE_METHOD)) {
this._generateQRCodeProps(props.request);
}
}
async _generateQRCodeProps(verificationRequest: VerificationRequest) {
@ -67,59 +71,60 @@ export default class VerificationPanel extends React.PureComponent {
}
renderQRPhase(pending) {
const {member} = this.props;
const {member, request} = this.props;
const showSAS = request.methods.includes(verificationMethods.SAS);
const showQR = this.props.request.otherPartySupportsMethod(SCAN_QR_CODE_METHOD);
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
const noCommonMethodError = !showSAS && !showQR ?
<p>{_t("The session you are trying to verify doesn't support scanning a QR code or emoji verification, which is what Riot supports. Try with a different client.")}</p> :
null;
if (this.props.layout === 'dialog') {
// HACK: This is a terrible idea.
let qrCode = <div className='mx_VerificationPanel_QRPhase_noQR'><Spinner /></div>;
if (this.state.qrCodeProps) {
qrCode = <VerificationQRCode {...this.state.qrCodeProps} />;
let qrBlock;
let sasBlock;
if (showQR) {
let qrCode;
if (this.state.qrCodeProps) {
qrCode = <VerificationQRCode {...this.state.qrCodeProps} />;
} else {
qrCode = <div className='mx_VerificationPanel_QRPhase_noQR'><Spinner /></div>;
}
qrBlock =
<div className='mx_VerificationPanel_QRPhase_startOption'>
<p>{_t("Scan this unique code")}</p>
{qrCode}
</div>;
}
if (showSAS) {
sasBlock =
<div className='mx_VerificationPanel_QRPhase_startOption'>
<p>{_t("Compare unique emoji")}</p>
<span className='mx_VerificationPanel_QRPhase_helpText'>{_t("Compare a unique set of emoji if you don't have a camera on either device")}</span>
<AccessibleButton disabled={this.state.emojiButtonClicked} onClick={this._startSAS} kind='primary'>
{_t("Start")}
</AccessibleButton>
</div>;
}
const or = qrBlock && sasBlock ?
<div className='mx_VerificationPanel_QRPhase_betweenText'>{_t("or")}</div> : null;
return (
<div>
{_t("Verify this session by completing one of the following:")}
<div className='mx_VerificationPanel_QRPhase_startOptions'>
<div className='mx_VerificationPanel_QRPhase_startOption'>
<p>{_t("Scan this unique code")}</p>
{qrCode}
</div>
<div className='mx_VerificationPanel_QRPhase_betweenText'>{_t("or")}</div>
<div className='mx_VerificationPanel_QRPhase_startOption'>
<p>{_t("Compare unique emoji")}</p>
<span className='mx_VerificationPanel_QRPhase_helpText'>{_t("Compare a unique set of emoji if you don't have a camera on either device")}</span>
<AccessibleButton disabled={this.state.emojiButtonClicked} onClick={this._startSAS} kind='primary'>
{_t("Start")}
</AccessibleButton>
</div>
{qrBlock}
{or}
{sasBlock}
{noCommonMethodError}
</div>
</div>
);
}
let button;
if (pending) {
button = <Spinner />;
} else {
const disabled = this.state.emojiButtonClicked;
button = (
<AccessibleButton disabled={disabled} kind="primary" className="mx_UserInfo_wideButton" onClick={this._startSAS}>
{_t("Verify by emoji")}
</AccessibleButton>
);
}
if (!this.state.qrCodeProps) {
return <div className="mx_UserInfo_container">
<h3>{_t("Verify by emoji")}</h3>
<p>{_t("Verify by comparing unique emoji.")}</p>
{ button }
</div>;
}
// TODO: add way to open camera to scan a QR code
return <React.Fragment>
<div className="mx_UserInfo_container">
let qrBlock;
if (this.state.qrCodeProps) {
qrBlock = <div className="mx_UserInfo_container">
<h3>{_t("Verify by scanning")}</h3>
<p>{_t("Ask %(displayName)s to scan your code:", {
displayName: member.displayName || member.name || member.userId,
@ -128,14 +133,41 @@ export default class VerificationPanel extends React.PureComponent {
<div className="mx_VerificationPanel_qrCode">
<VerificationQRCode {...this.state.qrCodeProps} />
</div>
</div>
</div>;
}
<div className="mx_UserInfo_container">
let sasBlock;
if (showSAS) {
let button;
if (pending) {
button = <Spinner />;
} else {
const disabled = this.state.emojiButtonClicked;
button = (
<AccessibleButton disabled={disabled} kind="primary" className="mx_UserInfo_wideButton" onClick={this._startSAS}>
{_t("Verify by emoji")}
</AccessibleButton>
);
}
const sasLabel = this.state.qrCodeProps ?
_t("If you can't scan the code above, verify by comparing unique emoji.") :
_t("Verify by comparing unique emoji.");
sasBlock = <div className="mx_UserInfo_container">
<h3>{_t("Verify by emoji")}</h3>
<p>{_t("If you can't scan the code above, verify by comparing unique emoji.")}</p>
<p>{sasLabel}</p>
{ button }
</div>
</div>;
}
const noCommonMethodBlock = noCommonMethodError ?
<div className="mx_UserInfo_container">{noCommonMethodError}</div> :
null;
// TODO: add way to open camera to scan a QR code
return <React.Fragment>
{qrBlock}
{sasBlock}
{noCommonMethodBlock}
</React.Fragment>;
}
@ -258,7 +290,11 @@ export default class VerificationPanel extends React.PureComponent {
};
componentDidMount() {
this.props.request.on("change", this._onRequestChange);
const {request} = this.props;
request.on("change", this._onRequestChange);
if (request.verifier) {
this.setState({sasEvent: request.verifier.sasEvent});
}
this._onRequestChange();
}

View File

@ -74,7 +74,6 @@ export default class AliasSettings extends React.Component {
roomId: PropTypes.string.isRequired,
canSetCanonicalAlias: PropTypes.bool.isRequired,
canSetAliases: PropTypes.bool.isRequired,
aliasEvents: PropTypes.array, // [MatrixEvent]
canonicalAliasEvent: PropTypes.object, // MatrixEvent
};
@ -94,12 +93,6 @@ export default class AliasSettings extends React.Component {
updatingCanonicalAlias: false,
};
const localDomain = MatrixClientPeg.get().getDomain();
state.domainToAliases = this.aliasEventsToDictionary(props.aliasEvents || []);
state.remoteDomains = Object.keys(state.domainToAliases).filter((domain) => {
return domain !== localDomain && state.domainToAliases[domain].length > 0;
});
if (props.canonicalAliasEvent) {
state.canonicalAlias = props.canonicalAliasEvent.getContent().alias;
}
@ -107,6 +100,42 @@ export default class AliasSettings extends React.Component {
this.state = state;
}
async componentWillMount() {
const cli = MatrixClientPeg.get();
if (await cli.doesServerSupportUnstableFeature("org.matrix.msc2432")) {
const response = await cli.unstableGetLocalAliases(this.props.roomId);
const localAliases = response.aliases;
const localDomain = cli.getDomain();
const domainToAliases = Object.assign(
{},
// FIXME, any localhost alt_aliases will be ignored as they are overwritten by localAliases
this.aliasesToDictionary(this._getAltAliases()),
{[localDomain]: localAliases || []},
);
const remoteDomains = Object.keys(domainToAliases).filter((domain) => {
return domain !== localDomain && domainToAliases[domain].length > 0;
});
this.setState({ domainToAliases, remoteDomains });
} else {
const state = {};
const localDomain = cli.getDomain();
state.domainToAliases = this.aliasEventsToDictionary(this.props.aliasEvents || []);
state.remoteDomains = Object.keys(state.domainToAliases).filter((domain) => {
return domain !== localDomain && state.domainToAliases[domain].length > 0;
});
this.setState(state);
}
}
aliasesToDictionary(aliases) {
return aliases.reduce((dict, alias) => {
const domain = alias.split(":")[1];
dict[domain] = dict[domain] || [];
dict[domain].push(alias);
return dict;
}, {});
}
aliasEventsToDictionary(aliasEvents) { // m.room.alias events
const dict = {};
aliasEvents.forEach((event) => {
@ -117,6 +146,16 @@ export default class AliasSettings extends React.Component {
return dict;
}
_getAltAliases() {
if (this.props.canonicalAliasEvent) {
const altAliases = this.props.canonicalAliasEvent.getContent().alt_aliases;
if (Array.isArray(altAliases)) {
return altAliases;
}
}
return [];
}
changeCanonicalAlias(alias) {
if (!this.props.canSetCanonicalAlias) return;
@ -126,6 +165,8 @@ export default class AliasSettings extends React.Component {
});
const eventContent = {};
const altAliases = this._getAltAliases();
if (altAliases) eventContent["alt_aliases"] = altAliases;
if (alias) eventContent["alias"] = alias;
MatrixClientPeg.get().sendStateEvent(this.props.roomId, "m.room.canonical_alias",

View File

@ -36,11 +36,12 @@ export default class SecurityRoomSettingsTab extends React.Component {
joinRule: "invite",
guestAccess: "can_join",
history: "shared",
hasAliases: false,
encrypted: false,
};
}
componentWillMount(): void {
async componentWillMount(): void {
MatrixClientPeg.get().on("RoomState.events", this._onStateEvent);
const room = MatrixClientPeg.get().getRoom(this.props.roomId);
@ -63,6 +64,8 @@ export default class SecurityRoomSettingsTab extends React.Component {
);
const encrypted = MatrixClientPeg.get().isRoomEncrypted(this.props.roomId);
this.setState({joinRule, guestAccess, history, encrypted});
const hasAliases = await this._hasAliases();
this.setState({hasAliases});
}
_pullContentPropertyFromEvent(event, key, defaultValue) {
@ -201,13 +204,25 @@ export default class SecurityRoomSettingsTab extends React.Component {
MatrixClientPeg.get().getRoom(this.props.roomId).setBlacklistUnverifiedDevices(checked);
};
async _hasAliases() {
const cli = MatrixClientPeg.get();
if (await cli.doesServerSupportUnstableFeature("org.matrix.msc2432")) {
const response = await cli.unstableGetLocalAliases(this.props.roomId);
const localAliases = response.aliases;
return Array.isArray(localAliases) && localAliases.length !== 0;
} else {
const room = cli.getRoom(this.props.roomId);
const aliasEvents = room.currentState.getStateEvents("m.room.aliases") || [];
const hasAliases = !!aliasEvents.find((ev) => (ev.getContent().aliases || []).length > 0);
return hasAliases;
}
}
_renderRoomAccess() {
const client = MatrixClientPeg.get();
const room = client.getRoom(this.props.roomId);
const joinRule = this.state.joinRule;
const guestAccess = this.state.guestAccess;
const aliasEvents = room.currentState.getStateEvents("m.room.aliases") || [];
const hasAliases = !!aliasEvents.find((ev) => (ev.getContent().aliases || []).length > 0);
const canChangeAccess = room.currentState.mayClientSendStateEvent("m.room.join_rules", client)
&& room.currentState.mayClientSendStateEvent("m.room.guest_access", client);
@ -226,7 +241,7 @@ export default class SecurityRoomSettingsTab extends React.Component {
}
let aliasWarning = null;
if (joinRule === 'public' && !hasAliases) {
if (joinRule === 'public' && !this.state.hasAliases) {
aliasWarning = (
<div className='mx_SecurityRoomSettingsTab_warning'>
<img src={require("../../../../../../res/img/warning.svg")} width={15} height={15} />

View File

@ -58,7 +58,7 @@ export default class VerificationRequestToast extends React.PureComponent {
_checkRequestIsPending = () => {
const {request} = this.props;
if (request.ready || request.done || request.cancelled || request.observeOnly) {
if (!request.canAccept) {
ToastStore.sharedInstance().dismissToast(this.props.toastKey);
}
};

View File

@ -23,6 +23,7 @@ import dis from "./dispatcher";
import * as Rooms from "./Rooms";
import DMRoomMap from "./utils/DMRoomMap";
import {getAddressType} from "./UserAddress";
import SettingsStore from "./settings/SettingsStore";
/**
* Create a new room, and switch to it.
@ -159,7 +160,7 @@ export default function createRoom(opts) {
});
}
export async function ensureDMExists(client, userId) {
export function findDMForUser(client, userId) {
const roomIds = DMRoomMap.shared().getDMRoomsForUserId(userId);
const rooms = roomIds.map(id => client.getRoom(id));
const suitableDMRooms = rooms.filter(r => {
@ -169,12 +170,60 @@ export async function ensureDMExists(client, userId) {
}
return false;
});
let roomId;
if (suitableDMRooms.length) {
const room = suitableDMRooms[0];
roomId = room.roomId;
return suitableDMRooms[0];
}
}
/*
* Try to ensure the user is already in the megolm session before continuing
* NOTE: this assumes you've just created the room and there's not been an opportunity
* for other code to run, so we shouldn't miss RoomState.newMember when it comes by.
*/
export async function _waitForMember(client, roomId, userId, opts = { timeout: 1500 }) {
const { timeout } = opts;
let handler;
return new Promise((resolve) => {
handler = function(_event, _roomstate, member) {
if (member.userId !== userId) return;
if (member.roomId !== roomId) return;
resolve(true);
};
client.on("RoomState.newMember", handler);
/* We don't want to hang if this goes wrong, so we proceed and hope the other
user is already in the megolm session */
setTimeout(resolve, timeout, false);
}).finally(() => {
client.removeListener("RoomState.newMember", handler);
});
}
/*
* Ensure that for every user in a room, there is at least one device that we
* can encrypt to.
*/
export async function canEncryptToAllUsers(client, userIds) {
const usersDeviceMap = await client.downloadKeys(userIds);
// { "@user:host": { "DEVICE": {...}, ... }, ... }
return Object.values(usersDeviceMap).every((userDevices) =>
// { "DEVICE": {...}, ... }
Object.keys(userDevices).length > 0,
);
}
export async function ensureDMExists(client, userId) {
const existingDMRoom = findDMForUser(client, userId);
let roomId;
if (existingDMRoom) {
roomId = existingDMRoom.roomId;
} else {
roomId = await createRoom({dmUserId: userId, spinner: false, andView: false});
let encryption;
if (SettingsStore.isFeatureEnabled("feature_cross_signing")) {
encryption = canEncryptToAllUsers(client, [userId]);
}
roomId = await createRoom({encryption, dmUserId: userId, spinner: false, andView: false});
await _waitForMember(client, roomId, userId);
}
return roomId;
}

View File

@ -5,21 +5,22 @@
"Failed to verify email address: make sure you clicked the link in the email": "Failed to verify email address: make sure you clicked the link in the email",
"Add Phone Number": "Add Phone Number",
"The platform you're on": "The platform you're on",
"The version of Riot.im": "The version of Riot.im",
"The version of Riot": "The version of Riot",
"Whether or not you're logged in (we don't record your username)": "Whether or not you're logged in (we don't record your username)",
"Your language of choice": "Your language of choice",
"Which officially provided instance you are using, if any": "Which officially provided instance you are using, if any",
"Whether or not you're using the Richtext mode of the Rich Text Editor": "Whether or not you're using the Richtext mode of the Rich Text Editor",
"Whether or not you're using the 'breadcrumbs' feature (avatars above the room list)": "Whether or not you're using the 'breadcrumbs' feature (avatars above the room list)",
"Your homeserver's URL": "Your homeserver's URL",
"Your identity server's URL": "Your identity server's URL",
"Whether you're using Riot on a device where touch is the primary input mechanism": "Whether you're using Riot on a device where touch is the primary input mechanism",
"Whether or not you're using the 'breadcrumbs' feature (avatars above the room list)": "Whether or not you're using the 'breadcrumbs' feature (avatars above the room list)",
"Whether you're using Riot as an installed Progressive Web App": "Whether you're using Riot as an installed Progressive Web App",
"e.g. %(exampleValue)s": "e.g. %(exampleValue)s",
"Every page you use in the app": "Every page you use in the app",
"e.g. <CurrentPageURL>": "e.g. <CurrentPageURL>",
"Your User Agent": "Your User Agent",
"Your user agent": "Your user agent",
"Your device resolution": "Your device resolution",
"Analytics": "Analytics",
"The information being sent to us to help make Riot.im better includes:": "The information being sent to us to help make Riot.im better includes:",
"The information being sent to us to help make Riot better includes:": "The information being sent to us to help make Riot better includes:",
"Where this page includes identifiable information, such as a room, user or group ID, that data is removed before being sent to the server.": "Where this page includes identifiable information, such as a room, user or group ID, that data is removed before being sent to the server.",
"Error": "Error",
"Unable to load! Check your network connectivity and try again.": "Unable to load! Check your network connectivity and try again.",
@ -1198,11 +1199,12 @@
"This client does not support end-to-end encryption.": "This client does not support end-to-end encryption.",
"Messages in this room are not end-to-end encrypted.": "Messages in this room are not end-to-end encrypted.",
"Security": "Security",
"Verify by emoji": "Verify by emoji",
"Verify by comparing unique emoji.": "Verify by comparing unique emoji.",
"The session you are trying to verify doesn't support scanning a QR code or emoji verification, which is what Riot supports. Try with a different client.": "The session you are trying to verify doesn't support scanning a QR code or emoji verification, which is what Riot supports. Try with a different client.",
"Verify by scanning": "Verify by scanning",
"Ask %(displayName)s to scan your code:": "Ask %(displayName)s to scan your code:",
"Verify by emoji": "Verify by emoji",
"If you can't scan the code above, verify by comparing unique emoji.": "If you can't scan the code above, verify by comparing unique emoji.",
"Verify by comparing unique emoji.": "Verify by comparing unique emoji.",
"You've successfully verified %(displayName)s!": "You've successfully verified %(displayName)s!",
"Got it": "Got it",
"Verification timed out. Start verification again from their profile.": "Verification timed out. Start verification again from their profile.",
@ -1240,8 +1242,12 @@
"%(name)s cancelled verifying": "%(name)s cancelled verifying",
"You accepted": "You accepted",
"%(name)s accepted": "%(name)s accepted",
"You declined": "You declined",
"You cancelled": "You cancelled",
"%(name)s declined": "%(name)s declined",
"%(name)s cancelled": "%(name)s cancelled",
"accepting …": "accepting …",
"declining …": "declining …",
"%(name)s wants to verify": "%(name)s wants to verify",
"You sent a verification request": "You sent a verification request",
"Error decrypting video": "Error decrypting video",

View File

@ -67,6 +67,18 @@ export default async function sendBugReport(bugReportEndpoint, opts) {
userAgent = window.navigator.userAgent;
}
let installedPWA = "UNKNOWN";
try {
// Known to work at least for desktop Chrome
installedPWA = window.matchMedia('(display-mode: standalone)').matches;
} catch (e) { }
let touchInput = "UNKNOWN";
try {
// MDN claims broad support across browsers
touchInput = window.matchMedia('(pointer: coarse)').matches;
} catch (e) { }
const client = MatrixClientPeg.get();
console.log("Sending bug report.");
@ -76,6 +88,8 @@ export default async function sendBugReport(bugReportEndpoint, opts) {
body.append('app', 'riot-web');
body.append('version', version);
body.append('user_agent', userAgent);
body.append('installed_pwa', installedPWA);
body.append('touch_input', touchInput);
if (client) {
body.append('user_id', client.credentials.userId);

View File

@ -0,0 +1,12 @@
<html>
<head>
<!--
Hello! If you're reading this, perhaps you're wondering what this
file is doing and why your Riot is using it.
In short, this allows Riot to isolate potentially unsafe encrypted
attachments into their own origin, away from your Riot.
Stay curious!
-->
</head>
<body></body>
</html>

49
src/usercontent/index.js Normal file
View File

@ -0,0 +1,49 @@
const params = window.location.search.substring(1).split('&');
let lockOrigin;
for (let i = 0; i < params.length; ++i) {
const parts = params[i].split('=');
if (parts[0] === 'origin') lockOrigin = decodeURIComponent(parts[1]);
}
function remoteRender(event) {
const data = event.data;
const img = document.createElement("img");
img.id = "img";
img.src = data.imgSrc;
img.style = data.imgStyle;
const a = document.createElement("a");
a.id = "a";
a.rel = "noopener";
a.target = "_blank";
a.download = data.download;
a.style = data.style;
a.style.fontFamily = "Arial, Helvetica, Sans-Serif";
a.href = window.URL.createObjectURL(data.blob);
a.appendChild(img);
a.appendChild(document.createTextNode(data.textContent));
const body = document.body;
// Don't display scrollbars if the link takes more than one line to display.
body.style = "margin: 0px; overflow: hidden";
body.appendChild(a);
}
function remoteSetTint(event) {
const data = event.data;
const img = document.getElementById("img");
img.src = data.imgSrc;
img.style = data.imgStyle;
const a = document.getElementById("a");
a.style = data.style;
}
window.onmessage = function(e) {
if (e.origin === lockOrigin) {
if (e.data.blob) remoteRender(e);
else remoteSetTint(e);
}
};

View File

@ -34,10 +34,15 @@ import Matrix from 'matrix-js-sdk';
const test_utils = require('../../test-utils');
const mockclock = require('../../mock-clock');
import Adapter from "enzyme-adapter-react-16";
import { configure, mount } from "enzyme";
import Velocity from 'velocity-animate';
import MatrixClientContext from "../../../src/contexts/MatrixClientContext";
import RoomContext from "../../../src/contexts/RoomContext";
configure({ adapter: new Adapter() });
let client;
const room = new Matrix.Room();
@ -251,4 +256,111 @@ describe('MessagePanel', function() {
}, 100);
}, 100);
});
it('should collapse creation events', function() {
const mkEvent = test_utils.mkEvent;
const mkMembership = test_utils.mkMembership;
const roomId = "!someroom";
const alice = "@alice:example.org";
const ts0 = Date.now();
const events = [
mkEvent({
event: true,
type: "m.room.create",
room: roomId,
user: alice,
content: {
creator: alice,
room_version: "5",
predecessor: {
room_id: "!prevroom",
event_id: "$someevent",
},
},
ts: ts0,
}),
mkMembership({
event: true,
room: roomId,
user: alice,
target: {
userId: alice,
name: "Alice",
getAvatarUrl: () => {
return "avatar.jpeg";
},
},
ts: ts0 + 1,
mship: 'join',
name: 'Alice',
}),
mkEvent({
event: true,
type: "m.room.join_rules",
room: roomId,
user: alice,
content: {
"join_rule": "invite"
},
ts: ts0 + 2,
}),
mkEvent({
event: true,
type: "m.room.history_visibility",
room: roomId,
user: alice,
content: {
"history_visibility": "invited",
},
ts: ts0 + 3,
}),
mkEvent({
event: true,
type: "m.room.encryption",
room: roomId,
user: alice,
content: {
"algorithm": "m.megolm.v1.aes-sha2",
},
ts: ts0 + 4,
}),
mkMembership({
event: true,
room: roomId,
user: alice,
skey: "@bob:example.org",
target: {
userId: "@bob:example.org",
name: "Bob",
getAvatarUrl: () => {
return "avatar.jpeg";
},
},
ts: ts0 + 5,
mship: 'invite',
name: 'Bob',
}),
];
const res = mount(
<WrappedMessagePanel className="cls" events={events} />,
);
// we expect that
// - the room creation event, the room encryption event, and Alice inviting Bob,
// should be outside of the room creation summary
// - all other events should be inside the room creation summary
const tiles = res.find(sdk.getComponent('views.rooms.EventTile'));
expect(tiles.at(0).props().mxEvent.getType()).toEqual("m.room.create");
expect(tiles.at(1).props().mxEvent.getType()).toEqual("m.room.encryption");
const summaryTiles = res.find(sdk.getComponent('views.elements.EventListSummary'));
const summaryTile = summaryTiles.at(0);
const summaryEventTiles = summaryTile.find(sdk.getComponent('views.rooms.EventTile'));
// every event except for the room creation, room encryption, and Bob's
// invite event should be in the event summary
expect(summaryEventTiles.length).toEqual(tiles.length - 3);
});
});

72
test/createRoom-test.js Normal file
View File

@ -0,0 +1,72 @@
import {_waitForMember, canEncryptToAllUsers} from '../src/createRoom';
import {EventEmitter} from 'events';
/* Shorter timeout, we've got tests to run */
const timeout = 30;
describe("waitForMember", () => {
let client;
beforeEach(() => {
client = new EventEmitter();
});
it("resolves with false if the timeout is reached", (done) => {
_waitForMember(client, "", "", { timeout: 0 }).then((r) => {
expect(r).toBe(false);
done();
});
});
it("resolves with false if the timeout is reached, even if other RoomState.newMember events fire", (done) => {
const roomId = "!roomId:domain";
const userId = "@clientId:domain";
_waitForMember(client, roomId, userId, { timeout }).then((r) => {
expect(r).toBe(false);
done();
});
client.emit("RoomState.newMember", undefined, undefined, { roomId, userId: "@anotherClient:domain" });
});
it("resolves with true if RoomState.newMember fires", (done) => {
const roomId = "!roomId:domain";
const userId = "@clientId:domain";
_waitForMember(client, roomId, userId, { timeout }).then((r) => {
expect(r).toBe(true);
expect(client.listeners("RoomState.newMember").length).toBe(0);
done();
});
client.emit("RoomState.newMember", undefined, undefined, { roomId, userId });
});
});
describe("canEncryptToAllUsers", () => {
const trueUser = {
"@goodUser:localhost": {
"DEV1": {},
"DEV2": {},
},
};
const falseUser = {
"@badUser:localhost": {},
};
it("returns true if all devices have crypto", async (done) => {
const client = {
downloadKeys: async function(userIds) { return trueUser; },
};
const response = await canEncryptToAllUsers(client, ["@goodUser:localhost"]);
expect(response).toBe(true);
done();
});
it("returns false if not all users have crypto", async (done) => {
const client = {
downloadKeys: async function(userIds) { return {...trueUser, ...falseUser}; },
};
const response = await canEncryptToAllUsers(client, ["@goodUser:localhost", "@badUser:localhost"]);
expect(response).toBe(false);
done();
});
});

View File

@ -51,7 +51,7 @@ export function createTestClient() {
getUserId: jest.fn().mockReturnValue("@userId:matrix.rog"),
getPushActionsForEvent: jest.fn(),
getRoom: jest.fn().mockReturnValue(mkStubRoom()),
getRoom: jest.fn().mockImplementation(mkStubRoom),
getRooms: jest.fn().mockReturnValue([]),
getVisibleRooms: jest.fn().mockReturnValue([]),
getGroups: jest.fn().mockReturnValue([]),
@ -111,7 +111,7 @@ export function mkEvent(opts) {
if (opts.skey) {
event.state_key = opts.skey;
} else if (["m.room.name", "m.room.topic", "m.room.create", "m.room.join_rules",
"m.room.power_levels", "m.room.topic",
"m.room.power_levels", "m.room.topic", "m.room.history_visibility", "m.room.encryption",
"com.example.state"].indexOf(opts.type) !== -1) {
event.state_key = "";
}

View File

@ -5760,9 +5760,10 @@ mathml-tag-names@^2.0.1:
resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.1.tgz#6dff66c99d55ecf739ca53c492e626f1d12a33cc"
integrity sha512-pWB896KPGSGkp1XtyzRBftpTzwSOL0Gfk0wLvxt4f2mgzjY19o0LxJ3U25vNWTzsh7da+KTbuXQoQ3lOJZ8WHw==
"matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop":
version "4.0.0"
resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/21e4c597d9633aef606871cf9ffffaf039142be3"
matrix-js-sdk@5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-5.0.0.tgz#dcbab35f1afdb35ef0364eb232e78e0fb7dc2a5b"
integrity sha512-A/aeE2Zn2OHq1n/9wIHCszrQZ7oXfThUHWi5Kz7illVCPUJ3JrZ31XVvx02k6vBasDcUtjAfZblHdTVN62cWLw==
dependencies:
"@babel/runtime" "^7.8.3"
another-json "^0.2.0"