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

Display push toggle for web sessions (MSC3890) #9327

Merged
merged 5 commits into from
Sep 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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