mirror of
https://github.com/vector-im/element-web.git
synced 2024-11-17 14:05:04 +08:00
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
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)
|
||||
===================================================================================================
|
||||
[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",
|
||||
"version": "2.0.0",
|
||||
"version": "2.1.0",
|
||||
"description": "SDK for matrix.org using React",
|
||||
"author": "matrix.org",
|
||||
"repository": {
|
||||
|
47
release.sh
47
release.sh
@ -9,4 +9,51 @@ set -e
|
||||
|
||||
cd `dirname $0`
|
||||
|
||||
for i in matrix-js-sdk
|
||||
do
|
||||
depver=`cat package.json | jq -r .dependencies[\"$i\"]`
|
||||
latestver=`yarn info -s $i dist-tags.next`
|
||||
if [ "$depver" != "$latestver" ]
|
||||
then
|
||||
echo "The latest version of $i is $latestver but package.json depends on $depver."
|
||||
echo -n "Type 'u' to auto-upgrade, 'c' to continue anyway, or 'a' to abort:"
|
||||
read resp
|
||||
if [ "$resp" != "u" ] && [ "$resp" != "c" ]
|
||||
then
|
||||
echo "Aborting."
|
||||
exit 1
|
||||
fi
|
||||
if [ "$resp" == "u" ]
|
||||
then
|
||||
echo "Upgrading $i to $latestver..."
|
||||
yarn add -E $i@$latestver
|
||||
git add -u
|
||||
# The `-e` flag opens the editor and gives you a chance to check
|
||||
# the upgrade for correctness.
|
||||
git commit -m "Upgrade $i to $latestver" -e
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
exec ./node_modules/matrix-js-sdk/release.sh -z "$@"
|
||||
|
||||
release="${1#v}"
|
||||
prerelease=0
|
||||
# We check if this build is a prerelease by looking to
|
||||
# see if the version has a hyphen in it. Crude,
|
||||
# but semver doesn't support postreleases so anything
|
||||
# with a hyphen is a prerelease.
|
||||
echo $release | grep -q '-' && prerelease=1
|
||||
|
||||
if [ $prerelease -eq 0 ]
|
||||
then
|
||||
# For a release, reset SDK deps back to the `develop` branch.
|
||||
for i in matrix-js-sdk
|
||||
do
|
||||
echo "Resetting $i to develop branch..."
|
||||
yarn add github:matrix-org/$i#develop
|
||||
git add -u
|
||||
git commit -m "Reset $i back to develop branch"
|
||||
done
|
||||
git push origin develop
|
||||
fi
|
||||
|
@ -57,6 +57,8 @@ function getRedactedUrl() {
|
||||
}
|
||||
|
||||
const customVariables = {
|
||||
// The Matomo installation at https://matomo.riot.im is currently configured
|
||||
// with a limit of 10 custom variables.
|
||||
'App Platform': {
|
||||
id: 1,
|
||||
expl: _td('The platform you\'re on'),
|
||||
@ -64,7 +66,7 @@ const customVariables = {
|
||||
},
|
||||
'App Version': {
|
||||
id: 2,
|
||||
expl: _td('The version of Riot.im'),
|
||||
expl: _td('The version of Riot'),
|
||||
example: '15.0.0',
|
||||
},
|
||||
'User Type': {
|
||||
@ -87,20 +89,25 @@ const customVariables = {
|
||||
expl: _td('Whether or not you\'re using the Richtext mode of the Rich Text Editor'),
|
||||
example: 'off',
|
||||
},
|
||||
'Breadcrumbs': {
|
||||
id: 9,
|
||||
expl: _td("Whether or not you're using the 'breadcrumbs' feature (avatars above the room list)"),
|
||||
example: 'disabled',
|
||||
},
|
||||
'Homeserver URL': {
|
||||
id: 7,
|
||||
expl: _td('Your homeserver\'s URL'),
|
||||
example: 'https://matrix.org',
|
||||
},
|
||||
'Identity Server URL': {
|
||||
'Touch Input': {
|
||||
id: 8,
|
||||
expl: _td('Your identity server\'s URL'),
|
||||
example: 'https://vector.im',
|
||||
expl: _td("Whether you're using Riot on a device where touch is the primary input mechanism"),
|
||||
example: 'false',
|
||||
},
|
||||
'Breadcrumbs': {
|
||||
id: 9,
|
||||
expl: _td("Whether or not you're using the 'breadcrumbs' feature (avatars above the room list)"),
|
||||
example: 'disabled',
|
||||
},
|
||||
'Installed PWA': {
|
||||
id: 10,
|
||||
expl: _td("Whether you're using Riot as an installed Progressive Web App"),
|
||||
example: 'false',
|
||||
},
|
||||
};
|
||||
|
||||
@ -190,6 +197,20 @@ class Analytics {
|
||||
this._setVisitVariable('Instance', window.location.pathname);
|
||||
}
|
||||
|
||||
let installedPWA = "unknown";
|
||||
try {
|
||||
// Known to work at least for desktop Chrome
|
||||
installedPWA = window.matchMedia('(display-mode: standalone)').matches;
|
||||
} catch (e) { }
|
||||
this._setVisitVariable('Installed PWA', installedPWA);
|
||||
|
||||
let touchInput = "unknown";
|
||||
try {
|
||||
// MDN claims broad support across browsers
|
||||
touchInput = window.matchMedia('(pointer: coarse)').matches;
|
||||
} catch (e) { }
|
||||
this._setVisitVariable('Touch Input', touchInput);
|
||||
|
||||
// start heartbeat
|
||||
this._heartbeatIntervalID = window.setInterval(this.ping.bind(this), HEARTBEAT_INTERVAL);
|
||||
}
|
||||
@ -291,11 +312,9 @@ class Analytics {
|
||||
if (!config.piwik) return;
|
||||
|
||||
const whitelistedHSUrls = config.piwik.whitelistedHSUrls || [];
|
||||
const whitelistedISUrls = config.piwik.whitelistedISUrls || [];
|
||||
|
||||
this._setVisitVariable('User Type', isGuest ? 'Guest' : 'Logged In');
|
||||
this._setVisitVariable('Homeserver URL', whitelistRedact(whitelistedHSUrls, homeserverUrl));
|
||||
this._setVisitVariable('Identity Server URL', whitelistRedact(whitelistedISUrls, identityServerUrl));
|
||||
}
|
||||
|
||||
setBreadcrumbs(state) {
|
||||
@ -328,7 +347,7 @@ class Analytics {
|
||||
},
|
||||
),
|
||||
},
|
||||
{ expl: _td('Your User Agent'), value: navigator.userAgent },
|
||||
{ expl: _td('Your user agent'), value: navigator.userAgent },
|
||||
{ expl: _td('Your device resolution'), value: resolution },
|
||||
];
|
||||
|
||||
@ -337,7 +356,7 @@ class Analytics {
|
||||
title: _t('Analytics'),
|
||||
description: <div className="mx_AnalyticsModal">
|
||||
<div>
|
||||
{ _t('The information being sent to us to help make Riot.im better includes:') }
|
||||
{ _t('The information being sent to us to help make Riot better includes:') }
|
||||
</div>
|
||||
<table>
|
||||
{ rows.map((row) => <tr key={row[0]}>
|
||||
|
@ -435,7 +435,7 @@ async function _doSetLoggedIn(credentials, clearStorage) {
|
||||
}
|
||||
}
|
||||
|
||||
Analytics.setLoggedIn(credentials.guest, credentials.homeserverUrl, credentials.identityServerUrl);
|
||||
Analytics.setLoggedIn(credentials.guest, credentials.homeserverUrl);
|
||||
|
||||
if (localStorage) {
|
||||
try {
|
||||
|
@ -413,10 +413,6 @@ export default class MessagePanel extends React.Component {
|
||||
};
|
||||
|
||||
_getEventTiles() {
|
||||
const DateSeparator = sdk.getComponent('messages.DateSeparator');
|
||||
const EventListSummary = sdk.getComponent('views.elements.EventListSummary');
|
||||
const MemberEventListSummary = sdk.getComponent('views.elements.MemberEventListSummary');
|
||||
|
||||
this.eventNodes = {};
|
||||
|
||||
let i;
|
||||
@ -458,199 +454,48 @@ export default class MessagePanel extends React.Component {
|
||||
this._readReceiptsByEvent = this._getReadReceiptsByShownEvent();
|
||||
}
|
||||
|
||||
let grouper = null;
|
||||
|
||||
for (i = 0; i < this.props.events.length; i++) {
|
||||
const mxEv = this.props.events[i];
|
||||
const eventId = mxEv.getId();
|
||||
const last = (mxEv === lastShownEvent);
|
||||
|
||||
// Wrap initial room creation events into an EventListSummary
|
||||
// Grouping only events sent by the same user that sent the `m.room.create` and only until
|
||||
// the first non-state event or membership event which is not regarding the sender of the `m.room.create` event
|
||||
const shouldGroup = (ev) => {
|
||||
if (ev.getType() === "m.room.member"
|
||||
&& (ev.getStateKey() !== mxEv.getSender() || ev.getContent()["membership"] !== "join")) {
|
||||
return false;
|
||||
}
|
||||
if (ev.isState() && ev.getSender() === mxEv.getSender()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
// events that we include in the group but then eject out and place
|
||||
// above the group.
|
||||
const shouldEject = (ev) => {
|
||||
if (ev.getType() === "m.room.encryption") return true;
|
||||
return false;
|
||||
};
|
||||
if (mxEv.getType() === "m.room.create") {
|
||||
let summaryReadMarker = null;
|
||||
const ts1 = mxEv.getTs();
|
||||
|
||||
if (this._wantsDateSeparator(prevEvent, mxEv.getDate())) {
|
||||
const dateSeparator = <li key={ts1+'~'}><DateSeparator key={ts1+'~'} ts={ts1} /></li>;
|
||||
ret.push(dateSeparator);
|
||||
if (grouper) {
|
||||
if (grouper.shouldGroup(mxEv)) {
|
||||
grouper.add(mxEv);
|
||||
continue;
|
||||
} else {
|
||||
// not part of group, so get the group tiles, close the
|
||||
// group, and continue like a normal event
|
||||
ret.push(...grouper.getTiles());
|
||||
prevEvent = grouper.getNewPrevEvent();
|
||||
grouper = null;
|
||||
}
|
||||
|
||||
// If RM event is the first in the summary, append the RM after the summary
|
||||
summaryReadMarker = summaryReadMarker || this._readMarkerForEvent(mxEv.getId());
|
||||
|
||||
// If this m.room.create event should be shown (room upgrade) then show it before the summary
|
||||
if (this._shouldShowEvent(mxEv)) {
|
||||
// pass in the mxEv as prevEvent as well so no extra DateSeparator is rendered
|
||||
ret.push(...this._getTilesForEvent(mxEv, mxEv, false));
|
||||
}
|
||||
|
||||
const summarisedEvents = []; // Don't add m.room.create here as we don't want it inside the summary
|
||||
const ejectedEvents = [];
|
||||
for (;i + 1 < this.props.events.length; i++) {
|
||||
const collapsedMxEv = this.props.events[i + 1];
|
||||
|
||||
// Ignore redacted/hidden member events
|
||||
if (!this._shouldShowEvent(collapsedMxEv)) {
|
||||
// If this hidden event is the RM and in or at end of a summary put RM after the summary.
|
||||
summaryReadMarker = summaryReadMarker || this._readMarkerForEvent(collapsedMxEv.getId());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!shouldGroup(collapsedMxEv) || this._wantsDateSeparator(mxEv, collapsedMxEv.getDate())) {
|
||||
break;
|
||||
}
|
||||
|
||||
// If RM event is in the summary, mark it as such and the RM will be appended after the summary.
|
||||
summaryReadMarker = summaryReadMarker || this._readMarkerForEvent(collapsedMxEv.getId());
|
||||
|
||||
if (shouldEject(collapsedMxEv)) {
|
||||
ejectedEvents.push(collapsedMxEv);
|
||||
} else {
|
||||
summarisedEvents.push(collapsedMxEv);
|
||||
}
|
||||
}
|
||||
|
||||
// At this point, i = the index of the last event in the summary sequence
|
||||
const eventTiles = summarisedEvents.map((e) => {
|
||||
// In order to prevent DateSeparators from appearing in the expanded form
|
||||
// of EventListSummary, render each member event as if the previous
|
||||
// one was itself. This way, the timestamp of the previous event === the
|
||||
// timestamp of the current event, and no DateSeparator is inserted.
|
||||
return this._getTilesForEvent(e, e, e === lastShownEvent);
|
||||
}).reduce((a, b) => a.concat(b), []);
|
||||
|
||||
for (const ejected of ejectedEvents) {
|
||||
ret.push(...this._getTilesForEvent(mxEv, ejected, last));
|
||||
}
|
||||
|
||||
// Get sender profile from the latest event in the summary as the m.room.create doesn't contain one
|
||||
const ev = this.props.events[i];
|
||||
ret.push(<EventListSummary
|
||||
key="roomcreationsummary"
|
||||
events={summarisedEvents}
|
||||
onToggle={this._onHeightChanged} // Update scroll state
|
||||
summaryMembers={[ev.sender]}
|
||||
summaryText={_t("%(creator)s created and configured the room.", {
|
||||
creator: ev.sender ? ev.sender.name : ev.getSender(),
|
||||
})}
|
||||
>
|
||||
{ eventTiles }
|
||||
</EventListSummary>);
|
||||
|
||||
if (summaryReadMarker) {
|
||||
ret.push(summaryReadMarker);
|
||||
}
|
||||
|
||||
prevEvent = mxEv;
|
||||
continue;
|
||||
}
|
||||
|
||||
const wantTile = this._shouldShowEvent(mxEv);
|
||||
|
||||
// Wrap consecutive member events in a ListSummary, ignore if redacted
|
||||
if (isMembershipChange(mxEv) && wantTile) {
|
||||
let summaryReadMarker = null;
|
||||
const ts1 = mxEv.getTs();
|
||||
// Ensure that the key of the MemberEventListSummary does not change with new
|
||||
// member events. This will prevent it from being re-created unnecessarily, and
|
||||
// instead will allow new props to be provided. In turn, the shouldComponentUpdate
|
||||
// method on MELS can be used to prevent unnecessary renderings.
|
||||
//
|
||||
// Whilst back-paginating with a MELS at the top of the panel, prevEvent will be null,
|
||||
// so use the key "membereventlistsummary-initial". Otherwise, use the ID of the first
|
||||
// membership event, which will not change during forward pagination.
|
||||
const key = "membereventlistsummary-" + (prevEvent ? mxEv.getId() : "initial");
|
||||
|
||||
if (this._wantsDateSeparator(prevEvent, mxEv.getDate())) {
|
||||
const dateSeparator = <li key={ts1+'~'}><DateSeparator key={ts1+'~'} ts={ts1} /></li>;
|
||||
ret.push(dateSeparator);
|
||||
for (const Grouper of groupers) {
|
||||
if (Grouper.canStartGroup(this, mxEv)) {
|
||||
grouper = new Grouper(this, mxEv, prevEvent, lastShownEvent);
|
||||
}
|
||||
|
||||
// If RM event is the first in the MELS, append the RM after MELS
|
||||
summaryReadMarker = summaryReadMarker || this._readMarkerForEvent(mxEv.getId());
|
||||
|
||||
const summarisedEvents = [mxEv];
|
||||
for (;i + 1 < this.props.events.length; i++) {
|
||||
const collapsedMxEv = this.props.events[i + 1];
|
||||
|
||||
// Ignore redacted/hidden member events
|
||||
if (!this._shouldShowEvent(collapsedMxEv)) {
|
||||
// If this hidden event is the RM and in or at end of a MELS put RM after MELS.
|
||||
summaryReadMarker = summaryReadMarker || this._readMarkerForEvent(collapsedMxEv.getId());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isMembershipChange(collapsedMxEv) ||
|
||||
this._wantsDateSeparator(mxEv, collapsedMxEv.getDate())) {
|
||||
break;
|
||||
}
|
||||
|
||||
// If RM event is in MELS mark it as such and the RM will be appended after MELS.
|
||||
summaryReadMarker = summaryReadMarker || this._readMarkerForEvent(collapsedMxEv.getId());
|
||||
|
||||
summarisedEvents.push(collapsedMxEv);
|
||||
}
|
||||
|
||||
let highlightInMels = false;
|
||||
|
||||
// At this point, i = the index of the last event in the summary sequence
|
||||
let eventTiles = summarisedEvents.map((e) => {
|
||||
if (e.getId() === this.props.highlightedEventId) {
|
||||
highlightInMels = true;
|
||||
}
|
||||
// In order to prevent DateSeparators from appearing in the expanded form
|
||||
// of MemberEventListSummary, render each member event as if the previous
|
||||
// one was itself. This way, the timestamp of the previous event === the
|
||||
// timestamp of the current event, and no DateSeparator is inserted.
|
||||
return this._getTilesForEvent(e, e, e === lastShownEvent);
|
||||
}).reduce((a, b) => a.concat(b), []);
|
||||
|
||||
if (eventTiles.length === 0) {
|
||||
eventTiles = null;
|
||||
}
|
||||
|
||||
ret.push(<MemberEventListSummary key={key}
|
||||
events={summarisedEvents}
|
||||
onToggle={this._onHeightChanged} // Update scroll state
|
||||
startExpanded={highlightInMels}
|
||||
>
|
||||
{ eventTiles }
|
||||
</MemberEventListSummary>);
|
||||
|
||||
if (summaryReadMarker) {
|
||||
ret.push(summaryReadMarker);
|
||||
}
|
||||
|
||||
prevEvent = mxEv;
|
||||
continue;
|
||||
}
|
||||
if (!grouper) {
|
||||
const wantTile = this._shouldShowEvent(mxEv);
|
||||
if (wantTile) {
|
||||
// make sure we unpack the array returned by _getTilesForEvent,
|
||||
// otherwise react will auto-generate keys and we will end up
|
||||
// replacing all of the DOM elements every time we paginate.
|
||||
ret.push(...this._getTilesForEvent(prevEvent, mxEv, last));
|
||||
prevEvent = mxEv;
|
||||
}
|
||||
|
||||
if (wantTile) {
|
||||
// make sure we unpack the array returned by _getTilesForEvent,
|
||||
// otherwise react will auto-generate keys and we will end up
|
||||
// replacing all of the DOM elements every time we paginate.
|
||||
ret.push(...this._getTilesForEvent(prevEvent, mxEv, last));
|
||||
prevEvent = mxEv;
|
||||
const readMarker = this._readMarkerForEvent(eventId, i >= lastShownNonLocalEchoIndex);
|
||||
if (readMarker) ret.push(readMarker);
|
||||
}
|
||||
}
|
||||
|
||||
const readMarker = this._readMarkerForEvent(eventId, i >= lastShownNonLocalEchoIndex);
|
||||
if (readMarker) ret.push(readMarker);
|
||||
if (grouper) {
|
||||
ret.push(...grouper.getTiles());
|
||||
}
|
||||
|
||||
return ret;
|
||||
@ -961,3 +806,222 @@ export default class MessagePanel extends React.Component {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/* Grouper classes determine when events can be grouped together in a summary.
|
||||
* Groupers should have the following methods:
|
||||
* - canStartGroup (static): determines if a new group should be started with the
|
||||
* given event
|
||||
* - shouldGroup: determines if the given event should be added to an existing group
|
||||
* - add: adds an event to an existing group (should only be called if shouldGroup
|
||||
* return true)
|
||||
* - getTiles: returns the tiles that represent the group
|
||||
* - getNewPrevEvent: returns the event that should be used as the new prevEvent
|
||||
* when determining things such as whether a date separator is necessary
|
||||
*/
|
||||
|
||||
// Wrap initial room creation events into an EventListSummary
|
||||
// Grouping only events sent by the same user that sent the `m.room.create` and only until
|
||||
// the first non-state event or membership event which is not regarding the sender of the `m.room.create` event
|
||||
class CreationGrouper {
|
||||
static canStartGroup = function(panel, ev) {
|
||||
return ev.getType() === "m.room.create";
|
||||
};
|
||||
|
||||
constructor(panel, createEvent, prevEvent, lastShownEvent) {
|
||||
this.panel = panel;
|
||||
this.createEvent = createEvent;
|
||||
this.prevEvent = prevEvent;
|
||||
this.lastShownEvent = lastShownEvent;
|
||||
this.events = [];
|
||||
// events that we include in the group but then eject out and place
|
||||
// above the group.
|
||||
this.ejectedEvents = [];
|
||||
this.readMarker = panel._readMarkerForEvent(createEvent.getId());
|
||||
}
|
||||
|
||||
shouldGroup(ev) {
|
||||
const panel = this.panel;
|
||||
const createEvent = this.createEvent;
|
||||
if (!panel._shouldShowEvent(ev)) {
|
||||
this.readMarker = this.readMarker || panel._readMarkerForEvent(ev.getId());
|
||||
return true;
|
||||
}
|
||||
if (panel._wantsDateSeparator(this.createEvent, ev.getDate())) {
|
||||
return false;
|
||||
}
|
||||
if (ev.getType() === "m.room.member"
|
||||
&& (ev.getStateKey() !== createEvent.getSender() || ev.getContent()["membership"] !== "join")) {
|
||||
return false;
|
||||
}
|
||||
if (ev.isState() && ev.getSender() === createEvent.getSender()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
add(ev) {
|
||||
const panel = this.panel;
|
||||
this.readMarker = this.readMarker || panel._readMarkerForEvent(ev.getId());
|
||||
if (!panel._shouldShowEvent(ev)) {
|
||||
return;
|
||||
}
|
||||
if (ev.getType() === "m.room.encryption") {
|
||||
this.ejectedEvents.push(ev);
|
||||
} else {
|
||||
this.events.push(ev);
|
||||
}
|
||||
}
|
||||
|
||||
getTiles() {
|
||||
const DateSeparator = sdk.getComponent('messages.DateSeparator');
|
||||
const EventListSummary = sdk.getComponent('views.elements.EventListSummary');
|
||||
|
||||
const panel = this.panel;
|
||||
const ret = [];
|
||||
const createEvent = this.createEvent;
|
||||
const lastShownEvent = this.lastShownEvent;
|
||||
|
||||
if (panel._wantsDateSeparator(this.prevEvent, createEvent.getDate())) {
|
||||
const ts = createEvent.getTs();
|
||||
ret.push(
|
||||
<li key={ts+'~'}><DateSeparator key={ts+'~'} ts={ts} /></li>,
|
||||
);
|
||||
}
|
||||
|
||||
// If this m.room.create event should be shown (room upgrade) then show it before the summary
|
||||
if (panel._shouldShowEvent(createEvent)) {
|
||||
// pass in the createEvent as prevEvent as well so no extra DateSeparator is rendered
|
||||
ret.push(...panel._getTilesForEvent(createEvent, createEvent, false));
|
||||
}
|
||||
|
||||
for (const ejected of this.ejectedEvents) {
|
||||
ret.push(...panel._getTilesForEvent(
|
||||
createEvent, ejected, createEvent === lastShownEvent,
|
||||
));
|
||||
}
|
||||
|
||||
const eventTiles = this.events.map((e) => {
|
||||
// In order to prevent DateSeparators from appearing in the expanded form
|
||||
// of EventListSummary, render each member event as if the previous
|
||||
// one was itself. This way, the timestamp of the previous event === the
|
||||
// timestamp of the current event, and no DateSeparator is inserted.
|
||||
return panel._getTilesForEvent(e, e, e === lastShownEvent);
|
||||
}).reduce((a, b) => a.concat(b), []);
|
||||
// Get sender profile from the latest event in the summary as the m.room.create doesn't contain one
|
||||
const ev = this.events[this.events.length - 1];
|
||||
ret.push(
|
||||
<EventListSummary
|
||||
key="roomcreationsummary"
|
||||
events={this.events}
|
||||
onToggle={panel._onHeightChanged} // Update scroll state
|
||||
summaryMembers={[ev.sender]}
|
||||
summaryText={_t("%(creator)s created and configured the room.", {
|
||||
creator: ev.sender ? ev.sender.name : ev.getSender(),
|
||||
})}
|
||||
>
|
||||
{ eventTiles }
|
||||
</EventListSummary>,
|
||||
);
|
||||
|
||||
if (this.readMarker) {
|
||||
ret.push(this.readMarker);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
getNewPrevEvent() {
|
||||
return this.createEvent;
|
||||
}
|
||||
}
|
||||
|
||||
// Wrap consecutive member events in a ListSummary, ignore if redacted
|
||||
class MemberGrouper {
|
||||
static canStartGroup = function(panel, ev) {
|
||||
return panel._shouldShowEvent(ev) && isMembershipChange(ev);
|
||||
}
|
||||
|
||||
constructor(panel, ev, prevEvent, lastShownEvent) {
|
||||
this.panel = panel;
|
||||
this.readMarker = panel._readMarkerForEvent(ev.getId());
|
||||
this.events = [ev];
|
||||
this.prevEvent = prevEvent;
|
||||
this.lastShownEvent = lastShownEvent;
|
||||
}
|
||||
|
||||
shouldGroup(ev) {
|
||||
return isMembershipChange(ev);
|
||||
}
|
||||
|
||||
add(ev) {
|
||||
this.readMarker = this.readMarker || this.panel._readMarkerForEvent(ev.getId());
|
||||
this.events.push(ev);
|
||||
}
|
||||
|
||||
getTiles() {
|
||||
const DateSeparator = sdk.getComponent('messages.DateSeparator');
|
||||
const MemberEventListSummary = sdk.getComponent('views.elements.MemberEventListSummary');
|
||||
|
||||
const panel = this.panel;
|
||||
const lastShownEvent = this.lastShownEvent;
|
||||
const ret = [];
|
||||
|
||||
if (panel._wantsDateSeparator(this.prevEvent, this.events[0].getDate())) {
|
||||
const ts = this.events[0].getTs();
|
||||
ret.push(
|
||||
<li key={ts+'~'}><DateSeparator key={ts+'~'} ts={ts} /></li>,
|
||||
);
|
||||
}
|
||||
|
||||
// Ensure that the key of the MemberEventListSummary does not change with new
|
||||
// member events. This will prevent it from being re-created unnecessarily, and
|
||||
// instead will allow new props to be provided. In turn, the shouldComponentUpdate
|
||||
// method on MELS can be used to prevent unnecessary renderings.
|
||||
//
|
||||
// Whilst back-paginating with a MELS at the top of the panel, prevEvent will be null,
|
||||
// so use the key "membereventlistsummary-initial". Otherwise, use the ID of the first
|
||||
// membership event, which will not change during forward pagination.
|
||||
const key = "membereventlistsummary-" + (
|
||||
this.prevEvent ? this.events[0].getId() : "initial"
|
||||
);
|
||||
|
||||
let highlightInMels;
|
||||
let eventTiles = this.events.map((e) => {
|
||||
if (e.getId() === panel.props.highlightedEventId) {
|
||||
highlightInMels = true;
|
||||
}
|
||||
// In order to prevent DateSeparators from appearing in the expanded form
|
||||
// of MemberEventListSummary, render each member event as if the previous
|
||||
// one was itself. This way, the timestamp of the previous event === the
|
||||
// timestamp of the current event, and no DateSeparator is inserted.
|
||||
return panel._getTilesForEvent(e, e, e === lastShownEvent);
|
||||
}).reduce((a, b) => a.concat(b), []);
|
||||
|
||||
if (eventTiles.length === 0) {
|
||||
eventTiles = null;
|
||||
}
|
||||
|
||||
ret.push(
|
||||
<MemberEventListSummary key={key}
|
||||
events={this.events}
|
||||
onToggle={panel._onHeightChanged} // Update scroll state
|
||||
startExpanded={highlightInMels}
|
||||
>
|
||||
{ eventTiles }
|
||||
</MemberEventListSummary>,
|
||||
);
|
||||
|
||||
if (this.readMarker) {
|
||||
ret.push(this.readMarker);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
getNewPrevEvent() {
|
||||
return this.events[0];
|
||||
}
|
||||
}
|
||||
|
||||
// all the grouper classes that we use
|
||||
const groupers = [CreationGrouper, MemberGrouper];
|
||||
|
@ -31,7 +31,7 @@ import dis from "../../../dispatcher";
|
||||
import IdentityAuthClient from "../../../IdentityAuthClient";
|
||||
import Modal from "../../../Modal";
|
||||
import {humanizeTime} from "../../../utils/humanize";
|
||||
import createRoom from "../../../createRoom";
|
||||
import createRoom, {canEncryptToAllUsers} from "../../../createRoom";
|
||||
import {inviteMultipleToRoom} from "../../../RoomInvite";
|
||||
import SettingsStore from '../../../settings/SettingsStore';
|
||||
|
||||
@ -535,11 +535,7 @@ export default class InviteDialog extends React.PureComponent {
|
||||
// Check whether all users have uploaded device keys before.
|
||||
// If so, enable encryption in the new room.
|
||||
const client = MatrixClientPeg.get();
|
||||
const usersToDevicesMap = await client.downloadKeys(targetIds);
|
||||
const allHaveDeviceKeys = Object.values(usersToDevicesMap).every(devices => {
|
||||
// `devices` is an object of the form { deviceId: deviceInfo, ... }.
|
||||
return Object.keys(devices).length > 0;
|
||||
});
|
||||
const allHaveDeviceKeys = await canEncryptToAllUsers(client, targetIds);
|
||||
if (allHaveDeviceKeys) {
|
||||
createRoomOptions.encryption = true;
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ import {decryptFile} from '../../../utils/DecryptFile';
|
||||
import Tinter from '../../../Tinter';
|
||||
import request from 'browser-request';
|
||||
import Modal from '../../../Modal';
|
||||
import SdkConfig from "../../../SdkConfig";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
|
||||
|
||||
// A cached tinted copy of require("../../../../res/img/download.svg")
|
||||
@ -94,84 +94,6 @@ Tinter.registerTintable(updateTintedDownloadImage);
|
||||
// The downside of using a second domain is that it complicates hosting,
|
||||
// the downside of using a sandboxed iframe is that the browers are overly
|
||||
// restrictive in what you are allowed to do with the generated URL.
|
||||
//
|
||||
// For now given how unusable the blobs generated in sandboxed iframes are we
|
||||
// default to using a renderer hosted on "usercontent.riot.im". This is
|
||||
// overridable so that people running their own version of the client can
|
||||
// choose a different renderer.
|
||||
//
|
||||
// To that end the current version of the blob generation is the following
|
||||
// html:
|
||||
//
|
||||
// <html><head><script>
|
||||
// var params = window.location.search.substring(1).split('&');
|
||||
// var lockOrigin;
|
||||
// for (var i = 0; i < params.length; ++i) {
|
||||
// var parts = params[i].split('=');
|
||||
// if (parts[0] == 'origin') lockOrigin = decodeURIComponent(parts[1]);
|
||||
// }
|
||||
// window.onmessage=function(e){
|
||||
// if (lockOrigin === undefined || e.origin === lockOrigin) eval("("+e.data.code+")")(e);
|
||||
// }
|
||||
// </script></head><body></body></html>
|
||||
//
|
||||
// This waits to receive a message event sent using the window.postMessage API.
|
||||
// When it receives the event it evals a javascript function in data.code and
|
||||
// runs the function passing the event as an argument. This version adds
|
||||
// support for a query parameter controlling the origin from which messages
|
||||
// will be processed as an extra layer of security (note that the default URL
|
||||
// is still 'v1' since it is backwards compatible).
|
||||
//
|
||||
// In particular it means that the rendering function can be written as a
|
||||
// ordinary javascript function which then is turned into a string using
|
||||
// toString().
|
||||
//
|
||||
const DEFAULT_CROSS_ORIGIN_RENDERER = "https://usercontent.riot.im/v1.html";
|
||||
|
||||
/**
|
||||
* Render the attachment inside the iframe.
|
||||
* We can't use imported libraries here so this has to be vanilla JS.
|
||||
*/
|
||||
function remoteRender(event) {
|
||||
const data = event.data;
|
||||
|
||||
const img = document.createElement("img");
|
||||
img.id = "img";
|
||||
img.src = data.imgSrc;
|
||||
|
||||
const a = document.createElement("a");
|
||||
a.id = "a";
|
||||
a.rel = data.rel;
|
||||
a.target = data.target;
|
||||
a.download = data.download;
|
||||
a.style = data.style;
|
||||
a.style.fontFamily = "Arial, Helvetica, Sans-Serif";
|
||||
a.href = window.URL.createObjectURL(data.blob);
|
||||
a.appendChild(img);
|
||||
a.appendChild(document.createTextNode(data.textContent));
|
||||
|
||||
const body = document.body;
|
||||
// Don't display scrollbars if the link takes more than one line
|
||||
// to display.
|
||||
body.style = "margin: 0px; overflow: hidden";
|
||||
body.appendChild(a);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the tint inside the iframe.
|
||||
* We can't use imported libraries here so this has to be vanilla JS.
|
||||
*/
|
||||
function remoteSetTint(event) {
|
||||
const data = event.data;
|
||||
|
||||
const img = document.getElementById("img");
|
||||
img.src = data.imgSrc;
|
||||
img.style = data.imgStyle;
|
||||
|
||||
const a = document.getElementById("a");
|
||||
a.style = data.style;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the current CSS style for a DOMElement.
|
||||
@ -283,7 +205,6 @@ export default createReactClass({
|
||||
// will be inside the iframe so we wont be able to update
|
||||
// it directly.
|
||||
this._iframe.current.contentWindow.postMessage({
|
||||
code: remoteSetTint.toString(),
|
||||
imgSrc: tintedDownloadImageURL,
|
||||
style: computedStyle(this._dummyLink.current),
|
||||
}, "*");
|
||||
@ -306,7 +227,7 @@ export default createReactClass({
|
||||
// Wait for the user to click on the link before downloading
|
||||
// and decrypting the attachment.
|
||||
let decrypting = false;
|
||||
const decrypt = () => {
|
||||
const decrypt = (e) => {
|
||||
if (decrypting) {
|
||||
return false;
|
||||
}
|
||||
@ -323,16 +244,15 @@ export default createReactClass({
|
||||
});
|
||||
}).finally(() => {
|
||||
decrypting = false;
|
||||
return;
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<span className="mx_MFileBody">
|
||||
<div className="mx_MFileBody_download">
|
||||
<a href="javascript:void(0)" onClick={decrypt}>
|
||||
<AccessibleButton onClick={decrypt}>
|
||||
{ _t("Decrypt %(text)s", { text: text }) }
|
||||
</a>
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
</span>
|
||||
);
|
||||
@ -341,7 +261,6 @@ export default createReactClass({
|
||||
// When the iframe loads we tell it to render a download link
|
||||
const onIframeLoad = (ev) => {
|
||||
ev.target.contentWindow.postMessage({
|
||||
code: remoteRender.toString(),
|
||||
imgSrc: tintedDownloadImageURL,
|
||||
style: computedStyle(this._dummyLink.current),
|
||||
blob: this.state.decryptedBlob,
|
||||
@ -349,19 +268,13 @@ export default createReactClass({
|
||||
// will have the correct name when the user tries to download it.
|
||||
// We can't provide a Content-Disposition header like we would for HTTP.
|
||||
download: fileName,
|
||||
rel: "noopener",
|
||||
target: "_blank",
|
||||
textContent: _t("Download %(text)s", { text: text }),
|
||||
}, "*");
|
||||
};
|
||||
|
||||
// If the attachment is encryped then put the link inside an iframe.
|
||||
let renderer_url = DEFAULT_CROSS_ORIGIN_RENDERER;
|
||||
const appConfig = SdkConfig.get();
|
||||
if (appConfig && appConfig.cross_origin_renderer_url) {
|
||||
renderer_url = appConfig.cross_origin_renderer_url;
|
||||
}
|
||||
renderer_url += "?origin=" + encodeURIComponent(window.location.origin);
|
||||
const url = "usercontent/"; // XXX: this path should probably be passed from the skin
|
||||
|
||||
// If the attachment is encrypted then put the link inside an iframe.
|
||||
return (
|
||||
<span className="mx_MFileBody">
|
||||
<div className="mx_MFileBody_download">
|
||||
@ -373,7 +286,11 @@ export default createReactClass({
|
||||
*/ }
|
||||
<a ref={this._dummyLink} />
|
||||
</div>
|
||||
<iframe src={renderer_url} onLoad={onIframeLoad} ref={this._iframe} />
|
||||
<iframe
|
||||
src={`${url}?origin=${encodeURIComponent(window.location.origin)}`}
|
||||
onLoad={onIframeLoad}
|
||||
ref={this._iframe}
|
||||
sandbox="allow-scripts allow-downloads" />
|
||||
</div>
|
||||
</span>
|
||||
);
|
||||
|
@ -59,7 +59,6 @@ export default class MKeyVerificationRequest extends React.Component {
|
||||
};
|
||||
|
||||
_onAcceptClicked = async () => {
|
||||
this.setState({acceptOrCancelClicked: true});
|
||||
const request = this.props.mxEvent.verificationRequest;
|
||||
if (request) {
|
||||
try {
|
||||
@ -72,7 +71,6 @@ export default class MKeyVerificationRequest extends React.Component {
|
||||
};
|
||||
|
||||
_onRejectClicked = async () => {
|
||||
this.setState({acceptOrCancelClicked: true});
|
||||
const request = this.props.mxEvent.verificationRequest;
|
||||
if (request) {
|
||||
try {
|
||||
@ -96,10 +94,20 @@ export default class MKeyVerificationRequest extends React.Component {
|
||||
_cancelledLabel(userId) {
|
||||
const client = MatrixClientPeg.get();
|
||||
const myUserId = client.getUserId();
|
||||
const {cancellationCode} = this.props.mxEvent.verificationRequest;
|
||||
const declined = cancellationCode === "m.user";
|
||||
if (userId === myUserId) {
|
||||
return _t("You cancelled");
|
||||
if (declined) {
|
||||
return _t("You declined");
|
||||
} else {
|
||||
return _t("You cancelled");
|
||||
}
|
||||
} else {
|
||||
return _t("%(name)s cancelled", {name: getNameForEventRoom(userId, this.props.mxEvent.getRoomId())});
|
||||
if (declined) {
|
||||
return _t("%(name)s declined", {name: getNameForEventRoom(userId, this.props.mxEvent.getRoomId())});
|
||||
} else {
|
||||
return _t("%(name)s cancelled", {name: getNameForEventRoom(userId, this.props.mxEvent.getRoomId())});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -118,15 +126,19 @@ export default class MKeyVerificationRequest extends React.Component {
|
||||
let subtitle;
|
||||
let stateNode;
|
||||
|
||||
const accepted = request.ready || request.started || request.done;
|
||||
if (accepted || request.cancelled) {
|
||||
if (!request.canAccept) {
|
||||
let stateLabel;
|
||||
const accepted = request.ready || request.started || request.done;
|
||||
if (accepted) {
|
||||
stateLabel = (<AccessibleButton onClick={this._openRequest}>
|
||||
{this._acceptedLabel(request.receivingUserId)}
|
||||
</AccessibleButton>);
|
||||
} else {
|
||||
} else if (request.cancelled) {
|
||||
stateLabel = this._cancelledLabel(request.cancellingUserId);
|
||||
} else if (request.accepting) {
|
||||
stateLabel = _t("accepting …");
|
||||
} else if (request.declining) {
|
||||
stateLabel = _t("declining …");
|
||||
}
|
||||
stateNode = (<div className="mx_cryptoEvent_state">{stateLabel}</div>);
|
||||
}
|
||||
@ -137,11 +149,10 @@ export default class MKeyVerificationRequest extends React.Component {
|
||||
_t("%(name)s wants to verify", {name})}</div>);
|
||||
subtitle = (<div className="mx_cryptoEvent_subtitle">{
|
||||
userLabelForEventRoom(request.requestingUserId, mxEvent.getRoomId())}</div>);
|
||||
if (request.requested && !request.observeOnly) {
|
||||
const disabled = this.state.acceptOrCancelClicked;
|
||||
if (request.canAccept) {
|
||||
stateNode = (<div className="mx_cryptoEvent_buttons">
|
||||
<FormButton disabled={disabled} kind="danger" onClick={this._onRejectClicked} label={_t("Decline")} />
|
||||
<FormButton disabled={disabled} onClick={this._onAcceptClicked} label={_t("Accept")} />
|
||||
<FormButton kind="danger" onClick={this._onRejectClicked} label={_t("Decline")} />
|
||||
<FormButton onClick={this._onAcceptClicked} label={_t("Accept")} />
|
||||
</div>);
|
||||
}
|
||||
} else { // request sent by us
|
||||
|
@ -23,7 +23,7 @@ import {MatrixClientPeg} from "../../../MatrixClientPeg";
|
||||
import {ensureDMExists} from "../../../createRoom";
|
||||
import {useEventEmitter} from "../../../hooks/useEventEmitter";
|
||||
import Modal from "../../../Modal";
|
||||
import {PHASE_REQUESTED} from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
|
||||
import {PHASE_REQUESTED, PHASE_UNSENT} from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
|
||||
import * as sdk from "../../../index";
|
||||
import {_t} from "../../../languageHandler";
|
||||
|
||||
@ -69,9 +69,10 @@ const EncryptionPanel = ({verificationRequest, member, onClose, layout}) => {
|
||||
const roomId = await ensureDMExists(cli, member.userId);
|
||||
const verificationRequest = await cli.requestVerificationDM(member.userId, roomId);
|
||||
setRequest(verificationRequest);
|
||||
setPhase(verificationRequest.phase);
|
||||
}, [member.userId]);
|
||||
|
||||
const requested = request && (phase === PHASE_REQUESTED || phase === undefined);
|
||||
const requested = request && (phase === PHASE_REQUESTED || phase === PHASE_UNSENT || phase === undefined);
|
||||
if (!request || requested) {
|
||||
return <EncryptionInfo onStartVerification={onStartVerification} member={member} pending={requested} />;
|
||||
} else {
|
||||
|
@ -25,7 +25,7 @@ import dis from '../../../dispatcher';
|
||||
import Modal from '../../../Modal';
|
||||
import * as sdk from '../../../index';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import createRoom from '../../../createRoom';
|
||||
import createRoom, {findDMForUser} from '../../../createRoom';
|
||||
import DMRoomMap from '../../../utils/DMRoomMap';
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
import SdkConfig from '../../../SdkConfig';
|
||||
@ -169,10 +169,19 @@ async function verifyDevice(userId, device) {
|
||||
}
|
||||
|
||||
function verifyUser(user) {
|
||||
const cli = MatrixClientPeg.get();
|
||||
const dmRoom = findDMForUser(cli, user.userId);
|
||||
let existingRequest;
|
||||
if (dmRoom) {
|
||||
existingRequest = cli.findVerificationRequestDMInProgress(dmRoom.roomId);
|
||||
}
|
||||
dis.dispatch({
|
||||
action: "set_right_panel_phase",
|
||||
phase: RIGHT_PANEL_PHASES.EncryptionPanel,
|
||||
refireParams: {member: user},
|
||||
refireParams: {
|
||||
member: user,
|
||||
verificationRequest: existingRequest,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -19,6 +19,8 @@ import PropTypes from "prop-types";
|
||||
|
||||
import * as sdk from '../../../index';
|
||||
import {verificationMethods} from 'matrix-js-sdk/src/crypto';
|
||||
import {SCAN_QR_CODE_METHOD} from "matrix-js-sdk/src/crypto/verification/QRCode";
|
||||
|
||||
import VerificationQRCode from "../elements/crypto/VerificationQRCode";
|
||||
import {_t} from "../../../languageHandler";
|
||||
import E2EIcon from "../rooms/E2EIcon";
|
||||
@ -54,7 +56,9 @@ export default class VerificationPanel extends React.PureComponent {
|
||||
qrCodeProps: null, // generated by the VerificationQRCode component itself
|
||||
};
|
||||
this._hasVerifier = false;
|
||||
this._generateQRCodeProps(props.request);
|
||||
if (this.props.request.otherPartySupportsMethod(SCAN_QR_CODE_METHOD)) {
|
||||
this._generateQRCodeProps(props.request);
|
||||
}
|
||||
}
|
||||
|
||||
async _generateQRCodeProps(verificationRequest: VerificationRequest) {
|
||||
@ -67,59 +71,60 @@ export default class VerificationPanel extends React.PureComponent {
|
||||
}
|
||||
|
||||
renderQRPhase(pending) {
|
||||
const {member} = this.props;
|
||||
const {member, request} = this.props;
|
||||
const showSAS = request.methods.includes(verificationMethods.SAS);
|
||||
const showQR = this.props.request.otherPartySupportsMethod(SCAN_QR_CODE_METHOD);
|
||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||
|
||||
const noCommonMethodError = !showSAS && !showQR ?
|
||||
<p>{_t("The session you are trying to verify doesn't support scanning a QR code or emoji verification, which is what Riot supports. Try with a different client.")}</p> :
|
||||
null;
|
||||
|
||||
if (this.props.layout === 'dialog') {
|
||||
// HACK: This is a terrible idea.
|
||||
let qrCode = <div className='mx_VerificationPanel_QRPhase_noQR'><Spinner /></div>;
|
||||
if (this.state.qrCodeProps) {
|
||||
qrCode = <VerificationQRCode {...this.state.qrCodeProps} />;
|
||||
let qrBlock;
|
||||
let sasBlock;
|
||||
if (showQR) {
|
||||
let qrCode;
|
||||
if (this.state.qrCodeProps) {
|
||||
qrCode = <VerificationQRCode {...this.state.qrCodeProps} />;
|
||||
} else {
|
||||
qrCode = <div className='mx_VerificationPanel_QRPhase_noQR'><Spinner /></div>;
|
||||
}
|
||||
qrBlock =
|
||||
<div className='mx_VerificationPanel_QRPhase_startOption'>
|
||||
<p>{_t("Scan this unique code")}</p>
|
||||
{qrCode}
|
||||
</div>;
|
||||
}
|
||||
if (showSAS) {
|
||||
sasBlock =
|
||||
<div className='mx_VerificationPanel_QRPhase_startOption'>
|
||||
<p>{_t("Compare unique emoji")}</p>
|
||||
<span className='mx_VerificationPanel_QRPhase_helpText'>{_t("Compare a unique set of emoji if you don't have a camera on either device")}</span>
|
||||
<AccessibleButton disabled={this.state.emojiButtonClicked} onClick={this._startSAS} kind='primary'>
|
||||
{_t("Start")}
|
||||
</AccessibleButton>
|
||||
</div>;
|
||||
}
|
||||
const or = qrBlock && sasBlock ?
|
||||
<div className='mx_VerificationPanel_QRPhase_betweenText'>{_t("or")}</div> : null;
|
||||
return (
|
||||
<div>
|
||||
{_t("Verify this session by completing one of the following:")}
|
||||
<div className='mx_VerificationPanel_QRPhase_startOptions'>
|
||||
<div className='mx_VerificationPanel_QRPhase_startOption'>
|
||||
<p>{_t("Scan this unique code")}</p>
|
||||
{qrCode}
|
||||
</div>
|
||||
<div className='mx_VerificationPanel_QRPhase_betweenText'>{_t("or")}</div>
|
||||
<div className='mx_VerificationPanel_QRPhase_startOption'>
|
||||
<p>{_t("Compare unique emoji")}</p>
|
||||
<span className='mx_VerificationPanel_QRPhase_helpText'>{_t("Compare a unique set of emoji if you don't have a camera on either device")}</span>
|
||||
<AccessibleButton disabled={this.state.emojiButtonClicked} onClick={this._startSAS} kind='primary'>
|
||||
{_t("Start")}
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
{qrBlock}
|
||||
{or}
|
||||
{sasBlock}
|
||||
{noCommonMethodError}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
let button;
|
||||
if (pending) {
|
||||
button = <Spinner />;
|
||||
} else {
|
||||
const disabled = this.state.emojiButtonClicked;
|
||||
button = (
|
||||
<AccessibleButton disabled={disabled} kind="primary" className="mx_UserInfo_wideButton" onClick={this._startSAS}>
|
||||
{_t("Verify by emoji")}
|
||||
</AccessibleButton>
|
||||
);
|
||||
}
|
||||
|
||||
if (!this.state.qrCodeProps) {
|
||||
return <div className="mx_UserInfo_container">
|
||||
<h3>{_t("Verify by emoji")}</h3>
|
||||
<p>{_t("Verify by comparing unique emoji.")}</p>
|
||||
{ button }
|
||||
</div>;
|
||||
}
|
||||
|
||||
// TODO: add way to open camera to scan a QR code
|
||||
return <React.Fragment>
|
||||
<div className="mx_UserInfo_container">
|
||||
let qrBlock;
|
||||
if (this.state.qrCodeProps) {
|
||||
qrBlock = <div className="mx_UserInfo_container">
|
||||
<h3>{_t("Verify by scanning")}</h3>
|
||||
<p>{_t("Ask %(displayName)s to scan your code:", {
|
||||
displayName: member.displayName || member.name || member.userId,
|
||||
@ -128,14 +133,41 @@ export default class VerificationPanel extends React.PureComponent {
|
||||
<div className="mx_VerificationPanel_qrCode">
|
||||
<VerificationQRCode {...this.state.qrCodeProps} />
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
<div className="mx_UserInfo_container">
|
||||
let sasBlock;
|
||||
if (showSAS) {
|
||||
let button;
|
||||
if (pending) {
|
||||
button = <Spinner />;
|
||||
} else {
|
||||
const disabled = this.state.emojiButtonClicked;
|
||||
button = (
|
||||
<AccessibleButton disabled={disabled} kind="primary" className="mx_UserInfo_wideButton" onClick={this._startSAS}>
|
||||
{_t("Verify by emoji")}
|
||||
</AccessibleButton>
|
||||
);
|
||||
}
|
||||
const sasLabel = this.state.qrCodeProps ?
|
||||
_t("If you can't scan the code above, verify by comparing unique emoji.") :
|
||||
_t("Verify by comparing unique emoji.");
|
||||
sasBlock = <div className="mx_UserInfo_container">
|
||||
<h3>{_t("Verify by emoji")}</h3>
|
||||
<p>{_t("If you can't scan the code above, verify by comparing unique emoji.")}</p>
|
||||
|
||||
<p>{sasLabel}</p>
|
||||
{ button }
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
const noCommonMethodBlock = noCommonMethodError ?
|
||||
<div className="mx_UserInfo_container">{noCommonMethodError}</div> :
|
||||
null;
|
||||
|
||||
// TODO: add way to open camera to scan a QR code
|
||||
return <React.Fragment>
|
||||
{qrBlock}
|
||||
{sasBlock}
|
||||
{noCommonMethodBlock}
|
||||
</React.Fragment>;
|
||||
}
|
||||
|
||||
@ -258,7 +290,11 @@ export default class VerificationPanel extends React.PureComponent {
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.props.request.on("change", this._onRequestChange);
|
||||
const {request} = this.props;
|
||||
request.on("change", this._onRequestChange);
|
||||
if (request.verifier) {
|
||||
this.setState({sasEvent: request.verifier.sasEvent});
|
||||
}
|
||||
this._onRequestChange();
|
||||
}
|
||||
|
||||
|
@ -74,7 +74,6 @@ export default class AliasSettings extends React.Component {
|
||||
roomId: PropTypes.string.isRequired,
|
||||
canSetCanonicalAlias: PropTypes.bool.isRequired,
|
||||
canSetAliases: PropTypes.bool.isRequired,
|
||||
aliasEvents: PropTypes.array, // [MatrixEvent]
|
||||
canonicalAliasEvent: PropTypes.object, // MatrixEvent
|
||||
};
|
||||
|
||||
@ -94,12 +93,6 @@ export default class AliasSettings extends React.Component {
|
||||
updatingCanonicalAlias: false,
|
||||
};
|
||||
|
||||
const localDomain = MatrixClientPeg.get().getDomain();
|
||||
state.domainToAliases = this.aliasEventsToDictionary(props.aliasEvents || []);
|
||||
state.remoteDomains = Object.keys(state.domainToAliases).filter((domain) => {
|
||||
return domain !== localDomain && state.domainToAliases[domain].length > 0;
|
||||
});
|
||||
|
||||
if (props.canonicalAliasEvent) {
|
||||
state.canonicalAlias = props.canonicalAliasEvent.getContent().alias;
|
||||
}
|
||||
@ -107,6 +100,42 @@ export default class AliasSettings extends React.Component {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
async componentWillMount() {
|
||||
const cli = MatrixClientPeg.get();
|
||||
if (await cli.doesServerSupportUnstableFeature("org.matrix.msc2432")) {
|
||||
const response = await cli.unstableGetLocalAliases(this.props.roomId);
|
||||
const localAliases = response.aliases;
|
||||
const localDomain = cli.getDomain();
|
||||
const domainToAliases = Object.assign(
|
||||
{},
|
||||
// FIXME, any localhost alt_aliases will be ignored as they are overwritten by localAliases
|
||||
this.aliasesToDictionary(this._getAltAliases()),
|
||||
{[localDomain]: localAliases || []},
|
||||
);
|
||||
const remoteDomains = Object.keys(domainToAliases).filter((domain) => {
|
||||
return domain !== localDomain && domainToAliases[domain].length > 0;
|
||||
});
|
||||
this.setState({ domainToAliases, remoteDomains });
|
||||
} else {
|
||||
const state = {};
|
||||
const localDomain = cli.getDomain();
|
||||
state.domainToAliases = this.aliasEventsToDictionary(this.props.aliasEvents || []);
|
||||
state.remoteDomains = Object.keys(state.domainToAliases).filter((domain) => {
|
||||
return domain !== localDomain && state.domainToAliases[domain].length > 0;
|
||||
});
|
||||
this.setState(state);
|
||||
}
|
||||
}
|
||||
|
||||
aliasesToDictionary(aliases) {
|
||||
return aliases.reduce((dict, alias) => {
|
||||
const domain = alias.split(":")[1];
|
||||
dict[domain] = dict[domain] || [];
|
||||
dict[domain].push(alias);
|
||||
return dict;
|
||||
}, {});
|
||||
}
|
||||
|
||||
aliasEventsToDictionary(aliasEvents) { // m.room.alias events
|
||||
const dict = {};
|
||||
aliasEvents.forEach((event) => {
|
||||
@ -117,6 +146,16 @@ export default class AliasSettings extends React.Component {
|
||||
return dict;
|
||||
}
|
||||
|
||||
_getAltAliases() {
|
||||
if (this.props.canonicalAliasEvent) {
|
||||
const altAliases = this.props.canonicalAliasEvent.getContent().alt_aliases;
|
||||
if (Array.isArray(altAliases)) {
|
||||
return altAliases;
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
changeCanonicalAlias(alias) {
|
||||
if (!this.props.canSetCanonicalAlias) return;
|
||||
|
||||
@ -126,6 +165,8 @@ export default class AliasSettings extends React.Component {
|
||||
});
|
||||
|
||||
const eventContent = {};
|
||||
const altAliases = this._getAltAliases();
|
||||
if (altAliases) eventContent["alt_aliases"] = altAliases;
|
||||
if (alias) eventContent["alias"] = alias;
|
||||
|
||||
MatrixClientPeg.get().sendStateEvent(this.props.roomId, "m.room.canonical_alias",
|
||||
|
@ -36,11 +36,12 @@ export default class SecurityRoomSettingsTab extends React.Component {
|
||||
joinRule: "invite",
|
||||
guestAccess: "can_join",
|
||||
history: "shared",
|
||||
hasAliases: false,
|
||||
encrypted: false,
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount(): void {
|
||||
async componentWillMount(): void {
|
||||
MatrixClientPeg.get().on("RoomState.events", this._onStateEvent);
|
||||
|
||||
const room = MatrixClientPeg.get().getRoom(this.props.roomId);
|
||||
@ -63,6 +64,8 @@ export default class SecurityRoomSettingsTab extends React.Component {
|
||||
);
|
||||
const encrypted = MatrixClientPeg.get().isRoomEncrypted(this.props.roomId);
|
||||
this.setState({joinRule, guestAccess, history, encrypted});
|
||||
const hasAliases = await this._hasAliases();
|
||||
this.setState({hasAliases});
|
||||
}
|
||||
|
||||
_pullContentPropertyFromEvent(event, key, defaultValue) {
|
||||
@ -201,13 +204,25 @@ export default class SecurityRoomSettingsTab extends React.Component {
|
||||
MatrixClientPeg.get().getRoom(this.props.roomId).setBlacklistUnverifiedDevices(checked);
|
||||
};
|
||||
|
||||
async _hasAliases() {
|
||||
const cli = MatrixClientPeg.get();
|
||||
if (await cli.doesServerSupportUnstableFeature("org.matrix.msc2432")) {
|
||||
const response = await cli.unstableGetLocalAliases(this.props.roomId);
|
||||
const localAliases = response.aliases;
|
||||
return Array.isArray(localAliases) && localAliases.length !== 0;
|
||||
} else {
|
||||
const room = cli.getRoom(this.props.roomId);
|
||||
const aliasEvents = room.currentState.getStateEvents("m.room.aliases") || [];
|
||||
const hasAliases = !!aliasEvents.find((ev) => (ev.getContent().aliases || []).length > 0);
|
||||
return hasAliases;
|
||||
}
|
||||
}
|
||||
|
||||
_renderRoomAccess() {
|
||||
const client = MatrixClientPeg.get();
|
||||
const room = client.getRoom(this.props.roomId);
|
||||
const joinRule = this.state.joinRule;
|
||||
const guestAccess = this.state.guestAccess;
|
||||
const aliasEvents = room.currentState.getStateEvents("m.room.aliases") || [];
|
||||
const hasAliases = !!aliasEvents.find((ev) => (ev.getContent().aliases || []).length > 0);
|
||||
|
||||
const canChangeAccess = room.currentState.mayClientSendStateEvent("m.room.join_rules", client)
|
||||
&& room.currentState.mayClientSendStateEvent("m.room.guest_access", client);
|
||||
@ -226,7 +241,7 @@ export default class SecurityRoomSettingsTab extends React.Component {
|
||||
}
|
||||
|
||||
let aliasWarning = null;
|
||||
if (joinRule === 'public' && !hasAliases) {
|
||||
if (joinRule === 'public' && !this.state.hasAliases) {
|
||||
aliasWarning = (
|
||||
<div className='mx_SecurityRoomSettingsTab_warning'>
|
||||
<img src={require("../../../../../../res/img/warning.svg")} width={15} height={15} />
|
||||
|
@ -58,7 +58,7 @@ export default class VerificationRequestToast extends React.PureComponent {
|
||||
|
||||
_checkRequestIsPending = () => {
|
||||
const {request} = this.props;
|
||||
if (request.ready || request.done || request.cancelled || request.observeOnly) {
|
||||
if (!request.canAccept) {
|
||||
ToastStore.sharedInstance().dismissToast(this.props.toastKey);
|
||||
}
|
||||
};
|
||||
|
@ -23,6 +23,7 @@ import dis from "./dispatcher";
|
||||
import * as Rooms from "./Rooms";
|
||||
import DMRoomMap from "./utils/DMRoomMap";
|
||||
import {getAddressType} from "./UserAddress";
|
||||
import SettingsStore from "./settings/SettingsStore";
|
||||
|
||||
/**
|
||||
* Create a new room, and switch to it.
|
||||
@ -159,7 +160,7 @@ export default function createRoom(opts) {
|
||||
});
|
||||
}
|
||||
|
||||
export async function ensureDMExists(client, userId) {
|
||||
export function findDMForUser(client, userId) {
|
||||
const roomIds = DMRoomMap.shared().getDMRoomsForUserId(userId);
|
||||
const rooms = roomIds.map(id => client.getRoom(id));
|
||||
const suitableDMRooms = rooms.filter(r => {
|
||||
@ -169,12 +170,60 @@ export async function ensureDMExists(client, userId) {
|
||||
}
|
||||
return false;
|
||||
});
|
||||
let roomId;
|
||||
if (suitableDMRooms.length) {
|
||||
const room = suitableDMRooms[0];
|
||||
roomId = room.roomId;
|
||||
return suitableDMRooms[0];
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Try to ensure the user is already in the megolm session before continuing
|
||||
* NOTE: this assumes you've just created the room and there's not been an opportunity
|
||||
* for other code to run, so we shouldn't miss RoomState.newMember when it comes by.
|
||||
*/
|
||||
export async function _waitForMember(client, roomId, userId, opts = { timeout: 1500 }) {
|
||||
const { timeout } = opts;
|
||||
let handler;
|
||||
return new Promise((resolve) => {
|
||||
handler = function(_event, _roomstate, member) {
|
||||
if (member.userId !== userId) return;
|
||||
if (member.roomId !== roomId) return;
|
||||
resolve(true);
|
||||
};
|
||||
client.on("RoomState.newMember", handler);
|
||||
|
||||
/* We don't want to hang if this goes wrong, so we proceed and hope the other
|
||||
user is already in the megolm session */
|
||||
setTimeout(resolve, timeout, false);
|
||||
}).finally(() => {
|
||||
client.removeListener("RoomState.newMember", handler);
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Ensure that for every user in a room, there is at least one device that we
|
||||
* can encrypt to.
|
||||
*/
|
||||
export async function canEncryptToAllUsers(client, userIds) {
|
||||
const usersDeviceMap = await client.downloadKeys(userIds);
|
||||
// { "@user:host": { "DEVICE": {...}, ... }, ... }
|
||||
return Object.values(usersDeviceMap).every((userDevices) =>
|
||||
// { "DEVICE": {...}, ... }
|
||||
Object.keys(userDevices).length > 0,
|
||||
);
|
||||
}
|
||||
|
||||
export async function ensureDMExists(client, userId) {
|
||||
const existingDMRoom = findDMForUser(client, userId);
|
||||
let roomId;
|
||||
if (existingDMRoom) {
|
||||
roomId = existingDMRoom.roomId;
|
||||
} else {
|
||||
roomId = await createRoom({dmUserId: userId, spinner: false, andView: false});
|
||||
let encryption;
|
||||
if (SettingsStore.isFeatureEnabled("feature_cross_signing")) {
|
||||
encryption = canEncryptToAllUsers(client, [userId]);
|
||||
}
|
||||
roomId = await createRoom({encryption, dmUserId: userId, spinner: false, andView: false});
|
||||
await _waitForMember(client, roomId, userId);
|
||||
}
|
||||
return roomId;
|
||||
}
|
||||
|
@ -5,21 +5,22 @@
|
||||
"Failed to verify email address: make sure you clicked the link in the email": "Failed to verify email address: make sure you clicked the link in the email",
|
||||
"Add Phone Number": "Add Phone Number",
|
||||
"The platform you're on": "The platform you're on",
|
||||
"The version of Riot.im": "The version of Riot.im",
|
||||
"The version of Riot": "The version of Riot",
|
||||
"Whether or not you're logged in (we don't record your username)": "Whether or not you're logged in (we don't record your username)",
|
||||
"Your language of choice": "Your language of choice",
|
||||
"Which officially provided instance you are using, if any": "Which officially provided instance you are using, if any",
|
||||
"Whether or not you're using the Richtext mode of the Rich Text Editor": "Whether or not you're using the Richtext mode of the Rich Text Editor",
|
||||
"Whether or not you're using the 'breadcrumbs' feature (avatars above the room list)": "Whether or not you're using the 'breadcrumbs' feature (avatars above the room list)",
|
||||
"Your homeserver's URL": "Your homeserver's URL",
|
||||
"Your identity server's URL": "Your identity server's URL",
|
||||
"Whether you're using Riot on a device where touch is the primary input mechanism": "Whether you're using Riot on a device where touch is the primary input mechanism",
|
||||
"Whether or not you're using the 'breadcrumbs' feature (avatars above the room list)": "Whether or not you're using the 'breadcrumbs' feature (avatars above the room list)",
|
||||
"Whether you're using Riot as an installed Progressive Web App": "Whether you're using Riot as an installed Progressive Web App",
|
||||
"e.g. %(exampleValue)s": "e.g. %(exampleValue)s",
|
||||
"Every page you use in the app": "Every page you use in the app",
|
||||
"e.g. <CurrentPageURL>": "e.g. <CurrentPageURL>",
|
||||
"Your User Agent": "Your User Agent",
|
||||
"Your user agent": "Your user agent",
|
||||
"Your device resolution": "Your device resolution",
|
||||
"Analytics": "Analytics",
|
||||
"The information being sent to us to help make Riot.im better includes:": "The information being sent to us to help make Riot.im better includes:",
|
||||
"The information being sent to us to help make Riot better includes:": "The information being sent to us to help make Riot better includes:",
|
||||
"Where this page includes identifiable information, such as a room, user or group ID, that data is removed before being sent to the server.": "Where this page includes identifiable information, such as a room, user or group ID, that data is removed before being sent to the server.",
|
||||
"Error": "Error",
|
||||
"Unable to load! Check your network connectivity and try again.": "Unable to load! Check your network connectivity and try again.",
|
||||
@ -1198,11 +1199,12 @@
|
||||
"This client does not support end-to-end encryption.": "This client does not support end-to-end encryption.",
|
||||
"Messages in this room are not end-to-end encrypted.": "Messages in this room are not end-to-end encrypted.",
|
||||
"Security": "Security",
|
||||
"Verify by emoji": "Verify by emoji",
|
||||
"Verify by comparing unique emoji.": "Verify by comparing unique emoji.",
|
||||
"The session you are trying to verify doesn't support scanning a QR code or emoji verification, which is what Riot supports. Try with a different client.": "The session you are trying to verify doesn't support scanning a QR code or emoji verification, which is what Riot supports. Try with a different client.",
|
||||
"Verify by scanning": "Verify by scanning",
|
||||
"Ask %(displayName)s to scan your code:": "Ask %(displayName)s to scan your code:",
|
||||
"Verify by emoji": "Verify by emoji",
|
||||
"If you can't scan the code above, verify by comparing unique emoji.": "If you can't scan the code above, verify by comparing unique emoji.",
|
||||
"Verify by comparing unique emoji.": "Verify by comparing unique emoji.",
|
||||
"You've successfully verified %(displayName)s!": "You've successfully verified %(displayName)s!",
|
||||
"Got it": "Got it",
|
||||
"Verification timed out. Start verification again from their profile.": "Verification timed out. Start verification again from their profile.",
|
||||
@ -1240,8 +1242,12 @@
|
||||
"%(name)s cancelled verifying": "%(name)s cancelled verifying",
|
||||
"You accepted": "You accepted",
|
||||
"%(name)s accepted": "%(name)s accepted",
|
||||
"You declined": "You declined",
|
||||
"You cancelled": "You cancelled",
|
||||
"%(name)s declined": "%(name)s declined",
|
||||
"%(name)s cancelled": "%(name)s cancelled",
|
||||
"accepting …": "accepting …",
|
||||
"declining …": "declining …",
|
||||
"%(name)s wants to verify": "%(name)s wants to verify",
|
||||
"You sent a verification request": "You sent a verification request",
|
||||
"Error decrypting video": "Error decrypting video",
|
||||
|
@ -67,6 +67,18 @@ export default async function sendBugReport(bugReportEndpoint, opts) {
|
||||
userAgent = window.navigator.userAgent;
|
||||
}
|
||||
|
||||
let installedPWA = "UNKNOWN";
|
||||
try {
|
||||
// Known to work at least for desktop Chrome
|
||||
installedPWA = window.matchMedia('(display-mode: standalone)').matches;
|
||||
} catch (e) { }
|
||||
|
||||
let touchInput = "UNKNOWN";
|
||||
try {
|
||||
// MDN claims broad support across browsers
|
||||
touchInput = window.matchMedia('(pointer: coarse)').matches;
|
||||
} catch (e) { }
|
||||
|
||||
const client = MatrixClientPeg.get();
|
||||
|
||||
console.log("Sending bug report.");
|
||||
@ -76,6 +88,8 @@ export default async function sendBugReport(bugReportEndpoint, opts) {
|
||||
body.append('app', 'riot-web');
|
||||
body.append('version', version);
|
||||
body.append('user_agent', userAgent);
|
||||
body.append('installed_pwa', installedPWA);
|
||||
body.append('touch_input', touchInput);
|
||||
|
||||
if (client) {
|
||||
body.append('user_id', client.credentials.userId);
|
||||
|
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 mockclock = require('../../mock-clock');
|
||||
|
||||
import Adapter from "enzyme-adapter-react-16";
|
||||
import { configure, mount } from "enzyme";
|
||||
|
||||
import Velocity from 'velocity-animate';
|
||||
import MatrixClientContext from "../../../src/contexts/MatrixClientContext";
|
||||
import RoomContext from "../../../src/contexts/RoomContext";
|
||||
|
||||
configure({ adapter: new Adapter() });
|
||||
|
||||
let client;
|
||||
const room = new Matrix.Room();
|
||||
|
||||
@ -251,4 +256,111 @@ describe('MessagePanel', function() {
|
||||
}, 100);
|
||||
}, 100);
|
||||
});
|
||||
|
||||
it('should collapse creation events', function() {
|
||||
const mkEvent = test_utils.mkEvent;
|
||||
const mkMembership = test_utils.mkMembership;
|
||||
const roomId = "!someroom";
|
||||
const alice = "@alice:example.org";
|
||||
const ts0 = Date.now();
|
||||
const events = [
|
||||
mkEvent({
|
||||
event: true,
|
||||
type: "m.room.create",
|
||||
room: roomId,
|
||||
user: alice,
|
||||
content: {
|
||||
creator: alice,
|
||||
room_version: "5",
|
||||
predecessor: {
|
||||
room_id: "!prevroom",
|
||||
event_id: "$someevent",
|
||||
},
|
||||
},
|
||||
ts: ts0,
|
||||
}),
|
||||
mkMembership({
|
||||
event: true,
|
||||
room: roomId,
|
||||
user: alice,
|
||||
target: {
|
||||
userId: alice,
|
||||
name: "Alice",
|
||||
getAvatarUrl: () => {
|
||||
return "avatar.jpeg";
|
||||
},
|
||||
},
|
||||
ts: ts0 + 1,
|
||||
mship: 'join',
|
||||
name: 'Alice',
|
||||
}),
|
||||
mkEvent({
|
||||
event: true,
|
||||
type: "m.room.join_rules",
|
||||
room: roomId,
|
||||
user: alice,
|
||||
content: {
|
||||
"join_rule": "invite"
|
||||
},
|
||||
ts: ts0 + 2,
|
||||
}),
|
||||
mkEvent({
|
||||
event: true,
|
||||
type: "m.room.history_visibility",
|
||||
room: roomId,
|
||||
user: alice,
|
||||
content: {
|
||||
"history_visibility": "invited",
|
||||
},
|
||||
ts: ts0 + 3,
|
||||
}),
|
||||
mkEvent({
|
||||
event: true,
|
||||
type: "m.room.encryption",
|
||||
room: roomId,
|
||||
user: alice,
|
||||
content: {
|
||||
"algorithm": "m.megolm.v1.aes-sha2",
|
||||
},
|
||||
ts: ts0 + 4,
|
||||
}),
|
||||
mkMembership({
|
||||
event: true,
|
||||
room: roomId,
|
||||
user: alice,
|
||||
skey: "@bob:example.org",
|
||||
target: {
|
||||
userId: "@bob:example.org",
|
||||
name: "Bob",
|
||||
getAvatarUrl: () => {
|
||||
return "avatar.jpeg";
|
||||
},
|
||||
},
|
||||
ts: ts0 + 5,
|
||||
mship: 'invite',
|
||||
name: 'Bob',
|
||||
}),
|
||||
];
|
||||
const res = mount(
|
||||
<WrappedMessagePanel className="cls" events={events} />,
|
||||
);
|
||||
|
||||
// we expect that
|
||||
// - the room creation event, the room encryption event, and Alice inviting Bob,
|
||||
// should be outside of the room creation summary
|
||||
// - all other events should be inside the room creation summary
|
||||
|
||||
const tiles = res.find(sdk.getComponent('views.rooms.EventTile'));
|
||||
|
||||
expect(tiles.at(0).props().mxEvent.getType()).toEqual("m.room.create");
|
||||
expect(tiles.at(1).props().mxEvent.getType()).toEqual("m.room.encryption");
|
||||
|
||||
const summaryTiles = res.find(sdk.getComponent('views.elements.EventListSummary'));
|
||||
const summaryTile = summaryTiles.at(0);
|
||||
|
||||
const summaryEventTiles = summaryTile.find(sdk.getComponent('views.rooms.EventTile'));
|
||||
// every event except for the room creation, room encryption, and Bob's
|
||||
// invite event should be in the event summary
|
||||
expect(summaryEventTiles.length).toEqual(tiles.length - 3);
|
||||
});
|
||||
});
|
||||
|
72
test/createRoom-test.js
Normal file
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"),
|
||||
|
||||
getPushActionsForEvent: jest.fn(),
|
||||
getRoom: jest.fn().mockReturnValue(mkStubRoom()),
|
||||
getRoom: jest.fn().mockImplementation(mkStubRoom),
|
||||
getRooms: jest.fn().mockReturnValue([]),
|
||||
getVisibleRooms: jest.fn().mockReturnValue([]),
|
||||
getGroups: jest.fn().mockReturnValue([]),
|
||||
@ -111,7 +111,7 @@ export function mkEvent(opts) {
|
||||
if (opts.skey) {
|
||||
event.state_key = opts.skey;
|
||||
} else if (["m.room.name", "m.room.topic", "m.room.create", "m.room.join_rules",
|
||||
"m.room.power_levels", "m.room.topic",
|
||||
"m.room.power_levels", "m.room.topic", "m.room.history_visibility", "m.room.encryption",
|
||||
"com.example.state"].indexOf(opts.type) !== -1) {
|
||||
event.state_key = "";
|
||||
}
|
||||
|
@ -5760,9 +5760,10 @@ mathml-tag-names@^2.0.1:
|
||||
resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.1.tgz#6dff66c99d55ecf739ca53c492e626f1d12a33cc"
|
||||
integrity sha512-pWB896KPGSGkp1XtyzRBftpTzwSOL0Gfk0wLvxt4f2mgzjY19o0LxJ3U25vNWTzsh7da+KTbuXQoQ3lOJZ8WHw==
|
||||
|
||||
"matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop":
|
||||
version "4.0.0"
|
||||
resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/21e4c597d9633aef606871cf9ffffaf039142be3"
|
||||
matrix-js-sdk@5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-5.0.0.tgz#dcbab35f1afdb35ef0364eb232e78e0fb7dc2a5b"
|
||||
integrity sha512-A/aeE2Zn2OHq1n/9wIHCszrQZ7oXfThUHWi5Kz7illVCPUJ3JrZ31XVvx02k6vBasDcUtjAfZblHdTVN62cWLw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.8.3"
|
||||
another-json "^0.2.0"
|
||||
|
Loading…
Reference in New Issue
Block a user