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:
commit
4278d44059
25 changed files with 1264 additions and 390 deletions
376
CHANGELOG.md
376
CHANGELOG.md
|
@ -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)
|
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)
|
[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
27
docs/usercontent.md
Normal 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.
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "matrix-react-sdk",
|
"name": "matrix-react-sdk",
|
||||||
"version": "2.0.0",
|
"version": "2.1.0",
|
||||||
"description": "SDK for matrix.org using React",
|
"description": "SDK for matrix.org using React",
|
||||||
"author": "matrix.org",
|
"author": "matrix.org",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|
47
release.sh
47
release.sh
|
@ -9,4 +9,51 @@ set -e
|
||||||
|
|
||||||
cd `dirname $0`
|
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 "$@"
|
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
|
||||||
|
|
|
@ -57,6 +57,8 @@ function getRedactedUrl() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const customVariables = {
|
const customVariables = {
|
||||||
|
// The Matomo installation at https://matomo.riot.im is currently configured
|
||||||
|
// with a limit of 10 custom variables.
|
||||||
'App Platform': {
|
'App Platform': {
|
||||||
id: 1,
|
id: 1,
|
||||||
expl: _td('The platform you\'re on'),
|
expl: _td('The platform you\'re on'),
|
||||||
|
@ -64,7 +66,7 @@ const customVariables = {
|
||||||
},
|
},
|
||||||
'App Version': {
|
'App Version': {
|
||||||
id: 2,
|
id: 2,
|
||||||
expl: _td('The version of Riot.im'),
|
expl: _td('The version of Riot'),
|
||||||
example: '15.0.0',
|
example: '15.0.0',
|
||||||
},
|
},
|
||||||
'User Type': {
|
'User Type': {
|
||||||
|
@ -87,20 +89,25 @@ const customVariables = {
|
||||||
expl: _td('Whether or not you\'re using the Richtext mode of the Rich Text Editor'),
|
expl: _td('Whether or not you\'re using the Richtext mode of the Rich Text Editor'),
|
||||||
example: 'off',
|
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': {
|
'Homeserver URL': {
|
||||||
id: 7,
|
id: 7,
|
||||||
expl: _td('Your homeserver\'s URL'),
|
expl: _td('Your homeserver\'s URL'),
|
||||||
example: 'https://matrix.org',
|
example: 'https://matrix.org',
|
||||||
},
|
},
|
||||||
'Identity Server URL': {
|
'Touch Input': {
|
||||||
id: 8,
|
id: 8,
|
||||||
expl: _td('Your identity server\'s URL'),
|
expl: _td("Whether you're using Riot on a device where touch is the primary input mechanism"),
|
||||||
example: 'https://vector.im',
|
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);
|
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
|
// start heartbeat
|
||||||
this._heartbeatIntervalID = window.setInterval(this.ping.bind(this), HEARTBEAT_INTERVAL);
|
this._heartbeatIntervalID = window.setInterval(this.ping.bind(this), HEARTBEAT_INTERVAL);
|
||||||
}
|
}
|
||||||
|
@ -291,11 +312,9 @@ class Analytics {
|
||||||
if (!config.piwik) return;
|
if (!config.piwik) return;
|
||||||
|
|
||||||
const whitelistedHSUrls = config.piwik.whitelistedHSUrls || [];
|
const whitelistedHSUrls = config.piwik.whitelistedHSUrls || [];
|
||||||
const whitelistedISUrls = config.piwik.whitelistedISUrls || [];
|
|
||||||
|
|
||||||
this._setVisitVariable('User Type', isGuest ? 'Guest' : 'Logged In');
|
this._setVisitVariable('User Type', isGuest ? 'Guest' : 'Logged In');
|
||||||
this._setVisitVariable('Homeserver URL', whitelistRedact(whitelistedHSUrls, homeserverUrl));
|
this._setVisitVariable('Homeserver URL', whitelistRedact(whitelistedHSUrls, homeserverUrl));
|
||||||
this._setVisitVariable('Identity Server URL', whitelistRedact(whitelistedISUrls, identityServerUrl));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setBreadcrumbs(state) {
|
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 },
|
{ expl: _td('Your device resolution'), value: resolution },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -337,7 +356,7 @@ class Analytics {
|
||||||
title: _t('Analytics'),
|
title: _t('Analytics'),
|
||||||
description: <div className="mx_AnalyticsModal">
|
description: <div className="mx_AnalyticsModal">
|
||||||
<div>
|
<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>
|
</div>
|
||||||
<table>
|
<table>
|
||||||
{ rows.map((row) => <tr key={row[0]}>
|
{ rows.map((row) => <tr key={row[0]}>
|
||||||
|
|
|
@ -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) {
|
if (localStorage) {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -413,10 +413,6 @@ export default class MessagePanel extends React.Component {
|
||||||
};
|
};
|
||||||
|
|
||||||
_getEventTiles() {
|
_getEventTiles() {
|
||||||
const DateSeparator = sdk.getComponent('messages.DateSeparator');
|
|
||||||
const EventListSummary = sdk.getComponent('views.elements.EventListSummary');
|
|
||||||
const MemberEventListSummary = sdk.getComponent('views.elements.MemberEventListSummary');
|
|
||||||
|
|
||||||
this.eventNodes = {};
|
this.eventNodes = {};
|
||||||
|
|
||||||
let i;
|
let i;
|
||||||
|
@ -458,199 +454,48 @@ export default class MessagePanel extends React.Component {
|
||||||
this._readReceiptsByEvent = this._getReadReceiptsByShownEvent();
|
this._readReceiptsByEvent = this._getReadReceiptsByShownEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let grouper = null;
|
||||||
|
|
||||||
for (i = 0; i < this.props.events.length; i++) {
|
for (i = 0; i < this.props.events.length; i++) {
|
||||||
const mxEv = this.props.events[i];
|
const mxEv = this.props.events[i];
|
||||||
const eventId = mxEv.getId();
|
const eventId = mxEv.getId();
|
||||||
const last = (mxEv === lastShownEvent);
|
const last = (mxEv === lastShownEvent);
|
||||||
|
|
||||||
// Wrap initial room creation events into an EventListSummary
|
if (grouper) {
|
||||||
// Grouping only events sent by the same user that sent the `m.room.create` and only until
|
if (grouper.shouldGroup(mxEv)) {
|
||||||
// the first non-state event or membership event which is not regarding the sender of the `m.room.create` event
|
grouper.add(mxEv);
|
||||||
const shouldGroup = (ev) => {
|
continue;
|
||||||
if (ev.getType() === "m.room.member"
|
} else {
|
||||||
&& (ev.getStateKey() !== mxEv.getSender() || ev.getContent()["membership"] !== "join")) {
|
// not part of group, so get the group tiles, close the
|
||||||
return false;
|
// group, and continue like a normal event
|
||||||
}
|
ret.push(...grouper.getTiles());
|
||||||
if (ev.isState() && ev.getSender() === mxEv.getSender()) {
|
prevEvent = grouper.getNewPrevEvent();
|
||||||
return true;
|
grouper = null;
|
||||||
}
|
|
||||||
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 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);
|
for (const Grouper of groupers) {
|
||||||
|
if (Grouper.canStartGroup(this, mxEv)) {
|
||||||
// Wrap consecutive member events in a ListSummary, ignore if redacted
|
grouper = new Grouper(this, mxEv, prevEvent, lastShownEvent);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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) {
|
const readMarker = this._readMarkerForEvent(eventId, i >= lastShownNonLocalEchoIndex);
|
||||||
// make sure we unpack the array returned by _getTilesForEvent,
|
if (readMarker) ret.push(readMarker);
|
||||||
// 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 (grouper) {
|
||||||
if (readMarker) ret.push(readMarker);
|
ret.push(...grouper.getTiles());
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
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];
|
||||||
|
|
|
@ -31,7 +31,7 @@ import dis from "../../../dispatcher";
|
||||||
import IdentityAuthClient from "../../../IdentityAuthClient";
|
import IdentityAuthClient from "../../../IdentityAuthClient";
|
||||||
import Modal from "../../../Modal";
|
import Modal from "../../../Modal";
|
||||||
import {humanizeTime} from "../../../utils/humanize";
|
import {humanizeTime} from "../../../utils/humanize";
|
||||||
import createRoom from "../../../createRoom";
|
import createRoom, {canEncryptToAllUsers} from "../../../createRoom";
|
||||||
import {inviteMultipleToRoom} from "../../../RoomInvite";
|
import {inviteMultipleToRoom} from "../../../RoomInvite";
|
||||||
import SettingsStore from '../../../settings/SettingsStore';
|
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.
|
// Check whether all users have uploaded device keys before.
|
||||||
// If so, enable encryption in the new room.
|
// If so, enable encryption in the new room.
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
const usersToDevicesMap = await client.downloadKeys(targetIds);
|
const allHaveDeviceKeys = await canEncryptToAllUsers(client, targetIds);
|
||||||
const allHaveDeviceKeys = Object.values(usersToDevicesMap).every(devices => {
|
|
||||||
// `devices` is an object of the form { deviceId: deviceInfo, ... }.
|
|
||||||
return Object.keys(devices).length > 0;
|
|
||||||
});
|
|
||||||
if (allHaveDeviceKeys) {
|
if (allHaveDeviceKeys) {
|
||||||
createRoomOptions.encryption = true;
|
createRoomOptions.encryption = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ import {decryptFile} from '../../../utils/DecryptFile';
|
||||||
import Tinter from '../../../Tinter';
|
import Tinter from '../../../Tinter';
|
||||||
import request from 'browser-request';
|
import request from 'browser-request';
|
||||||
import Modal from '../../../Modal';
|
import Modal from '../../../Modal';
|
||||||
import SdkConfig from "../../../SdkConfig";
|
import AccessibleButton from "../elements/AccessibleButton";
|
||||||
|
|
||||||
|
|
||||||
// A cached tinted copy of require("../../../../res/img/download.svg")
|
// 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 second domain is that it complicates hosting,
|
||||||
// the downside of using a sandboxed iframe is that the browers are overly
|
// 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.
|
// 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.
|
* 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
|
// will be inside the iframe so we wont be able to update
|
||||||
// it directly.
|
// it directly.
|
||||||
this._iframe.current.contentWindow.postMessage({
|
this._iframe.current.contentWindow.postMessage({
|
||||||
code: remoteSetTint.toString(),
|
|
||||||
imgSrc: tintedDownloadImageURL,
|
imgSrc: tintedDownloadImageURL,
|
||||||
style: computedStyle(this._dummyLink.current),
|
style: computedStyle(this._dummyLink.current),
|
||||||
}, "*");
|
}, "*");
|
||||||
|
@ -306,7 +227,7 @@ export default createReactClass({
|
||||||
// Wait for the user to click on the link before downloading
|
// Wait for the user to click on the link before downloading
|
||||||
// and decrypting the attachment.
|
// and decrypting the attachment.
|
||||||
let decrypting = false;
|
let decrypting = false;
|
||||||
const decrypt = () => {
|
const decrypt = (e) => {
|
||||||
if (decrypting) {
|
if (decrypting) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -323,16 +244,15 @@ export default createReactClass({
|
||||||
});
|
});
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
decrypting = false;
|
decrypting = false;
|
||||||
return;
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span className="mx_MFileBody">
|
<span className="mx_MFileBody">
|
||||||
<div className="mx_MFileBody_download">
|
<div className="mx_MFileBody_download">
|
||||||
<a href="javascript:void(0)" onClick={decrypt}>
|
<AccessibleButton onClick={decrypt}>
|
||||||
{ _t("Decrypt %(text)s", { text: text }) }
|
{ _t("Decrypt %(text)s", { text: text }) }
|
||||||
</a>
|
</AccessibleButton>
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
@ -341,7 +261,6 @@ export default createReactClass({
|
||||||
// When the iframe loads we tell it to render a download link
|
// When the iframe loads we tell it to render a download link
|
||||||
const onIframeLoad = (ev) => {
|
const onIframeLoad = (ev) => {
|
||||||
ev.target.contentWindow.postMessage({
|
ev.target.contentWindow.postMessage({
|
||||||
code: remoteRender.toString(),
|
|
||||||
imgSrc: tintedDownloadImageURL,
|
imgSrc: tintedDownloadImageURL,
|
||||||
style: computedStyle(this._dummyLink.current),
|
style: computedStyle(this._dummyLink.current),
|
||||||
blob: this.state.decryptedBlob,
|
blob: this.state.decryptedBlob,
|
||||||
|
@ -349,19 +268,13 @@ export default createReactClass({
|
||||||
// will have the correct name when the user tries to download it.
|
// 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.
|
// We can't provide a Content-Disposition header like we would for HTTP.
|
||||||
download: fileName,
|
download: fileName,
|
||||||
rel: "noopener",
|
|
||||||
target: "_blank",
|
|
||||||
textContent: _t("Download %(text)s", { text: text }),
|
textContent: _t("Download %(text)s", { text: text }),
|
||||||
}, "*");
|
}, "*");
|
||||||
};
|
};
|
||||||
|
|
||||||
// If the attachment is encryped then put the link inside an iframe.
|
const url = "usercontent/"; // XXX: this path should probably be passed from the skin
|
||||||
let renderer_url = DEFAULT_CROSS_ORIGIN_RENDERER;
|
|
||||||
const appConfig = SdkConfig.get();
|
// If the attachment is encrypted then put the link inside an iframe.
|
||||||
if (appConfig && appConfig.cross_origin_renderer_url) {
|
|
||||||
renderer_url = appConfig.cross_origin_renderer_url;
|
|
||||||
}
|
|
||||||
renderer_url += "?origin=" + encodeURIComponent(window.location.origin);
|
|
||||||
return (
|
return (
|
||||||
<span className="mx_MFileBody">
|
<span className="mx_MFileBody">
|
||||||
<div className="mx_MFileBody_download">
|
<div className="mx_MFileBody_download">
|
||||||
|
@ -373,7 +286,11 @@ export default createReactClass({
|
||||||
*/ }
|
*/ }
|
||||||
<a ref={this._dummyLink} />
|
<a ref={this._dummyLink} />
|
||||||
</div>
|
</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>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|
|
@ -59,7 +59,6 @@ export default class MKeyVerificationRequest extends React.Component {
|
||||||
};
|
};
|
||||||
|
|
||||||
_onAcceptClicked = async () => {
|
_onAcceptClicked = async () => {
|
||||||
this.setState({acceptOrCancelClicked: true});
|
|
||||||
const request = this.props.mxEvent.verificationRequest;
|
const request = this.props.mxEvent.verificationRequest;
|
||||||
if (request) {
|
if (request) {
|
||||||
try {
|
try {
|
||||||
|
@ -72,7 +71,6 @@ export default class MKeyVerificationRequest extends React.Component {
|
||||||
};
|
};
|
||||||
|
|
||||||
_onRejectClicked = async () => {
|
_onRejectClicked = async () => {
|
||||||
this.setState({acceptOrCancelClicked: true});
|
|
||||||
const request = this.props.mxEvent.verificationRequest;
|
const request = this.props.mxEvent.verificationRequest;
|
||||||
if (request) {
|
if (request) {
|
||||||
try {
|
try {
|
||||||
|
@ -96,10 +94,20 @@ export default class MKeyVerificationRequest extends React.Component {
|
||||||
_cancelledLabel(userId) {
|
_cancelledLabel(userId) {
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
const myUserId = client.getUserId();
|
const myUserId = client.getUserId();
|
||||||
|
const {cancellationCode} = this.props.mxEvent.verificationRequest;
|
||||||
|
const declined = cancellationCode === "m.user";
|
||||||
if (userId === myUserId) {
|
if (userId === myUserId) {
|
||||||
return _t("You cancelled");
|
if (declined) {
|
||||||
|
return _t("You declined");
|
||||||
|
} else {
|
||||||
|
return _t("You cancelled");
|
||||||
|
}
|
||||||
} else {
|
} 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 subtitle;
|
||||||
let stateNode;
|
let stateNode;
|
||||||
|
|
||||||
const accepted = request.ready || request.started || request.done;
|
if (!request.canAccept) {
|
||||||
if (accepted || request.cancelled) {
|
|
||||||
let stateLabel;
|
let stateLabel;
|
||||||
|
const accepted = request.ready || request.started || request.done;
|
||||||
if (accepted) {
|
if (accepted) {
|
||||||
stateLabel = (<AccessibleButton onClick={this._openRequest}>
|
stateLabel = (<AccessibleButton onClick={this._openRequest}>
|
||||||
{this._acceptedLabel(request.receivingUserId)}
|
{this._acceptedLabel(request.receivingUserId)}
|
||||||
</AccessibleButton>);
|
</AccessibleButton>);
|
||||||
} else {
|
} else if (request.cancelled) {
|
||||||
stateLabel = this._cancelledLabel(request.cancellingUserId);
|
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>);
|
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>);
|
_t("%(name)s wants to verify", {name})}</div>);
|
||||||
subtitle = (<div className="mx_cryptoEvent_subtitle">{
|
subtitle = (<div className="mx_cryptoEvent_subtitle">{
|
||||||
userLabelForEventRoom(request.requestingUserId, mxEvent.getRoomId())}</div>);
|
userLabelForEventRoom(request.requestingUserId, mxEvent.getRoomId())}</div>);
|
||||||
if (request.requested && !request.observeOnly) {
|
if (request.canAccept) {
|
||||||
const disabled = this.state.acceptOrCancelClicked;
|
|
||||||
stateNode = (<div className="mx_cryptoEvent_buttons">
|
stateNode = (<div className="mx_cryptoEvent_buttons">
|
||||||
<FormButton disabled={disabled} kind="danger" onClick={this._onRejectClicked} label={_t("Decline")} />
|
<FormButton kind="danger" onClick={this._onRejectClicked} label={_t("Decline")} />
|
||||||
<FormButton disabled={disabled} onClick={this._onAcceptClicked} label={_t("Accept")} />
|
<FormButton onClick={this._onAcceptClicked} label={_t("Accept")} />
|
||||||
</div>);
|
</div>);
|
||||||
}
|
}
|
||||||
} else { // request sent by us
|
} else { // request sent by us
|
||||||
|
|
|
@ -23,7 +23,7 @@ import {MatrixClientPeg} from "../../../MatrixClientPeg";
|
||||||
import {ensureDMExists} from "../../../createRoom";
|
import {ensureDMExists} from "../../../createRoom";
|
||||||
import {useEventEmitter} from "../../../hooks/useEventEmitter";
|
import {useEventEmitter} from "../../../hooks/useEventEmitter";
|
||||||
import Modal from "../../../Modal";
|
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 * as sdk from "../../../index";
|
||||||
import {_t} from "../../../languageHandler";
|
import {_t} from "../../../languageHandler";
|
||||||
|
|
||||||
|
@ -69,9 +69,10 @@ const EncryptionPanel = ({verificationRequest, member, onClose, layout}) => {
|
||||||
const roomId = await ensureDMExists(cli, member.userId);
|
const roomId = await ensureDMExists(cli, member.userId);
|
||||||
const verificationRequest = await cli.requestVerificationDM(member.userId, roomId);
|
const verificationRequest = await cli.requestVerificationDM(member.userId, roomId);
|
||||||
setRequest(verificationRequest);
|
setRequest(verificationRequest);
|
||||||
|
setPhase(verificationRequest.phase);
|
||||||
}, [member.userId]);
|
}, [member.userId]);
|
||||||
|
|
||||||
const requested = request && (phase === PHASE_REQUESTED || phase === undefined);
|
const requested = request && (phase === PHASE_REQUESTED || phase === PHASE_UNSENT || phase === undefined);
|
||||||
if (!request || requested) {
|
if (!request || requested) {
|
||||||
return <EncryptionInfo onStartVerification={onStartVerification} member={member} pending={requested} />;
|
return <EncryptionInfo onStartVerification={onStartVerification} member={member} pending={requested} />;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -25,7 +25,7 @@ import dis from '../../../dispatcher';
|
||||||
import Modal from '../../../Modal';
|
import Modal from '../../../Modal';
|
||||||
import * as sdk from '../../../index';
|
import * as sdk from '../../../index';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import createRoom from '../../../createRoom';
|
import createRoom, {findDMForUser} from '../../../createRoom';
|
||||||
import DMRoomMap from '../../../utils/DMRoomMap';
|
import DMRoomMap from '../../../utils/DMRoomMap';
|
||||||
import AccessibleButton from '../elements/AccessibleButton';
|
import AccessibleButton from '../elements/AccessibleButton';
|
||||||
import SdkConfig from '../../../SdkConfig';
|
import SdkConfig from '../../../SdkConfig';
|
||||||
|
@ -169,10 +169,19 @@ async function verifyDevice(userId, device) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function verifyUser(user) {
|
function verifyUser(user) {
|
||||||
|
const cli = MatrixClientPeg.get();
|
||||||
|
const dmRoom = findDMForUser(cli, user.userId);
|
||||||
|
let existingRequest;
|
||||||
|
if (dmRoom) {
|
||||||
|
existingRequest = cli.findVerificationRequestDMInProgress(dmRoom.roomId);
|
||||||
|
}
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: "set_right_panel_phase",
|
action: "set_right_panel_phase",
|
||||||
phase: RIGHT_PANEL_PHASES.EncryptionPanel,
|
phase: RIGHT_PANEL_PHASES.EncryptionPanel,
|
||||||
refireParams: {member: user},
|
refireParams: {
|
||||||
|
member: user,
|
||||||
|
verificationRequest: existingRequest,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,8 @@ import PropTypes from "prop-types";
|
||||||
|
|
||||||
import * as sdk from '../../../index';
|
import * as sdk from '../../../index';
|
||||||
import {verificationMethods} from 'matrix-js-sdk/src/crypto';
|
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 VerificationQRCode from "../elements/crypto/VerificationQRCode";
|
||||||
import {_t} from "../../../languageHandler";
|
import {_t} from "../../../languageHandler";
|
||||||
import E2EIcon from "../rooms/E2EIcon";
|
import E2EIcon from "../rooms/E2EIcon";
|
||||||
|
@ -54,7 +56,9 @@ export default class VerificationPanel extends React.PureComponent {
|
||||||
qrCodeProps: null, // generated by the VerificationQRCode component itself
|
qrCodeProps: null, // generated by the VerificationQRCode component itself
|
||||||
};
|
};
|
||||||
this._hasVerifier = false;
|
this._hasVerifier = false;
|
||||||
this._generateQRCodeProps(props.request);
|
if (this.props.request.otherPartySupportsMethod(SCAN_QR_CODE_METHOD)) {
|
||||||
|
this._generateQRCodeProps(props.request);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async _generateQRCodeProps(verificationRequest: VerificationRequest) {
|
async _generateQRCodeProps(verificationRequest: VerificationRequest) {
|
||||||
|
@ -67,59 +71,60 @@ export default class VerificationPanel extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
renderQRPhase(pending) {
|
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 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') {
|
if (this.props.layout === 'dialog') {
|
||||||
// HACK: This is a terrible idea.
|
// HACK: This is a terrible idea.
|
||||||
let qrCode = <div className='mx_VerificationPanel_QRPhase_noQR'><Spinner /></div>;
|
let qrBlock;
|
||||||
if (this.state.qrCodeProps) {
|
let sasBlock;
|
||||||
qrCode = <VerificationQRCode {...this.state.qrCodeProps} />;
|
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 (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{_t("Verify this session by completing one of the following:")}
|
{_t("Verify this session by completing one of the following:")}
|
||||||
<div className='mx_VerificationPanel_QRPhase_startOptions'>
|
<div className='mx_VerificationPanel_QRPhase_startOptions'>
|
||||||
<div className='mx_VerificationPanel_QRPhase_startOption'>
|
{qrBlock}
|
||||||
<p>{_t("Scan this unique code")}</p>
|
{or}
|
||||||
{qrCode}
|
{sasBlock}
|
||||||
</div>
|
{noCommonMethodError}
|
||||||
<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>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let button;
|
let qrBlock;
|
||||||
if (pending) {
|
if (this.state.qrCodeProps) {
|
||||||
button = <Spinner />;
|
qrBlock = <div className="mx_UserInfo_container">
|
||||||
} 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">
|
|
||||||
<h3>{_t("Verify by scanning")}</h3>
|
<h3>{_t("Verify by scanning")}</h3>
|
||||||
<p>{_t("Ask %(displayName)s to scan your code:", {
|
<p>{_t("Ask %(displayName)s to scan your code:", {
|
||||||
displayName: member.displayName || member.name || member.userId,
|
displayName: member.displayName || member.name || member.userId,
|
||||||
|
@ -128,14 +133,41 @@ export default class VerificationPanel extends React.PureComponent {
|
||||||
<div className="mx_VerificationPanel_qrCode">
|
<div className="mx_VerificationPanel_qrCode">
|
||||||
<VerificationQRCode {...this.state.qrCodeProps} />
|
<VerificationQRCode {...this.state.qrCodeProps} />
|
||||||
</div>
|
</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>
|
<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 }
|
{ 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>;
|
</React.Fragment>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -258,7 +290,11 @@ export default class VerificationPanel extends React.PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
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();
|
this._onRequestChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -74,7 +74,6 @@ export default class AliasSettings extends React.Component {
|
||||||
roomId: PropTypes.string.isRequired,
|
roomId: PropTypes.string.isRequired,
|
||||||
canSetCanonicalAlias: PropTypes.bool.isRequired,
|
canSetCanonicalAlias: PropTypes.bool.isRequired,
|
||||||
canSetAliases: PropTypes.bool.isRequired,
|
canSetAliases: PropTypes.bool.isRequired,
|
||||||
aliasEvents: PropTypes.array, // [MatrixEvent]
|
|
||||||
canonicalAliasEvent: PropTypes.object, // MatrixEvent
|
canonicalAliasEvent: PropTypes.object, // MatrixEvent
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -94,12 +93,6 @@ export default class AliasSettings extends React.Component {
|
||||||
updatingCanonicalAlias: false,
|
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) {
|
if (props.canonicalAliasEvent) {
|
||||||
state.canonicalAlias = props.canonicalAliasEvent.getContent().alias;
|
state.canonicalAlias = props.canonicalAliasEvent.getContent().alias;
|
||||||
}
|
}
|
||||||
|
@ -107,6 +100,42 @@ export default class AliasSettings extends React.Component {
|
||||||
this.state = state;
|
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
|
aliasEventsToDictionary(aliasEvents) { // m.room.alias events
|
||||||
const dict = {};
|
const dict = {};
|
||||||
aliasEvents.forEach((event) => {
|
aliasEvents.forEach((event) => {
|
||||||
|
@ -117,6 +146,16 @@ export default class AliasSettings extends React.Component {
|
||||||
return dict;
|
return dict;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_getAltAliases() {
|
||||||
|
if (this.props.canonicalAliasEvent) {
|
||||||
|
const altAliases = this.props.canonicalAliasEvent.getContent().alt_aliases;
|
||||||
|
if (Array.isArray(altAliases)) {
|
||||||
|
return altAliases;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
changeCanonicalAlias(alias) {
|
changeCanonicalAlias(alias) {
|
||||||
if (!this.props.canSetCanonicalAlias) return;
|
if (!this.props.canSetCanonicalAlias) return;
|
||||||
|
|
||||||
|
@ -126,6 +165,8 @@ export default class AliasSettings extends React.Component {
|
||||||
});
|
});
|
||||||
|
|
||||||
const eventContent = {};
|
const eventContent = {};
|
||||||
|
const altAliases = this._getAltAliases();
|
||||||
|
if (altAliases) eventContent["alt_aliases"] = altAliases;
|
||||||
if (alias) eventContent["alias"] = alias;
|
if (alias) eventContent["alias"] = alias;
|
||||||
|
|
||||||
MatrixClientPeg.get().sendStateEvent(this.props.roomId, "m.room.canonical_alias",
|
MatrixClientPeg.get().sendStateEvent(this.props.roomId, "m.room.canonical_alias",
|
||||||
|
|
|
@ -36,11 +36,12 @@ export default class SecurityRoomSettingsTab extends React.Component {
|
||||||
joinRule: "invite",
|
joinRule: "invite",
|
||||||
guestAccess: "can_join",
|
guestAccess: "can_join",
|
||||||
history: "shared",
|
history: "shared",
|
||||||
|
hasAliases: false,
|
||||||
encrypted: false,
|
encrypted: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillMount(): void {
|
async componentWillMount(): void {
|
||||||
MatrixClientPeg.get().on("RoomState.events", this._onStateEvent);
|
MatrixClientPeg.get().on("RoomState.events", this._onStateEvent);
|
||||||
|
|
||||||
const room = MatrixClientPeg.get().getRoom(this.props.roomId);
|
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);
|
const encrypted = MatrixClientPeg.get().isRoomEncrypted(this.props.roomId);
|
||||||
this.setState({joinRule, guestAccess, history, encrypted});
|
this.setState({joinRule, guestAccess, history, encrypted});
|
||||||
|
const hasAliases = await this._hasAliases();
|
||||||
|
this.setState({hasAliases});
|
||||||
}
|
}
|
||||||
|
|
||||||
_pullContentPropertyFromEvent(event, key, defaultValue) {
|
_pullContentPropertyFromEvent(event, key, defaultValue) {
|
||||||
|
@ -201,13 +204,25 @@ export default class SecurityRoomSettingsTab extends React.Component {
|
||||||
MatrixClientPeg.get().getRoom(this.props.roomId).setBlacklistUnverifiedDevices(checked);
|
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() {
|
_renderRoomAccess() {
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
const room = client.getRoom(this.props.roomId);
|
const room = client.getRoom(this.props.roomId);
|
||||||
const joinRule = this.state.joinRule;
|
const joinRule = this.state.joinRule;
|
||||||
const guestAccess = this.state.guestAccess;
|
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)
|
const canChangeAccess = room.currentState.mayClientSendStateEvent("m.room.join_rules", client)
|
||||||
&& room.currentState.mayClientSendStateEvent("m.room.guest_access", client);
|
&& room.currentState.mayClientSendStateEvent("m.room.guest_access", client);
|
||||||
|
@ -226,7 +241,7 @@ export default class SecurityRoomSettingsTab extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
let aliasWarning = null;
|
let aliasWarning = null;
|
||||||
if (joinRule === 'public' && !hasAliases) {
|
if (joinRule === 'public' && !this.state.hasAliases) {
|
||||||
aliasWarning = (
|
aliasWarning = (
|
||||||
<div className='mx_SecurityRoomSettingsTab_warning'>
|
<div className='mx_SecurityRoomSettingsTab_warning'>
|
||||||
<img src={require("../../../../../../res/img/warning.svg")} width={15} height={15} />
|
<img src={require("../../../../../../res/img/warning.svg")} width={15} height={15} />
|
||||||
|
|
|
@ -58,7 +58,7 @@ export default class VerificationRequestToast extends React.PureComponent {
|
||||||
|
|
||||||
_checkRequestIsPending = () => {
|
_checkRequestIsPending = () => {
|
||||||
const {request} = this.props;
|
const {request} = this.props;
|
||||||
if (request.ready || request.done || request.cancelled || request.observeOnly) {
|
if (!request.canAccept) {
|
||||||
ToastStore.sharedInstance().dismissToast(this.props.toastKey);
|
ToastStore.sharedInstance().dismissToast(this.props.toastKey);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -23,6 +23,7 @@ import dis from "./dispatcher";
|
||||||
import * as Rooms from "./Rooms";
|
import * as Rooms from "./Rooms";
|
||||||
import DMRoomMap from "./utils/DMRoomMap";
|
import DMRoomMap from "./utils/DMRoomMap";
|
||||||
import {getAddressType} from "./UserAddress";
|
import {getAddressType} from "./UserAddress";
|
||||||
|
import SettingsStore from "./settings/SettingsStore";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new room, and switch to it.
|
* 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 roomIds = DMRoomMap.shared().getDMRoomsForUserId(userId);
|
||||||
const rooms = roomIds.map(id => client.getRoom(id));
|
const rooms = roomIds.map(id => client.getRoom(id));
|
||||||
const suitableDMRooms = rooms.filter(r => {
|
const suitableDMRooms = rooms.filter(r => {
|
||||||
|
@ -169,12 +170,60 @@ export async function ensureDMExists(client, userId) {
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
let roomId;
|
|
||||||
if (suitableDMRooms.length) {
|
if (suitableDMRooms.length) {
|
||||||
const room = suitableDMRooms[0];
|
return suitableDMRooms[0];
|
||||||
roomId = room.roomId;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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 {
|
} 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;
|
return roomId;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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",
|
"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",
|
"Add Phone Number": "Add Phone Number",
|
||||||
"The platform you're on": "The platform you're on",
|
"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)",
|
"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",
|
"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",
|
"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 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 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",
|
"e.g. %(exampleValue)s": "e.g. %(exampleValue)s",
|
||||||
"Every page you use in the app": "Every page you use in the app",
|
"Every page you use in the app": "Every page you use in the app",
|
||||||
"e.g. <CurrentPageURL>": "e.g. <CurrentPageURL>",
|
"e.g. <CurrentPageURL>": "e.g. <CurrentPageURL>",
|
||||||
"Your User Agent": "Your User Agent",
|
"Your user agent": "Your user agent",
|
||||||
"Your device resolution": "Your device resolution",
|
"Your device resolution": "Your device resolution",
|
||||||
"Analytics": "Analytics",
|
"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.",
|
"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",
|
"Error": "Error",
|
||||||
"Unable to load! Check your network connectivity and try again.": "Unable to load! Check your network connectivity and try again.",
|
"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.",
|
"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.",
|
"Messages in this room are not end-to-end encrypted.": "Messages in this room are not end-to-end encrypted.",
|
||||||
"Security": "Security",
|
"Security": "Security",
|
||||||
"Verify by emoji": "Verify by 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 comparing unique emoji.": "Verify by comparing unique emoji.",
|
|
||||||
"Verify by scanning": "Verify by scanning",
|
"Verify by scanning": "Verify by scanning",
|
||||||
"Ask %(displayName)s to scan your code:": "Ask %(displayName)s to scan your code:",
|
"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.",
|
"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!",
|
"You've successfully verified %(displayName)s!": "You've successfully verified %(displayName)s!",
|
||||||
"Got it": "Got it",
|
"Got it": "Got it",
|
||||||
"Verification timed out. Start verification again from their profile.": "Verification timed out. Start verification again from their profile.",
|
"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",
|
"%(name)s cancelled verifying": "%(name)s cancelled verifying",
|
||||||
"You accepted": "You accepted",
|
"You accepted": "You accepted",
|
||||||
"%(name)s accepted": "%(name)s accepted",
|
"%(name)s accepted": "%(name)s accepted",
|
||||||
|
"You declined": "You declined",
|
||||||
"You cancelled": "You cancelled",
|
"You cancelled": "You cancelled",
|
||||||
|
"%(name)s declined": "%(name)s declined",
|
||||||
"%(name)s cancelled": "%(name)s cancelled",
|
"%(name)s cancelled": "%(name)s cancelled",
|
||||||
|
"accepting …": "accepting …",
|
||||||
|
"declining …": "declining …",
|
||||||
"%(name)s wants to verify": "%(name)s wants to verify",
|
"%(name)s wants to verify": "%(name)s wants to verify",
|
||||||
"You sent a verification request": "You sent a verification request",
|
"You sent a verification request": "You sent a verification request",
|
||||||
"Error decrypting video": "Error decrypting video",
|
"Error decrypting video": "Error decrypting video",
|
||||||
|
|
|
@ -67,6 +67,18 @@ export default async function sendBugReport(bugReportEndpoint, opts) {
|
||||||
userAgent = window.navigator.userAgent;
|
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();
|
const client = MatrixClientPeg.get();
|
||||||
|
|
||||||
console.log("Sending bug report.");
|
console.log("Sending bug report.");
|
||||||
|
@ -76,6 +88,8 @@ export default async function sendBugReport(bugReportEndpoint, opts) {
|
||||||
body.append('app', 'riot-web');
|
body.append('app', 'riot-web');
|
||||||
body.append('version', version);
|
body.append('version', version);
|
||||||
body.append('user_agent', userAgent);
|
body.append('user_agent', userAgent);
|
||||||
|
body.append('installed_pwa', installedPWA);
|
||||||
|
body.append('touch_input', touchInput);
|
||||||
|
|
||||||
if (client) {
|
if (client) {
|
||||||
body.append('user_id', client.credentials.userId);
|
body.append('user_id', client.credentials.userId);
|
||||||
|
|
12
src/usercontent/index.html
Normal file
12
src/usercontent/index.html
Normal 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
49
src/usercontent/index.js
Normal 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);
|
||||||
|
}
|
||||||
|
};
|
|
@ -34,10 +34,15 @@ import Matrix from 'matrix-js-sdk';
|
||||||
const test_utils = require('../../test-utils');
|
const test_utils = require('../../test-utils');
|
||||||
const mockclock = require('../../mock-clock');
|
const mockclock = require('../../mock-clock');
|
||||||
|
|
||||||
|
import Adapter from "enzyme-adapter-react-16";
|
||||||
|
import { configure, mount } from "enzyme";
|
||||||
|
|
||||||
import Velocity from 'velocity-animate';
|
import Velocity from 'velocity-animate';
|
||||||
import MatrixClientContext from "../../../src/contexts/MatrixClientContext";
|
import MatrixClientContext from "../../../src/contexts/MatrixClientContext";
|
||||||
import RoomContext from "../../../src/contexts/RoomContext";
|
import RoomContext from "../../../src/contexts/RoomContext";
|
||||||
|
|
||||||
|
configure({ adapter: new Adapter() });
|
||||||
|
|
||||||
let client;
|
let client;
|
||||||
const room = new Matrix.Room();
|
const room = new Matrix.Room();
|
||||||
|
|
||||||
|
@ -251,4 +256,111 @@ describe('MessagePanel', function() {
|
||||||
}, 100);
|
}, 100);
|
||||||
}, 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
72
test/createRoom-test.js
Normal 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();
|
||||||
|
});
|
||||||
|
});
|
|
@ -51,7 +51,7 @@ export function createTestClient() {
|
||||||
getUserId: jest.fn().mockReturnValue("@userId:matrix.rog"),
|
getUserId: jest.fn().mockReturnValue("@userId:matrix.rog"),
|
||||||
|
|
||||||
getPushActionsForEvent: jest.fn(),
|
getPushActionsForEvent: jest.fn(),
|
||||||
getRoom: jest.fn().mockReturnValue(mkStubRoom()),
|
getRoom: jest.fn().mockImplementation(mkStubRoom),
|
||||||
getRooms: jest.fn().mockReturnValue([]),
|
getRooms: jest.fn().mockReturnValue([]),
|
||||||
getVisibleRooms: jest.fn().mockReturnValue([]),
|
getVisibleRooms: jest.fn().mockReturnValue([]),
|
||||||
getGroups: jest.fn().mockReturnValue([]),
|
getGroups: jest.fn().mockReturnValue([]),
|
||||||
|
@ -111,7 +111,7 @@ export function mkEvent(opts) {
|
||||||
if (opts.skey) {
|
if (opts.skey) {
|
||||||
event.state_key = opts.skey;
|
event.state_key = opts.skey;
|
||||||
} else if (["m.room.name", "m.room.topic", "m.room.create", "m.room.join_rules",
|
} 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) {
|
"com.example.state"].indexOf(opts.type) !== -1) {
|
||||||
event.state_key = "";
|
event.state_key = "";
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.1.tgz#6dff66c99d55ecf739ca53c492e626f1d12a33cc"
|
||||||
integrity sha512-pWB896KPGSGkp1XtyzRBftpTzwSOL0Gfk0wLvxt4f2mgzjY19o0LxJ3U25vNWTzsh7da+KTbuXQoQ3lOJZ8WHw==
|
integrity sha512-pWB896KPGSGkp1XtyzRBftpTzwSOL0Gfk0wLvxt4f2mgzjY19o0LxJ3U25vNWTzsh7da+KTbuXQoQ3lOJZ8WHw==
|
||||||
|
|
||||||
"matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop":
|
matrix-js-sdk@5.0.0:
|
||||||
version "4.0.0"
|
version "5.0.0"
|
||||||
resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/21e4c597d9633aef606871cf9ffffaf039142be3"
|
resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-5.0.0.tgz#dcbab35f1afdb35ef0364eb232e78e0fb7dc2a5b"
|
||||||
|
integrity sha512-A/aeE2Zn2OHq1n/9wIHCszrQZ7oXfThUHWi5Kz7illVCPUJ3JrZ31XVvx02k6vBasDcUtjAfZblHdTVN62cWLw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.8.3"
|
"@babel/runtime" "^7.8.3"
|
||||||
another-json "^0.2.0"
|
another-json "^0.2.0"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue