@@ -9,7 +9,16 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
9
9
Please see LICENSE files in the repository root for full details.
10
10
*/
11
11
12
- import React , { ChangeEvent , ComponentProps , createRef , ReactElement , ReactNode , RefObject , useContext } from "react" ;
12
+ import React , {
13
+ ChangeEvent ,
14
+ ComponentProps ,
15
+ createRef ,
16
+ ReactElement ,
17
+ ReactNode ,
18
+ RefObject ,
19
+ useContext ,
20
+ JSX ,
21
+ } from "react" ;
13
22
import classNames from "classnames" ;
14
23
import {
15
24
IRecommendedVersion ,
@@ -29,6 +38,7 @@ import {
29
38
MatrixError ,
30
39
ISearchResults ,
31
40
THREAD_RELATION_TYPE ,
41
+ MatrixClient ,
32
42
} from "matrix-js-sdk/src/matrix" ;
33
43
import { KnownMembership } from "matrix-js-sdk/src/types" ;
34
44
import { logger } from "matrix-js-sdk/src/logger" ;
@@ -233,6 +243,11 @@ export interface IRoomState {
233
243
liveTimeline ?: EventTimeline ;
234
244
narrow : boolean ;
235
245
msc3946ProcessDynamicPredecessor : boolean ;
246
+ /**
247
+ * Whether the room is encrypted or not.
248
+ * If null, we are still determining the encryption status.
249
+ */
250
+ isRoomEncrypted : boolean | null ;
236
251
237
252
canAskToJoin : boolean ;
238
253
promptAskToJoin : boolean ;
@@ -417,6 +432,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
417
432
canAskToJoin : this . askToJoinEnabled ,
418
433
promptAskToJoin : false ,
419
434
viewRoomOpts : { buttons : [ ] } ,
435
+ isRoomEncrypted : null ,
420
436
} ;
421
437
}
422
438
@@ -847,7 +863,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
847
863
return isManuallyShown && widgets . length > 0 ;
848
864
}
849
865
850
- public componentDidMount ( ) : void {
866
+ public async componentDidMount ( ) : Promise < void > {
851
867
this . unmounted = false ;
852
868
853
869
this . dispatcherRef = defaultDispatcher . register ( this . onAction ) ;
@@ -1342,13 +1358,12 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
1342
1358
this . context . widgetLayoutStore . on ( WidgetLayoutStore . emissionForRoom ( room ) , this . onWidgetLayoutChange ) ;
1343
1359
1344
1360
this . calculatePeekRules ( room ) ;
1345
- this . updatePreviewUrlVisibility ( room ) ;
1346
1361
this . loadMembersIfJoined ( room ) ;
1347
1362
this . calculateRecommendedVersion ( room ) ;
1348
- this . updateE2EStatus ( room ) ;
1349
1363
this . updatePermissions ( room ) ;
1350
1364
this . checkWidgets ( room ) ;
1351
1365
this . loadVirtualRoom ( room ) ;
1366
+ this . updateRoomEncrypted ( room ) ;
1352
1367
1353
1368
if (
1354
1369
this . getMainSplitContentType ( room ) !== MainSplitContentType . Timeline &&
@@ -1377,6 +1392,13 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
1377
1392
return room ?. currentState . getStateEvents ( EventType . RoomTombstone , "" ) ?? undefined ;
1378
1393
}
1379
1394
1395
+ private async getIsRoomEncrypted ( roomId = this . state . roomId ) : Promise < boolean > {
1396
+ const crypto = this . context . client ?. getCrypto ( ) ;
1397
+ if ( ! crypto || ! roomId ) return false ;
1398
+
1399
+ return await crypto . isEncryptionEnabledInRoom ( roomId ) ;
1400
+ }
1401
+
1380
1402
private async calculateRecommendedVersion ( room : Room ) : Promise < void > {
1381
1403
const upgradeRecommendation = await room . getRecommendedVersion ( ) ;
1382
1404
if ( this . unmounted ) return ;
@@ -1409,12 +1431,15 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
1409
1431
} ) ;
1410
1432
}
1411
1433
1412
- private updatePreviewUrlVisibility ( { roomId } : Room ) : void {
1413
- // URL Previews in E2EE rooms can be a privacy leak so use a different setting which is per-room explicit
1414
- const key = this . context . client ?. isRoomEncrypted ( roomId ) ? "urlPreviewsEnabled_e2ee" : "urlPreviewsEnabled" ;
1415
- this . setState ( {
1416
- showUrlPreview : SettingsStore . getValue ( key , roomId ) ,
1417
- } ) ;
1434
+ private updatePreviewUrlVisibility ( room : Room ) : void {
1435
+ this . setState ( ( { isRoomEncrypted } ) => ( {
1436
+ showUrlPreview : this . getPreviewUrlVisibility ( room , isRoomEncrypted ) ,
1437
+ } ) ) ;
1438
+ }
1439
+
1440
+ private getPreviewUrlVisibility ( { roomId } : Room , isRoomEncrypted : boolean | null ) : boolean {
1441
+ const key = isRoomEncrypted ? "urlPreviewsEnabled_e2ee" : "urlPreviewsEnabled" ;
1442
+ return SettingsStore . getValue ( key , roomId ) ;
1418
1443
}
1419
1444
1420
1445
private onRoom = ( room : Room ) : void => {
@@ -1456,7 +1481,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
1456
1481
} ;
1457
1482
1458
1483
private async updateE2EStatus ( room : Room ) : Promise < void > {
1459
- if ( ! this . context . client ?. isRoomEncrypted ( room . roomId ) ) return ;
1484
+ if ( ! this . context . client || ! this . state . isRoomEncrypted ) return ;
1460
1485
1461
1486
// If crypto is not currently enabled, we aren't tracking devices at all,
1462
1487
// so we don't know what the answer is. Let's error on the safe side and show
@@ -1467,33 +1492,54 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
1467
1492
1468
1493
if ( this . context . client . getCrypto ( ) ) {
1469
1494
/* At this point, the user has encryption on and cross-signing on */
1470
- e2eStatus = await shieldStatusForRoom ( this . context . client , room ) ;
1471
- RoomView . e2eStatusCache . set ( room . roomId , e2eStatus ) ;
1495
+ e2eStatus = await this . cacheAndGetE2EStatus ( room , this . context . client ) ;
1472
1496
if ( this . unmounted ) return ;
1473
1497
this . setState ( { e2eStatus } ) ;
1474
1498
}
1475
1499
}
1476
1500
1501
+ private async cacheAndGetE2EStatus ( room : Room , client : MatrixClient ) : Promise < E2EStatus > {
1502
+ const e2eStatus = await shieldStatusForRoom ( client , room ) ;
1503
+ RoomView . e2eStatusCache . set ( room . roomId , e2eStatus ) ;
1504
+ return e2eStatus ;
1505
+ }
1506
+
1477
1507
private onUrlPreviewsEnabledChange = ( ) : void => {
1478
1508
if ( this . state . room ) {
1479
1509
this . updatePreviewUrlVisibility ( this . state . room ) ;
1480
1510
}
1481
1511
} ;
1482
1512
1483
- private onRoomStateEvents = ( ev : MatrixEvent , state : RoomState ) : void => {
1513
+ private onRoomStateEvents = async ( ev : MatrixEvent , state : RoomState ) : Promise < void > => {
1484
1514
// ignore if we don't have a room yet
1485
- if ( ! this . state . room || this . state . room . roomId !== state . roomId ) return ;
1515
+ if ( ! this . state . room || this . state . room . roomId !== state . roomId || ! this . context . client ) return ;
1486
1516
1487
1517
switch ( ev . getType ( ) ) {
1488
1518
case EventType . RoomTombstone :
1489
1519
this . setState ( { tombstone : this . getRoomTombstone ( ) } ) ;
1490
1520
break ;
1491
-
1521
+ case EventType . RoomEncryption : {
1522
+ await this . updateRoomEncrypted ( ) ;
1523
+ break ;
1524
+ }
1492
1525
default :
1493
1526
this . updatePermissions ( this . state . room ) ;
1494
1527
}
1495
1528
} ;
1496
1529
1530
+ private async updateRoomEncrypted ( room = this . state . room ) : Promise < void > {
1531
+ if ( ! room || ! this . context . client ) return ;
1532
+
1533
+ const isRoomEncrypted = await this . getIsRoomEncrypted ( room . roomId ) ;
1534
+ const newE2EStatus = isRoomEncrypted ? await this . cacheAndGetE2EStatus ( room , this . context . client ) : null ;
1535
+
1536
+ this . setState ( {
1537
+ isRoomEncrypted,
1538
+ showUrlPreview : this . getPreviewUrlVisibility ( room , isRoomEncrypted ) ,
1539
+ ...( newE2EStatus && { e2eStatus : newE2EStatus } ) ,
1540
+ } ) ;
1541
+ }
1542
+
1497
1543
private onRoomStateUpdate = ( state : RoomState ) : void => {
1498
1544
// ignore members in other rooms
1499
1545
if ( state . roomId !== this . state . room ?. roomId ) {
@@ -2027,6 +2073,8 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
2027
2073
2028
2074
public render ( ) : ReactNode {
2029
2075
if ( ! this . context . client ) return null ;
2076
+ const { isRoomEncrypted } = this . state ;
2077
+ const isRoomEncryptionLoading = isRoomEncrypted === null ;
2030
2078
2031
2079
if ( this . state . room instanceof LocalRoom ) {
2032
2080
if ( this . state . room . state === LocalRoomState . CREATING ) {
@@ -2242,14 +2290,16 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
2242
2290
let aux : JSX . Element | undefined ;
2243
2291
let previewBar ;
2244
2292
if ( this . state . timelineRenderingType === TimelineRenderingType . Search ) {
2245
- aux = (
2246
- < RoomSearchAuxPanel
2247
- searchInfo = { this . state . search }
2248
- onCancelClick = { this . onCancelSearchClick }
2249
- onSearchScopeChange = { this . onSearchScopeChange }
2250
- isRoomEncrypted = { this . context . client . isRoomEncrypted ( this . state . room . roomId ) }
2251
- />
2252
- ) ;
2293
+ if ( ! isRoomEncryptionLoading ) {
2294
+ aux = (
2295
+ < RoomSearchAuxPanel
2296
+ searchInfo = { this . state . search }
2297
+ onCancelClick = { this . onCancelSearchClick }
2298
+ onSearchScopeChange = { this . onSearchScopeChange }
2299
+ isRoomEncrypted = { isRoomEncrypted }
2300
+ />
2301
+ ) ;
2302
+ }
2253
2303
} else if ( showRoomUpgradeBar ) {
2254
2304
aux = < RoomUpgradeWarningBar room = { this . state . room } /> ;
2255
2305
} else if ( myMembership !== KnownMembership . Join ) {
@@ -2325,8 +2375,10 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
2325
2375
2326
2376
let messageComposer ;
2327
2377
const showComposer =
2378
+ ! isRoomEncryptionLoading &&
2328
2379
// joined and not showing search results
2329
- myMembership === KnownMembership . Join && ! this . state . search ;
2380
+ myMembership === KnownMembership . Join &&
2381
+ ! this . state . search ;
2330
2382
if ( showComposer ) {
2331
2383
messageComposer = (
2332
2384
< MessageComposer
@@ -2367,34 +2419,37 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
2367
2419
highlightedEventId = this . state . initialEventId ;
2368
2420
}
2369
2421
2370
- const messagePanel = (
2371
- < TimelinePanel
2372
- ref = { this . gatherTimelinePanelRef }
2373
- timelineSet = { this . state . room . getUnfilteredTimelineSet ( ) }
2374
- overlayTimelineSet = { this . state . virtualRoom ?. getUnfilteredTimelineSet ( ) }
2375
- overlayTimelineSetFilter = { isCallEvent }
2376
- showReadReceipts = { this . state . showReadReceipts }
2377
- manageReadReceipts = { ! this . state . isPeeking }
2378
- sendReadReceiptOnLoad = { ! this . state . wasContextSwitch }
2379
- manageReadMarkers = { ! this . state . isPeeking }
2380
- hidden = { hideMessagePanel }
2381
- highlightedEventId = { highlightedEventId }
2382
- eventId = { this . state . initialEventId }
2383
- eventScrollIntoView = { this . state . initialEventScrollIntoView }
2384
- eventPixelOffset = { this . state . initialEventPixelOffset }
2385
- onScroll = { this . onMessageListScroll }
2386
- onEventScrolledIntoView = { this . resetJumpToEvent }
2387
- onReadMarkerUpdated = { this . updateTopUnreadMessagesBar }
2388
- showUrlPreview = { this . state . showUrlPreview }
2389
- className = { this . messagePanelClassNames }
2390
- membersLoaded = { this . state . membersLoaded }
2391
- permalinkCreator = { this . permalinkCreator }
2392
- resizeNotifier = { this . props . resizeNotifier }
2393
- showReactions = { true }
2394
- layout = { this . state . layout }
2395
- editState = { this . state . editState }
2396
- />
2397
- ) ;
2422
+ let messagePanel : JSX . Element | undefined ;
2423
+ if ( ! isRoomEncryptionLoading ) {
2424
+ messagePanel = (
2425
+ < TimelinePanel
2426
+ ref = { this . gatherTimelinePanelRef }
2427
+ timelineSet = { this . state . room . getUnfilteredTimelineSet ( ) }
2428
+ overlayTimelineSet = { this . state . virtualRoom ?. getUnfilteredTimelineSet ( ) }
2429
+ overlayTimelineSetFilter = { isCallEvent }
2430
+ showReadReceipts = { this . state . showReadReceipts }
2431
+ manageReadReceipts = { ! this . state . isPeeking }
2432
+ sendReadReceiptOnLoad = { ! this . state . wasContextSwitch }
2433
+ manageReadMarkers = { ! this . state . isPeeking }
2434
+ hidden = { hideMessagePanel }
2435
+ highlightedEventId = { highlightedEventId }
2436
+ eventId = { this . state . initialEventId }
2437
+ eventScrollIntoView = { this . state . initialEventScrollIntoView }
2438
+ eventPixelOffset = { this . state . initialEventPixelOffset }
2439
+ onScroll = { this . onMessageListScroll }
2440
+ onEventScrolledIntoView = { this . resetJumpToEvent }
2441
+ onReadMarkerUpdated = { this . updateTopUnreadMessagesBar }
2442
+ showUrlPreview = { this . state . showUrlPreview }
2443
+ className = { this . messagePanelClassNames }
2444
+ membersLoaded = { this . state . membersLoaded }
2445
+ permalinkCreator = { this . permalinkCreator }
2446
+ resizeNotifier = { this . props . resizeNotifier }
2447
+ showReactions = { true }
2448
+ layout = { this . state . layout }
2449
+ editState = { this . state . editState }
2450
+ />
2451
+ ) ;
2452
+ }
2398
2453
2399
2454
let topUnreadMessagesBar : JSX . Element | undefined ;
2400
2455
// Do not show TopUnreadMessagesBar if we have search results showing, it makes no sense
@@ -2415,7 +2470,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
2415
2470
) ;
2416
2471
}
2417
2472
2418
- const showRightPanel = this . state . room && this . state . showRightPanel ;
2473
+ const showRightPanel = ! isRoomEncryptionLoading && this . state . room && this . state . showRightPanel ;
2419
2474
2420
2475
const rightPanel = showRightPanel ? (
2421
2476
< RightPanel
0 commit comments