Display push toggle for web sessions (MSC3890) (#9327)
This commit is contained in:
parent
e15ef9f3de
commit
c3bfb6e4a9
8 changed files with 166 additions and 33 deletions
|
@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { LocalNotificationSettings } from 'matrix-js-sdk/src/@types/local_notifications';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
import { _t } from '../../../../languageHandler';
|
import { _t } from '../../../../languageHandler';
|
||||||
|
@ -29,6 +30,8 @@ interface Props {
|
||||||
device?: DeviceWithVerification;
|
device?: DeviceWithVerification;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
isSigningOut: boolean;
|
isSigningOut: boolean;
|
||||||
|
localNotificationSettings?: LocalNotificationSettings | undefined;
|
||||||
|
setPushNotifications?: (deviceId: string, enabled: boolean) => Promise<void> | undefined;
|
||||||
onVerifyCurrentDevice: () => void;
|
onVerifyCurrentDevice: () => void;
|
||||||
onSignOutCurrentDevice: () => void;
|
onSignOutCurrentDevice: () => void;
|
||||||
saveDeviceName: (deviceName: string) => Promise<void>;
|
saveDeviceName: (deviceName: string) => Promise<void>;
|
||||||
|
@ -38,6 +41,8 @@ const CurrentDeviceSection: React.FC<Props> = ({
|
||||||
device,
|
device,
|
||||||
isLoading,
|
isLoading,
|
||||||
isSigningOut,
|
isSigningOut,
|
||||||
|
localNotificationSettings,
|
||||||
|
setPushNotifications,
|
||||||
onVerifyCurrentDevice,
|
onVerifyCurrentDevice,
|
||||||
onSignOutCurrentDevice,
|
onSignOutCurrentDevice,
|
||||||
saveDeviceName,
|
saveDeviceName,
|
||||||
|
@ -63,6 +68,8 @@ const CurrentDeviceSection: React.FC<Props> = ({
|
||||||
{ isExpanded &&
|
{ isExpanded &&
|
||||||
<DeviceDetails
|
<DeviceDetails
|
||||||
device={device}
|
device={device}
|
||||||
|
localNotificationSettings={localNotificationSettings}
|
||||||
|
setPushNotifications={setPushNotifications}
|
||||||
isSigningOut={isSigningOut}
|
isSigningOut={isSigningOut}
|
||||||
onVerifyDevice={onVerifyCurrentDevice}
|
onVerifyDevice={onVerifyCurrentDevice}
|
||||||
onSignOutDevice={onSignOutCurrentDevice}
|
onSignOutDevice={onSignOutCurrentDevice}
|
||||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { IPusher } from 'matrix-js-sdk/src/@types/PushRules';
|
import { IPusher } from 'matrix-js-sdk/src/@types/PushRules';
|
||||||
import { PUSHER_ENABLED } from 'matrix-js-sdk/src/@types/event';
|
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 { formatDate } from '../../../../DateUtils';
|
||||||
import { _t } from '../../../../languageHandler';
|
import { _t } from '../../../../languageHandler';
|
||||||
|
@ -30,11 +31,12 @@ import { DeviceWithVerification } from './types';
|
||||||
interface Props {
|
interface Props {
|
||||||
device: DeviceWithVerification;
|
device: DeviceWithVerification;
|
||||||
pusher?: IPusher | undefined;
|
pusher?: IPusher | undefined;
|
||||||
|
localNotificationSettings?: LocalNotificationSettings | undefined;
|
||||||
isSigningOut: boolean;
|
isSigningOut: boolean;
|
||||||
onVerifyDevice?: () => void;
|
onVerifyDevice?: () => void;
|
||||||
onSignOutDevice: () => void;
|
onSignOutDevice: () => void;
|
||||||
saveDeviceName: (deviceName: string) => Promise<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;
|
supportsMSC3881?: boolean | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,11 +48,12 @@ interface MetadataTable {
|
||||||
const DeviceDetails: React.FC<Props> = ({
|
const DeviceDetails: React.FC<Props> = ({
|
||||||
device,
|
device,
|
||||||
pusher,
|
pusher,
|
||||||
|
localNotificationSettings,
|
||||||
isSigningOut,
|
isSigningOut,
|
||||||
onVerifyDevice,
|
onVerifyDevice,
|
||||||
onSignOutDevice,
|
onSignOutDevice,
|
||||||
saveDeviceName,
|
saveDeviceName,
|
||||||
setPusherEnabled,
|
setPushNotifications,
|
||||||
supportsMSC3881,
|
supportsMSC3881,
|
||||||
}) => {
|
}) => {
|
||||||
const metadata: MetadataTable[] = [
|
const metadata: MetadataTable[] = [
|
||||||
|
@ -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}`}>
|
return <div className='mx_DeviceDetails' data-testid={`device-detail-${device.device_id}`}>
|
||||||
<section className='mx_DeviceDetails_section'>
|
<section className='mx_DeviceDetails_section'>
|
||||||
<DeviceDetailHeading
|
<DeviceDetailHeading
|
||||||
|
@ -102,7 +120,7 @@ const DeviceDetails: React.FC<Props> = ({
|
||||||
</table>,
|
</table>,
|
||||||
) }
|
) }
|
||||||
</section>
|
</section>
|
||||||
{ pusher && (
|
{ showPushNotificationSection && (
|
||||||
<section
|
<section
|
||||||
className='mx_DeviceDetails_section mx_DeviceDetails_pushNotifications'
|
className='mx_DeviceDetails_section mx_DeviceDetails_pushNotifications'
|
||||||
data-testid='device-detail-push-notification'
|
data-testid='device-detail-push-notification'
|
||||||
|
@ -110,9 +128,9 @@ const DeviceDetails: React.FC<Props> = ({
|
||||||
<ToggleSwitch
|
<ToggleSwitch
|
||||||
// For backwards compatibility, if `enabled` is missing
|
// For backwards compatibility, if `enabled` is missing
|
||||||
// default to `true`
|
// default to `true`
|
||||||
checked={pusher?.[PUSHER_ENABLED.name] ?? true}
|
checked={isPushNotificationsEnabled(pusher, localNotificationSettings)}
|
||||||
disabled={!supportsMSC3881}
|
disabled={isCheckboxDisabled(pusher, localNotificationSettings)}
|
||||||
onChange={(checked) => setPusherEnabled?.(device.device_id, checked)}
|
onChange={checked => setPushNotifications?.(device.device_id, checked)}
|
||||||
aria-label={_t("Toggle push notifications on this session.")}
|
aria-label={_t("Toggle push notifications on this session.")}
|
||||||
data-testid='device-detail-push-notification-checkbox'
|
data-testid='device-detail-push-notification-checkbox'
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
||||||
import React, { ForwardedRef, forwardRef } from 'react';
|
import React, { ForwardedRef, forwardRef } from 'react';
|
||||||
import { IPusher } from 'matrix-js-sdk/src/@types/PushRules';
|
import { IPusher } from 'matrix-js-sdk/src/@types/PushRules';
|
||||||
import { PUSHER_DEVICE_ID } from 'matrix-js-sdk/src/@types/event';
|
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 { _t } from '../../../../languageHandler';
|
||||||
import AccessibleButton from '../../elements/AccessibleButton';
|
import AccessibleButton from '../../elements/AccessibleButton';
|
||||||
|
@ -39,6 +40,7 @@ import { DevicesState } from './useOwnDevices';
|
||||||
interface Props {
|
interface Props {
|
||||||
devices: DevicesDictionary;
|
devices: DevicesDictionary;
|
||||||
pushers: IPusher[];
|
pushers: IPusher[];
|
||||||
|
localNotificationSettings: Map<string, LocalNotificationSettings>;
|
||||||
expandedDeviceIds: DeviceWithVerification['device_id'][];
|
expandedDeviceIds: DeviceWithVerification['device_id'][];
|
||||||
signingOutDeviceIds: DeviceWithVerification['device_id'][];
|
signingOutDeviceIds: DeviceWithVerification['device_id'][];
|
||||||
filter?: DeviceSecurityVariation;
|
filter?: DeviceSecurityVariation;
|
||||||
|
@ -47,7 +49,7 @@ interface Props {
|
||||||
onSignOutDevices: (deviceIds: DeviceWithVerification['device_id'][]) => void;
|
onSignOutDevices: (deviceIds: DeviceWithVerification['device_id'][]) => void;
|
||||||
saveDeviceName: DevicesState['saveDeviceName'];
|
saveDeviceName: DevicesState['saveDeviceName'];
|
||||||
onRequestDeviceVerification?: (deviceId: DeviceWithVerification['device_id']) => void;
|
onRequestDeviceVerification?: (deviceId: DeviceWithVerification['device_id']) => void;
|
||||||
setPusherEnabled: (deviceId: string, enabled: boolean) => Promise<void>;
|
setPushNotifications: (deviceId: string, enabled: boolean) => Promise<void>;
|
||||||
supportsMSC3881?: boolean | undefined;
|
supportsMSC3881?: boolean | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,24 +143,26 @@ const NoResults: React.FC<NoResultsProps> = ({ filter, clearFilter }) =>
|
||||||
const DeviceListItem: React.FC<{
|
const DeviceListItem: React.FC<{
|
||||||
device: DeviceWithVerification;
|
device: DeviceWithVerification;
|
||||||
pusher?: IPusher | undefined;
|
pusher?: IPusher | undefined;
|
||||||
|
localNotificationSettings?: LocalNotificationSettings | undefined;
|
||||||
isExpanded: boolean;
|
isExpanded: boolean;
|
||||||
isSigningOut: boolean;
|
isSigningOut: boolean;
|
||||||
onDeviceExpandToggle: () => void;
|
onDeviceExpandToggle: () => void;
|
||||||
onSignOutDevice: () => void;
|
onSignOutDevice: () => void;
|
||||||
saveDeviceName: (deviceName: string) => Promise<void>;
|
saveDeviceName: (deviceName: string) => Promise<void>;
|
||||||
onRequestDeviceVerification?: () => void;
|
onRequestDeviceVerification?: () => void;
|
||||||
setPusherEnabled: (deviceId: string, enabled: boolean) => Promise<void>;
|
setPushNotifications: (deviceId: string, enabled: boolean) => Promise<void>;
|
||||||
supportsMSC3881?: boolean | undefined;
|
supportsMSC3881?: boolean | undefined;
|
||||||
}> = ({
|
}> = ({
|
||||||
device,
|
device,
|
||||||
pusher,
|
pusher,
|
||||||
|
localNotificationSettings,
|
||||||
isExpanded,
|
isExpanded,
|
||||||
isSigningOut,
|
isSigningOut,
|
||||||
onDeviceExpandToggle,
|
onDeviceExpandToggle,
|
||||||
onSignOutDevice,
|
onSignOutDevice,
|
||||||
saveDeviceName,
|
saveDeviceName,
|
||||||
onRequestDeviceVerification,
|
onRequestDeviceVerification,
|
||||||
setPusherEnabled,
|
setPushNotifications,
|
||||||
supportsMSC3881,
|
supportsMSC3881,
|
||||||
}) => <li className='mx_FilteredDeviceList_listItem'>
|
}) => <li className='mx_FilteredDeviceList_listItem'>
|
||||||
<DeviceTile
|
<DeviceTile
|
||||||
|
@ -174,11 +178,12 @@ const DeviceListItem: React.FC<{
|
||||||
<DeviceDetails
|
<DeviceDetails
|
||||||
device={device}
|
device={device}
|
||||||
pusher={pusher}
|
pusher={pusher}
|
||||||
|
localNotificationSettings={localNotificationSettings}
|
||||||
isSigningOut={isSigningOut}
|
isSigningOut={isSigningOut}
|
||||||
onVerifyDevice={onRequestDeviceVerification}
|
onVerifyDevice={onRequestDeviceVerification}
|
||||||
onSignOutDevice={onSignOutDevice}
|
onSignOutDevice={onSignOutDevice}
|
||||||
saveDeviceName={saveDeviceName}
|
saveDeviceName={saveDeviceName}
|
||||||
setPusherEnabled={setPusherEnabled}
|
setPushNotifications={setPushNotifications}
|
||||||
supportsMSC3881={supportsMSC3881}
|
supportsMSC3881={supportsMSC3881}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
@ -192,6 +197,7 @@ export const FilteredDeviceList =
|
||||||
forwardRef(({
|
forwardRef(({
|
||||||
devices,
|
devices,
|
||||||
pushers,
|
pushers,
|
||||||
|
localNotificationSettings,
|
||||||
filter,
|
filter,
|
||||||
expandedDeviceIds,
|
expandedDeviceIds,
|
||||||
signingOutDeviceIds,
|
signingOutDeviceIds,
|
||||||
|
@ -200,7 +206,7 @@ export const FilteredDeviceList =
|
||||||
saveDeviceName,
|
saveDeviceName,
|
||||||
onSignOutDevices,
|
onSignOutDevices,
|
||||||
onRequestDeviceVerification,
|
onRequestDeviceVerification,
|
||||||
setPusherEnabled,
|
setPushNotifications,
|
||||||
supportsMSC3881,
|
supportsMSC3881,
|
||||||
}: Props, ref: ForwardedRef<HTMLDivElement>) => {
|
}: Props, ref: ForwardedRef<HTMLDivElement>) => {
|
||||||
const sortedDevices = getFilteredSortedDevices(devices, filter);
|
const sortedDevices = getFilteredSortedDevices(devices, filter);
|
||||||
|
@ -258,6 +264,7 @@ export const FilteredDeviceList =
|
||||||
key={device.device_id}
|
key={device.device_id}
|
||||||
device={device}
|
device={device}
|
||||||
pusher={getPusherForDevice(device)}
|
pusher={getPusherForDevice(device)}
|
||||||
|
localNotificationSettings={localNotificationSettings.get(device.device_id)}
|
||||||
isExpanded={expandedDeviceIds.includes(device.device_id)}
|
isExpanded={expandedDeviceIds.includes(device.device_id)}
|
||||||
isSigningOut={signingOutDeviceIds.includes(device.device_id)}
|
isSigningOut={signingOutDeviceIds.includes(device.device_id)}
|
||||||
onDeviceExpandToggle={() => onDeviceExpandToggle(device.device_id)}
|
onDeviceExpandToggle={() => onDeviceExpandToggle(device.device_id)}
|
||||||
|
@ -268,7 +275,7 @@ export const FilteredDeviceList =
|
||||||
? () => onRequestDeviceVerification(device.device_id)
|
? () => onRequestDeviceVerification(device.device_id)
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
setPusherEnabled={setPusherEnabled}
|
setPushNotifications={setPushNotifications}
|
||||||
supportsMSC3881={supportsMSC3881}
|
supportsMSC3881={supportsMSC3881}
|
||||||
/>,
|
/>,
|
||||||
) }
|
) }
|
||||||
|
|
|
@ -15,11 +15,19 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useCallback, useContext, useEffect, useState } from "react";
|
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 { CrossSigningInfo } from "matrix-js-sdk/src/crypto/CrossSigning";
|
||||||
import { VerificationRequest } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
|
import { VerificationRequest } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
|
||||||
import { MatrixError } from "matrix-js-sdk/src/http-api";
|
import { MatrixError } from "matrix-js-sdk/src/http-api";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
import { LocalNotificationSettings } from "matrix-js-sdk/src/@types/local_notifications";
|
||||||
|
|
||||||
import MatrixClientContext from "../../../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../../../contexts/MatrixClientContext";
|
||||||
import { _t } from "../../../../languageHandler";
|
import { _t } from "../../../../languageHandler";
|
||||||
|
@ -77,13 +85,14 @@ export enum OwnDevicesError {
|
||||||
export type DevicesState = {
|
export type DevicesState = {
|
||||||
devices: DevicesDictionary;
|
devices: DevicesDictionary;
|
||||||
pushers: IPusher[];
|
pushers: IPusher[];
|
||||||
|
localNotificationSettings: Map<string, LocalNotificationSettings>;
|
||||||
currentDeviceId: string;
|
currentDeviceId: string;
|
||||||
isLoadingDeviceList: boolean;
|
isLoadingDeviceList: boolean;
|
||||||
// not provided when current session cannot request verification
|
// not provided when current session cannot request verification
|
||||||
requestDeviceVerification?: (deviceId: DeviceWithVerification['device_id']) => Promise<VerificationRequest>;
|
requestDeviceVerification?: (deviceId: DeviceWithVerification['device_id']) => Promise<VerificationRequest>;
|
||||||
refreshDevices: () => Promise<void>;
|
refreshDevices: () => Promise<void>;
|
||||||
saveDeviceName: (deviceId: DeviceWithVerification['device_id'], deviceName: string) => 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;
|
error?: OwnDevicesError;
|
||||||
supportsMSC3881?: boolean | undefined;
|
supportsMSC3881?: boolean | undefined;
|
||||||
};
|
};
|
||||||
|
@ -95,6 +104,8 @@ export const useOwnDevices = (): DevicesState => {
|
||||||
|
|
||||||
const [devices, setDevices] = useState<DevicesState['devices']>({});
|
const [devices, setDevices] = useState<DevicesState['devices']>({});
|
||||||
const [pushers, setPushers] = useState<DevicesState['pushers']>([]);
|
const [pushers, setPushers] = useState<DevicesState['pushers']>([]);
|
||||||
|
const [localNotificationSettings, setLocalNotificationSettings]
|
||||||
|
= useState<DevicesState['localNotificationSettings']>(new Map<string, LocalNotificationSettings>());
|
||||||
const [isLoadingDeviceList, setIsLoadingDeviceList] = useState(true);
|
const [isLoadingDeviceList, setIsLoadingDeviceList] = useState(true);
|
||||||
const [supportsMSC3881, setSupportsMSC3881] = useState(true); // optimisticly saying yes!
|
const [supportsMSC3881, setSupportsMSC3881] = useState(true); // optimisticly saying yes!
|
||||||
|
|
||||||
|
@ -120,6 +131,19 @@ export const useOwnDevices = (): DevicesState => {
|
||||||
const { pushers } = await matrixClient.getPushers();
|
const { pushers } = await matrixClient.getPushers();
|
||||||
setPushers(pushers);
|
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);
|
setIsLoadingDeviceList(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if ((error as MatrixError).httpStatus == 404) {
|
if ((error as MatrixError).httpStatus == 404) {
|
||||||
|
@ -169,32 +193,40 @@ export const useOwnDevices = (): DevicesState => {
|
||||||
}
|
}
|
||||||
}, [matrixClient, devices, refreshDevices]);
|
}, [matrixClient, devices, refreshDevices]);
|
||||||
|
|
||||||
const setPusherEnabled = useCallback(
|
const setPushNotifications = useCallback(
|
||||||
async (deviceId: DeviceWithVerification['device_id'], enabled: boolean): Promise<void> => {
|
async (deviceId: DeviceWithVerification['device_id'], enabled: boolean): Promise<void> => {
|
||||||
const pusher = pushers.find(pusher => pusher[PUSHER_DEVICE_ID.name] === deviceId);
|
|
||||||
try {
|
try {
|
||||||
|
const pusher = pushers.find(pusher => pusher[PUSHER_DEVICE_ID.name] === deviceId);
|
||||||
|
if (pusher) {
|
||||||
await matrixClient.setPusher({
|
await matrixClient.setPusher({
|
||||||
...pusher,
|
...pusher,
|
||||||
[PUSHER_ENABLED.name]: enabled,
|
[PUSHER_ENABLED.name]: enabled,
|
||||||
});
|
});
|
||||||
await refreshDevices();
|
} else if (localNotificationSettings.has(deviceId)) {
|
||||||
|
await matrixClient.setLocalNotificationSettings(deviceId, {
|
||||||
|
is_silenced: !enabled,
|
||||||
|
});
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("Error setting pusher state", error);
|
logger.error("Error setting pusher state", error);
|
||||||
throw new Error(_t("Failed to set pusher state"));
|
throw new Error(_t("Failed to set pusher state"));
|
||||||
|
} finally {
|
||||||
|
await refreshDevices();
|
||||||
}
|
}
|
||||||
}, [matrixClient, pushers, refreshDevices],
|
}, [matrixClient, pushers, localNotificationSettings, refreshDevices],
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
devices,
|
devices,
|
||||||
pushers,
|
pushers,
|
||||||
|
localNotificationSettings,
|
||||||
currentDeviceId,
|
currentDeviceId,
|
||||||
isLoadingDeviceList,
|
isLoadingDeviceList,
|
||||||
error,
|
error,
|
||||||
requestDeviceVerification,
|
requestDeviceVerification,
|
||||||
refreshDevices,
|
refreshDevices,
|
||||||
saveDeviceName,
|
saveDeviceName,
|
||||||
setPusherEnabled,
|
setPushNotifications,
|
||||||
supportsMSC3881,
|
supportsMSC3881,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -88,12 +88,13 @@ const SessionManagerTab: React.FC = () => {
|
||||||
const {
|
const {
|
||||||
devices,
|
devices,
|
||||||
pushers,
|
pushers,
|
||||||
|
localNotificationSettings,
|
||||||
currentDeviceId,
|
currentDeviceId,
|
||||||
isLoadingDeviceList,
|
isLoadingDeviceList,
|
||||||
requestDeviceVerification,
|
requestDeviceVerification,
|
||||||
refreshDevices,
|
refreshDevices,
|
||||||
saveDeviceName,
|
saveDeviceName,
|
||||||
setPusherEnabled,
|
setPushNotifications,
|
||||||
supportsMSC3881,
|
supportsMSC3881,
|
||||||
} = useOwnDevices();
|
} = useOwnDevices();
|
||||||
const [filter, setFilter] = useState<DeviceSecurityVariation>();
|
const [filter, setFilter] = useState<DeviceSecurityVariation>();
|
||||||
|
@ -171,9 +172,11 @@ const SessionManagerTab: React.FC = () => {
|
||||||
/>
|
/>
|
||||||
<CurrentDeviceSection
|
<CurrentDeviceSection
|
||||||
device={currentDevice}
|
device={currentDevice}
|
||||||
isSigningOut={signingOutDeviceIds.includes(currentDevice?.device_id)}
|
localNotificationSettings={localNotificationSettings.get(currentDeviceId)}
|
||||||
|
setPushNotifications={setPushNotifications}
|
||||||
|
isSigningOut={signingOutDeviceIds.includes(currentDeviceId)}
|
||||||
isLoading={isLoadingDeviceList}
|
isLoading={isLoadingDeviceList}
|
||||||
saveDeviceName={(deviceName) => saveDeviceName(currentDevice?.device_id, deviceName)}
|
saveDeviceName={(deviceName) => saveDeviceName(currentDeviceId, deviceName)}
|
||||||
onVerifyCurrentDevice={onVerifyCurrentDevice}
|
onVerifyCurrentDevice={onVerifyCurrentDevice}
|
||||||
onSignOutCurrentDevice={onSignOutCurrentDevice}
|
onSignOutCurrentDevice={onSignOutCurrentDevice}
|
||||||
/>
|
/>
|
||||||
|
@ -190,6 +193,7 @@ const SessionManagerTab: React.FC = () => {
|
||||||
<FilteredDeviceList
|
<FilteredDeviceList
|
||||||
devices={otherDevices}
|
devices={otherDevices}
|
||||||
pushers={pushers}
|
pushers={pushers}
|
||||||
|
localNotificationSettings={localNotificationSettings}
|
||||||
filter={filter}
|
filter={filter}
|
||||||
expandedDeviceIds={expandedDeviceIds}
|
expandedDeviceIds={expandedDeviceIds}
|
||||||
signingOutDeviceIds={signingOutDeviceIds}
|
signingOutDeviceIds={signingOutDeviceIds}
|
||||||
|
@ -198,7 +202,7 @@ const SessionManagerTab: React.FC = () => {
|
||||||
onRequestDeviceVerification={requestDeviceVerification ? onTriggerDeviceVerification : undefined}
|
onRequestDeviceVerification={requestDeviceVerification ? onTriggerDeviceVerification : undefined}
|
||||||
onSignOutDevices={onSignOutOtherDevices}
|
onSignOutDevices={onSignOutOtherDevices}
|
||||||
saveDeviceName={saveDeviceName}
|
saveDeviceName={saveDeviceName}
|
||||||
setPusherEnabled={setPusherEnabled}
|
setPushNotifications={setPushNotifications}
|
||||||
ref={filteredDeviceListRef}
|
ref={filteredDeviceListRef}
|
||||||
supportsMSC3881={supportsMSC3881}
|
supportsMSC3881={supportsMSC3881}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -33,7 +33,7 @@ describe('<DeviceDetails />', () => {
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
onSignOutDevice: jest.fn(),
|
onSignOutDevice: jest.fn(),
|
||||||
saveDeviceName: jest.fn(),
|
saveDeviceName: jest.fn(),
|
||||||
setPusherEnabled: jest.fn(),
|
setPushNotifications: jest.fn(),
|
||||||
supportsMSC3881: true,
|
supportsMSC3881: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -157,6 +157,27 @@ describe('<DeviceDetails />', () => {
|
||||||
|
|
||||||
fireEvent.click(checkbox);
|
fireEvent.click(checkbox);
|
||||||
|
|
||||||
expect(defaultProps.setPusherEnabled).toHaveBeenCalledWith(device.device_id, !enabled);
|
expect(defaultProps.setPushNotifications).toHaveBeenCalledWith(device.device_id, !enabled);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('changes the local notifications settings status when clicked', () => {
|
||||||
|
const device = {
|
||||||
|
...baseDevice,
|
||||||
|
};
|
||||||
|
|
||||||
|
const enabled = false;
|
||||||
|
|
||||||
|
const { getByTestId } = render(getComponent({
|
||||||
|
device,
|
||||||
|
localNotificationSettings: {
|
||||||
|
is_silenced: !enabled,
|
||||||
|
},
|
||||||
|
isSigningOut: true,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const checkbox = getByTestId('device-detail-push-notification-checkbox');
|
||||||
|
fireEvent.click(checkbox);
|
||||||
|
|
||||||
|
expect(defaultProps.setPushNotifications).toHaveBeenCalledWith(device.device_id, !enabled);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -45,9 +45,10 @@ describe('<FilteredDeviceList />', () => {
|
||||||
onDeviceExpandToggle: jest.fn(),
|
onDeviceExpandToggle: jest.fn(),
|
||||||
onSignOutDevices: jest.fn(),
|
onSignOutDevices: jest.fn(),
|
||||||
saveDeviceName: jest.fn(),
|
saveDeviceName: jest.fn(),
|
||||||
setPusherEnabled: jest.fn(),
|
setPushNotifications: jest.fn(),
|
||||||
expandedDeviceIds: [],
|
expandedDeviceIds: [],
|
||||||
signingOutDeviceIds: [],
|
signingOutDeviceIds: [],
|
||||||
|
localNotificationSettings: new Map(),
|
||||||
devices: {
|
devices: {
|
||||||
[unverifiedNoMetadata.device_id]: unverifiedNoMetadata,
|
[unverifiedNoMetadata.device_id]: unverifiedNoMetadata,
|
||||||
[verifiedNoMetadata.device_id]: verifiedNoMetadata,
|
[verifiedNoMetadata.device_id]: verifiedNoMetadata,
|
||||||
|
|
|
@ -22,7 +22,13 @@ import { logger } from 'matrix-js-sdk/src/logger';
|
||||||
import { DeviceTrustLevel } from 'matrix-js-sdk/src/crypto/CrossSigning';
|
import { DeviceTrustLevel } from 'matrix-js-sdk/src/crypto/CrossSigning';
|
||||||
import { VerificationRequest } from 'matrix-js-sdk/src/crypto/verification/request/VerificationRequest';
|
import { VerificationRequest } from 'matrix-js-sdk/src/crypto/verification/request/VerificationRequest';
|
||||||
import { sleep } from 'matrix-js-sdk/src/utils';
|
import { sleep } from 'matrix-js-sdk/src/utils';
|
||||||
import { IMyDevice, PUSHER_DEVICE_ID, PUSHER_ENABLED } from 'matrix-js-sdk/src/matrix';
|
import {
|
||||||
|
IMyDevice,
|
||||||
|
LOCAL_NOTIFICATION_SETTINGS_PREFIX,
|
||||||
|
MatrixEvent,
|
||||||
|
PUSHER_DEVICE_ID,
|
||||||
|
PUSHER_ENABLED,
|
||||||
|
} from 'matrix-js-sdk/src/matrix';
|
||||||
|
|
||||||
import SessionManagerTab from '../../../../../../src/components/views/settings/tabs/user/SessionManagerTab';
|
import SessionManagerTab from '../../../../../../src/components/views/settings/tabs/user/SessionManagerTab';
|
||||||
import MatrixClientContext from '../../../../../../src/contexts/MatrixClientContext';
|
import MatrixClientContext from '../../../../../../src/contexts/MatrixClientContext';
|
||||||
|
@ -71,6 +77,8 @@ describe('<SessionManagerTab />', () => {
|
||||||
doesServerSupportUnstableFeature: jest.fn().mockResolvedValue(true),
|
doesServerSupportUnstableFeature: jest.fn().mockResolvedValue(true),
|
||||||
getPushers: jest.fn(),
|
getPushers: jest.fn(),
|
||||||
setPusher: jest.fn(),
|
setPusher: jest.fn(),
|
||||||
|
getAccountData: jest.fn(),
|
||||||
|
setLocalNotificationSettings: jest.fn(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const defaultProps = {};
|
const defaultProps = {};
|
||||||
|
@ -114,6 +122,19 @@ describe('<SessionManagerTab />', () => {
|
||||||
[PUSHER_ENABLED.name]: true,
|
[PUSHER_ENABLED.name]: true,
|
||||||
})],
|
})],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
mockClient.getAccountData
|
||||||
|
.mockReset()
|
||||||
|
.mockImplementation(eventType => {
|
||||||
|
if (eventType.startsWith(LOCAL_NOTIFICATION_SETTINGS_PREFIX.name)) {
|
||||||
|
return new MatrixEvent({
|
||||||
|
type: eventType,
|
||||||
|
content: {
|
||||||
|
is_silenced: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders spinner while devices load', () => {
|
it('renders spinner while devices load', () => {
|
||||||
|
@ -333,7 +354,6 @@ describe('<SessionManagerTab />', () => {
|
||||||
mockClient.getStoredDevice.mockImplementation((_userId, deviceId) => new DeviceInfo(deviceId));
|
mockClient.getStoredDevice.mockImplementation((_userId, deviceId) => new DeviceInfo(deviceId));
|
||||||
mockCrossSigningInfo.checkDeviceTrust
|
mockCrossSigningInfo.checkDeviceTrust
|
||||||
.mockImplementation((_userId, { deviceId }) => {
|
.mockImplementation((_userId, { deviceId }) => {
|
||||||
console.log('hhh', deviceId);
|
|
||||||
if (deviceId === alicesDevice.device_id) {
|
if (deviceId === alicesDevice.device_id) {
|
||||||
return new DeviceTrustLevel(true, true, false, false);
|
return new DeviceTrustLevel(true, true, false, false);
|
||||||
}
|
}
|
||||||
|
@ -363,7 +383,6 @@ describe('<SessionManagerTab />', () => {
|
||||||
mockClient.getStoredDevice.mockImplementation((_userId, deviceId) => new DeviceInfo(deviceId));
|
mockClient.getStoredDevice.mockImplementation((_userId, deviceId) => new DeviceInfo(deviceId));
|
||||||
mockCrossSigningInfo.checkDeviceTrust
|
mockCrossSigningInfo.checkDeviceTrust
|
||||||
.mockImplementation((_userId, { deviceId }) => {
|
.mockImplementation((_userId, { deviceId }) => {
|
||||||
console.log('hhh', deviceId);
|
|
||||||
if (deviceId === alicesDevice.device_id) {
|
if (deviceId === alicesDevice.device_id) {
|
||||||
return new DeviceTrustLevel(true, true, false, false);
|
return new DeviceTrustLevel(true, true, false, false);
|
||||||
}
|
}
|
||||||
|
@ -702,4 +721,28 @@ describe('<SessionManagerTab />', () => {
|
||||||
|
|
||||||
expect(mockClient.setPusher).toHaveBeenCalled();
|
expect(mockClient.setPusher).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("lets you change the local notification settings state", async () => {
|
||||||
|
const { getByTestId } = render(getComponent());
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
await flushPromisesWithFakeTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
toggleDeviceDetails(getByTestId, alicesDevice.device_id);
|
||||||
|
|
||||||
|
// device details are expanded
|
||||||
|
expect(getByTestId(`device-detail-${alicesDevice.device_id}`)).toBeTruthy();
|
||||||
|
expect(getByTestId('device-detail-push-notification')).toBeTruthy();
|
||||||
|
|
||||||
|
const checkbox = getByTestId('device-detail-push-notification-checkbox');
|
||||||
|
|
||||||
|
expect(checkbox).toBeTruthy();
|
||||||
|
fireEvent.click(checkbox);
|
||||||
|
|
||||||
|
expect(mockClient.setLocalNotificationSettings).toHaveBeenCalledWith(
|
||||||
|
alicesDevice.device_id,
|
||||||
|
{ is_silenced: true },
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue