Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Commit

Permalink
Display push toggle for web sessions (MSC3890) (#9327)
Browse files Browse the repository at this point in the history
  • Loading branch information
Germain authored Sep 28, 2022
1 parent e15ef9f commit c3bfb6e
Show file tree
Hide file tree
Showing 8 changed files with 166 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

import { LocalNotificationSettings } from 'matrix-js-sdk/src/@types/local_notifications';
import React, { useState } from 'react';

import { _t } from '../../../../languageHandler';
Expand All @@ -29,6 +30,8 @@ interface Props {
device?: DeviceWithVerification;
isLoading: boolean;
isSigningOut: boolean;
localNotificationSettings?: LocalNotificationSettings | undefined;
setPushNotifications?: (deviceId: string, enabled: boolean) => Promise<void> | undefined;
onVerifyCurrentDevice: () => void;
onSignOutCurrentDevice: () => void;
saveDeviceName: (deviceName: string) => Promise<void>;
Expand All @@ -38,6 +41,8 @@ const CurrentDeviceSection: React.FC<Props> = ({
device,
isLoading,
isSigningOut,
localNotificationSettings,
setPushNotifications,
onVerifyCurrentDevice,
onSignOutCurrentDevice,
saveDeviceName,
Expand All @@ -63,6 +68,8 @@ const CurrentDeviceSection: React.FC<Props> = ({
{ isExpanded &&
<DeviceDetails
device={device}
localNotificationSettings={localNotificationSettings}
setPushNotifications={setPushNotifications}
isSigningOut={isSigningOut}
onVerifyDevice={onVerifyCurrentDevice}
onSignOutDevice={onSignOutCurrentDevice}
Expand Down
30 changes: 24 additions & 6 deletions src/components/views/settings/devices/DeviceDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
import React from 'react';
import { IPusher } from 'matrix-js-sdk/src/@types/PushRules';
import { PUSHER_ENABLED } from 'matrix-js-sdk/src/@types/event';
import { LocalNotificationSettings } from 'matrix-js-sdk/src/@types/local_notifications';

import { formatDate } from '../../../../DateUtils';
import { _t } from '../../../../languageHandler';
Expand All @@ -30,11 +31,12 @@ import { DeviceWithVerification } from './types';
interface Props {
device: DeviceWithVerification;
pusher?: IPusher | undefined;
localNotificationSettings?: LocalNotificationSettings | undefined;
isSigningOut: boolean;
onVerifyDevice?: () => void;
onSignOutDevice: () => void;
saveDeviceName: (deviceName: string) => Promise<void>;
setPusherEnabled?: (deviceId: string, enabled: boolean) => Promise<void> | undefined;
setPushNotifications?: (deviceId: string, enabled: boolean) => Promise<void> | undefined;
supportsMSC3881?: boolean | undefined;
}

Expand All @@ -46,11 +48,12 @@ interface MetadataTable {
const DeviceDetails: React.FC<Props> = ({
device,
pusher,
localNotificationSettings,
isSigningOut,
onVerifyDevice,
onSignOutDevice,
saveDeviceName,
setPusherEnabled,
setPushNotifications,
supportsMSC3881,
}) => {
const metadata: MetadataTable[] = [
Expand All @@ -70,6 +73,21 @@ const DeviceDetails: React.FC<Props> = ({
],
},
];

const showPushNotificationSection = !!pusher || !!localNotificationSettings;

function isPushNotificationsEnabled(pusher: IPusher, notificationSettings: LocalNotificationSettings): boolean {
if (pusher) return pusher[PUSHER_ENABLED.name];
if (localNotificationSettings) return !localNotificationSettings.is_silenced;
return true;
}

function isCheckboxDisabled(pusher: IPusher, notificationSettings: LocalNotificationSettings): boolean {
if (localNotificationSettings) return false;
if (pusher && !supportsMSC3881) return true;
return false;
}

return <div className='mx_DeviceDetails' data-testid={`device-detail-${device.device_id}`}>
<section className='mx_DeviceDetails_section'>
<DeviceDetailHeading
Expand Down Expand Up @@ -102,17 +120,17 @@ const DeviceDetails: React.FC<Props> = ({
</table>,
) }
</section>
{ pusher && (
{ showPushNotificationSection && (
<section
className='mx_DeviceDetails_section mx_DeviceDetails_pushNotifications'
data-testid='device-detail-push-notification'
>
<ToggleSwitch
// For backwards compatibility, if `enabled` is missing
// default to `true`
checked={pusher?.[PUSHER_ENABLED.name] ?? true}
disabled={!supportsMSC3881}
onChange={(checked) => setPusherEnabled?.(device.device_id, checked)}
checked={isPushNotificationsEnabled(pusher, localNotificationSettings)}
disabled={isCheckboxDisabled(pusher, localNotificationSettings)}
onChange={checked => setPushNotifications?.(device.device_id, checked)}
aria-label={_t("Toggle push notifications on this session.")}
data-testid='device-detail-push-notification-checkbox'
/>
Expand Down
19 changes: 13 additions & 6 deletions src/components/views/settings/devices/FilteredDeviceList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
import React, { ForwardedRef, forwardRef } from 'react';
import { IPusher } from 'matrix-js-sdk/src/@types/PushRules';
import { PUSHER_DEVICE_ID } from 'matrix-js-sdk/src/@types/event';
import { LocalNotificationSettings } from 'matrix-js-sdk/src/@types/local_notifications';

import { _t } from '../../../../languageHandler';
import AccessibleButton from '../../elements/AccessibleButton';
Expand All @@ -39,6 +40,7 @@ import { DevicesState } from './useOwnDevices';
interface Props {
devices: DevicesDictionary;
pushers: IPusher[];
localNotificationSettings: Map<string, LocalNotificationSettings>;
expandedDeviceIds: DeviceWithVerification['device_id'][];
signingOutDeviceIds: DeviceWithVerification['device_id'][];
filter?: DeviceSecurityVariation;
Expand All @@ -47,7 +49,7 @@ interface Props {
onSignOutDevices: (deviceIds: DeviceWithVerification['device_id'][]) => void;
saveDeviceName: DevicesState['saveDeviceName'];
onRequestDeviceVerification?: (deviceId: DeviceWithVerification['device_id']) => void;
setPusherEnabled: (deviceId: string, enabled: boolean) => Promise<void>;
setPushNotifications: (deviceId: string, enabled: boolean) => Promise<void>;
supportsMSC3881?: boolean | undefined;
}

Expand Down Expand Up @@ -141,24 +143,26 @@ const NoResults: React.FC<NoResultsProps> = ({ filter, clearFilter }) =>
const DeviceListItem: React.FC<{
device: DeviceWithVerification;
pusher?: IPusher | undefined;
localNotificationSettings?: LocalNotificationSettings | undefined;
isExpanded: boolean;
isSigningOut: boolean;
onDeviceExpandToggle: () => void;
onSignOutDevice: () => void;
saveDeviceName: (deviceName: string) => Promise<void>;
onRequestDeviceVerification?: () => void;
setPusherEnabled: (deviceId: string, enabled: boolean) => Promise<void>;
setPushNotifications: (deviceId: string, enabled: boolean) => Promise<void>;
supportsMSC3881?: boolean | undefined;
}> = ({
device,
pusher,
localNotificationSettings,
isExpanded,
isSigningOut,
onDeviceExpandToggle,
onSignOutDevice,
saveDeviceName,
onRequestDeviceVerification,
setPusherEnabled,
setPushNotifications,
supportsMSC3881,
}) => <li className='mx_FilteredDeviceList_listItem'>
<DeviceTile
Expand All @@ -174,11 +178,12 @@ const DeviceListItem: React.FC<{
<DeviceDetails
device={device}
pusher={pusher}
localNotificationSettings={localNotificationSettings}
isSigningOut={isSigningOut}
onVerifyDevice={onRequestDeviceVerification}
onSignOutDevice={onSignOutDevice}
saveDeviceName={saveDeviceName}
setPusherEnabled={setPusherEnabled}
setPushNotifications={setPushNotifications}
supportsMSC3881={supportsMSC3881}
/>
}
Expand All @@ -192,6 +197,7 @@ export const FilteredDeviceList =
forwardRef(({
devices,
pushers,
localNotificationSettings,
filter,
expandedDeviceIds,
signingOutDeviceIds,
Expand All @@ -200,7 +206,7 @@ export const FilteredDeviceList =
saveDeviceName,
onSignOutDevices,
onRequestDeviceVerification,
setPusherEnabled,
setPushNotifications,
supportsMSC3881,
}: Props, ref: ForwardedRef<HTMLDivElement>) => {
const sortedDevices = getFilteredSortedDevices(devices, filter);
Expand Down Expand Up @@ -258,6 +264,7 @@ export const FilteredDeviceList =
key={device.device_id}
device={device}
pusher={getPusherForDevice(device)}
localNotificationSettings={localNotificationSettings.get(device.device_id)}
isExpanded={expandedDeviceIds.includes(device.device_id)}
isSigningOut={signingOutDeviceIds.includes(device.device_id)}
onDeviceExpandToggle={() => onDeviceExpandToggle(device.device_id)}
Expand All @@ -268,7 +275,7 @@ export const FilteredDeviceList =
? () => onRequestDeviceVerification(device.device_id)
: undefined
}
setPusherEnabled={setPusherEnabled}
setPushNotifications={setPushNotifications}
supportsMSC3881={supportsMSC3881}
/>,
) }
Expand Down
54 changes: 43 additions & 11 deletions src/components/views/settings/devices/useOwnDevices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,19 @@ limitations under the License.
*/

import { useCallback, useContext, useEffect, useState } from "react";
import { IMyDevice, IPusher, MatrixClient, PUSHER_DEVICE_ID, PUSHER_ENABLED } from "matrix-js-sdk/src/matrix";
import {
IMyDevice,
IPusher,
LOCAL_NOTIFICATION_SETTINGS_PREFIX,
MatrixClient,
PUSHER_DEVICE_ID,
PUSHER_ENABLED,
} from "matrix-js-sdk/src/matrix";
import { CrossSigningInfo } from "matrix-js-sdk/src/crypto/CrossSigning";
import { VerificationRequest } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
import { MatrixError } from "matrix-js-sdk/src/http-api";
import { logger } from "matrix-js-sdk/src/logger";
import { LocalNotificationSettings } from "matrix-js-sdk/src/@types/local_notifications";

import MatrixClientContext from "../../../../contexts/MatrixClientContext";
import { _t } from "../../../../languageHandler";
Expand Down Expand Up @@ -77,13 +85,14 @@ export enum OwnDevicesError {
export type DevicesState = {
devices: DevicesDictionary;
pushers: IPusher[];
localNotificationSettings: Map<string, LocalNotificationSettings>;
currentDeviceId: string;
isLoadingDeviceList: boolean;
// not provided when current session cannot request verification
requestDeviceVerification?: (deviceId: DeviceWithVerification['device_id']) => Promise<VerificationRequest>;
refreshDevices: () => Promise<void>;
saveDeviceName: (deviceId: DeviceWithVerification['device_id'], deviceName: string) => Promise<void>;
setPusherEnabled: (deviceId: DeviceWithVerification['device_id'], enabled: boolean) => Promise<void>;
setPushNotifications: (deviceId: DeviceWithVerification['device_id'], enabled: boolean) => Promise<void>;
error?: OwnDevicesError;
supportsMSC3881?: boolean | undefined;
};
Expand All @@ -95,6 +104,8 @@ export const useOwnDevices = (): DevicesState => {

const [devices, setDevices] = useState<DevicesState['devices']>({});
const [pushers, setPushers] = useState<DevicesState['pushers']>([]);
const [localNotificationSettings, setLocalNotificationSettings]
= useState<DevicesState['localNotificationSettings']>(new Map<string, LocalNotificationSettings>());
const [isLoadingDeviceList, setIsLoadingDeviceList] = useState(true);
const [supportsMSC3881, setSupportsMSC3881] = useState(true); // optimisticly saying yes!

Expand All @@ -120,6 +131,19 @@ export const useOwnDevices = (): DevicesState => {
const { pushers } = await matrixClient.getPushers();
setPushers(pushers);

const notificationSettings = new Map<string, LocalNotificationSettings>();
Object.keys(devices).forEach((deviceId) => {
const eventType = `${LOCAL_NOTIFICATION_SETTINGS_PREFIX.name}.${deviceId}`;
const event = matrixClient.getAccountData(eventType);
if (event) {
notificationSettings.set(
deviceId,
event.getContent(),
);
}
});
setLocalNotificationSettings(notificationSettings);

setIsLoadingDeviceList(false);
} catch (error) {
if ((error as MatrixError).httpStatus == 404) {
Expand Down Expand Up @@ -169,32 +193,40 @@ export const useOwnDevices = (): DevicesState => {
}
}, [matrixClient, devices, refreshDevices]);

const setPusherEnabled = useCallback(
const setPushNotifications = useCallback(
async (deviceId: DeviceWithVerification['device_id'], enabled: boolean): Promise<void> => {
const pusher = pushers.find(pusher => pusher[PUSHER_DEVICE_ID.name] === deviceId);
try {
await matrixClient.setPusher({
...pusher,
[PUSHER_ENABLED.name]: enabled,
});
await refreshDevices();
const pusher = pushers.find(pusher => pusher[PUSHER_DEVICE_ID.name] === deviceId);
if (pusher) {
await matrixClient.setPusher({
...pusher,
[PUSHER_ENABLED.name]: enabled,
});
} else if (localNotificationSettings.has(deviceId)) {
await matrixClient.setLocalNotificationSettings(deviceId, {
is_silenced: !enabled,
});
}
} catch (error) {
logger.error("Error setting pusher state", error);
throw new Error(_t("Failed to set pusher state"));
} finally {
await refreshDevices();
}
}, [matrixClient, pushers, refreshDevices],
}, [matrixClient, pushers, localNotificationSettings, refreshDevices],
);

return {
devices,
pushers,
localNotificationSettings,
currentDeviceId,
isLoadingDeviceList,
error,
requestDeviceVerification,
refreshDevices,
saveDeviceName,
setPusherEnabled,
setPushNotifications,
supportsMSC3881,
};
};
12 changes: 8 additions & 4 deletions src/components/views/settings/tabs/user/SessionManagerTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,13 @@ const SessionManagerTab: React.FC = () => {
const {
devices,
pushers,
localNotificationSettings,
currentDeviceId,
isLoadingDeviceList,
requestDeviceVerification,
refreshDevices,
saveDeviceName,
setPusherEnabled,
setPushNotifications,
supportsMSC3881,
} = useOwnDevices();
const [filter, setFilter] = useState<DeviceSecurityVariation>();
Expand Down Expand Up @@ -171,9 +172,11 @@ const SessionManagerTab: React.FC = () => {
/>
<CurrentDeviceSection
device={currentDevice}
isSigningOut={signingOutDeviceIds.includes(currentDevice?.device_id)}
localNotificationSettings={localNotificationSettings.get(currentDeviceId)}
setPushNotifications={setPushNotifications}
isSigningOut={signingOutDeviceIds.includes(currentDeviceId)}
isLoading={isLoadingDeviceList}
saveDeviceName={(deviceName) => saveDeviceName(currentDevice?.device_id, deviceName)}
saveDeviceName={(deviceName) => saveDeviceName(currentDeviceId, deviceName)}
onVerifyCurrentDevice={onVerifyCurrentDevice}
onSignOutCurrentDevice={onSignOutCurrentDevice}
/>
Expand All @@ -190,6 +193,7 @@ const SessionManagerTab: React.FC = () => {
<FilteredDeviceList
devices={otherDevices}
pushers={pushers}
localNotificationSettings={localNotificationSettings}
filter={filter}
expandedDeviceIds={expandedDeviceIds}
signingOutDeviceIds={signingOutDeviceIds}
Expand All @@ -198,7 +202,7 @@ const SessionManagerTab: React.FC = () => {
onRequestDeviceVerification={requestDeviceVerification ? onTriggerDeviceVerification : undefined}
onSignOutDevices={onSignOutOtherDevices}
saveDeviceName={saveDeviceName}
setPusherEnabled={setPusherEnabled}
setPushNotifications={setPushNotifications}
ref={filteredDeviceListRef}
supportsMSC3881={supportsMSC3881}
/>
Expand Down
Loading

0 comments on commit c3bfb6e

Please sign in to comment.