Permissions popup context iso
This commit is contained in:
parent
f3715411c6
commit
7b5faa1cc1
49 changed files with 562 additions and 506 deletions
|
@ -1,51 +0,0 @@
|
|||
// Copyright 2018-2020 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
/* global $, i18n */
|
||||
|
||||
$(document).on('keydown', e => {
|
||||
if (e.keyCode === 27) {
|
||||
window.closePermissionsPopup();
|
||||
}
|
||||
});
|
||||
|
||||
const $body = $(document.body);
|
||||
|
||||
async function applyTheme() {
|
||||
const theme = await window.Settings.themeSetting.getValue();
|
||||
$body.removeClass('light-theme');
|
||||
$body.removeClass('dark-theme');
|
||||
$body.addClass(`${theme === 'system' ? window.systemTheme : theme}-theme`);
|
||||
}
|
||||
|
||||
applyTheme();
|
||||
|
||||
window.SignalContext.nativeThemeListener.subscribe(() => {
|
||||
applyTheme();
|
||||
});
|
||||
|
||||
let message;
|
||||
if (window.forCalling) {
|
||||
if (window.forCamera) {
|
||||
message = i18n('videoCallingPermissionNeeded');
|
||||
} else {
|
||||
message = i18n('audioCallingPermissionNeeded');
|
||||
}
|
||||
} else {
|
||||
message = i18n('audioPermissionNeeded');
|
||||
}
|
||||
|
||||
window.showConfirmationDialog({
|
||||
confirmStyle: 'affirmative',
|
||||
message,
|
||||
okText: i18n('allowAccess'),
|
||||
resolve: () => {
|
||||
if (!window.forCamera) {
|
||||
window.Settings.mediaPermissions.setValue(true);
|
||||
} else {
|
||||
window.Settings.mediaCameraPermissions.setValue(true);
|
||||
}
|
||||
window.closePermissionsPopup();
|
||||
},
|
||||
reject: window.closePermissionsPopup,
|
||||
});
|
10
main.js
10
main.js
|
@ -1103,9 +1103,15 @@ function showPermissionsPopupWindow(forCalling, forCamera) {
|
|||
...defaultWebPrefs,
|
||||
nodeIntegration: false,
|
||||
nodeIntegrationInWorker: false,
|
||||
contextIsolation: false,
|
||||
contextIsolation: true,
|
||||
enableRemoteModule: true,
|
||||
preload: path.join(__dirname, 'permissions_popup_preload.js'),
|
||||
preload: path.join(
|
||||
__dirname,
|
||||
'ts',
|
||||
'windows',
|
||||
'permissions',
|
||||
'preload.js'
|
||||
),
|
||||
nativeWindowOpen: true,
|
||||
},
|
||||
parent: mainWindow,
|
||||
|
|
|
@ -6,11 +6,7 @@
|
|||
<meta
|
||||
http-equiv="Content-Security-Policy"
|
||||
content="default-src 'none';
|
||||
child-src 'self';
|
||||
connect-src 'self' https: wss:;
|
||||
font-src 'self';
|
||||
form-action 'self';
|
||||
frame-src 'none';
|
||||
img-src 'self' blob: data:;
|
||||
media-src 'self' blob:;
|
||||
object-src 'none';
|
||||
|
@ -23,14 +19,13 @@
|
|||
type="text/css"
|
||||
/>
|
||||
<link href="stylesheets/manifest.css" rel="stylesheet" type="text/css" />
|
||||
<style></style>
|
||||
</head>
|
||||
<body class="permissions-popup"></body>
|
||||
<script type="text/javascript" src="js/components.js"></script>
|
||||
<script type="text/javascript" src="ts/backboneJquery.js"></script>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="ts/shims/showConfirmationDialog.js"
|
||||
></script>
|
||||
<script type="text/javascript" src="js/permissions_popup_start.js"></script>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script
|
||||
type="application/javascript"
|
||||
src="ts/windows/applyTheme.js"
|
||||
></script>
|
||||
<script type="application/javascript" src="ts/windows/init.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -1,57 +0,0 @@
|
|||
// Copyright 2018-2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
/* global window */
|
||||
|
||||
window.React = require('react');
|
||||
window.ReactDOM = require('react-dom');
|
||||
|
||||
const { ipcRenderer } = require('electron');
|
||||
const url = require('url');
|
||||
|
||||
// It is important to call this as early as possible
|
||||
require('./ts/windows/context');
|
||||
|
||||
const i18n = require('./js/modules/i18n');
|
||||
const { ConfirmationDialog } = require('./ts/components/ConfirmationDialog');
|
||||
const {
|
||||
getEnvironment,
|
||||
setEnvironment,
|
||||
parseEnvironment,
|
||||
} = require('./ts/environment');
|
||||
|
||||
const config = url.parse(window.location.toString(), true).query;
|
||||
const { locale } = config;
|
||||
const localeMessages = ipcRenderer.sendSync('locale-data');
|
||||
setEnvironment(parseEnvironment(config.environment));
|
||||
|
||||
const { createSetting } = require('./ts/util/preload');
|
||||
|
||||
window.getEnvironment = getEnvironment;
|
||||
window.getVersion = () => config.version;
|
||||
window.theme = config.theme;
|
||||
window.i18n = i18n.setup(locale, localeMessages);
|
||||
window.forCalling = config.forCalling === 'true';
|
||||
window.forCamera = config.forCamera === 'true';
|
||||
window.Signal = {
|
||||
Components: {
|
||||
ConfirmationDialog,
|
||||
},
|
||||
};
|
||||
|
||||
require('./ts/logging/set_up_renderer_logging').initialize();
|
||||
|
||||
window.closePermissionsPopup = () =>
|
||||
ipcRenderer.send('close-permissions-popup');
|
||||
|
||||
window.Backbone = require('backbone');
|
||||
|
||||
window.Settings = {
|
||||
mediaCameraPermissions: createSetting('mediaCameraPermissions', {
|
||||
getter: false,
|
||||
}),
|
||||
mediaPermissions: createSetting('mediaPermissions', {
|
||||
getter: false,
|
||||
}),
|
||||
themeSetting: createSetting('themeSetting', { setter: false }),
|
||||
};
|
35
stylesheets/components/PermissionsPopup.scss
Normal file
35
stylesheets/components/PermissionsPopup.scss
Normal file
|
@ -0,0 +1,35 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
.PermissionsPopup {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding: 16px;
|
||||
|
||||
@include light-theme() {
|
||||
background: $color-white;
|
||||
color: $color-gray-90;
|
||||
}
|
||||
|
||||
@include dark-theme() {
|
||||
background: $color-gray-95;
|
||||
color: $color-gray-05;
|
||||
}
|
||||
|
||||
&__body {
|
||||
@include font-body-1;
|
||||
}
|
||||
|
||||
&__buttons {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 12px;
|
||||
width: 100%;
|
||||
|
||||
button {
|
||||
margin-left: 16px;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -69,6 +69,7 @@
|
|||
@import './components/MessageAudio.scss';
|
||||
@import './components/MessageDetail.scss';
|
||||
@import './components/Modal.scss';
|
||||
@import './components/PermissionsPopup.scss';
|
||||
@import './components/Preferences.scss';
|
||||
@import './components/ProfileEditor.scss';
|
||||
@import './components/ReactionPickerPicker.scss';
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React, { useEffect } from 'react';
|
||||
import React from 'react';
|
||||
import { LocalizerType } from '../types/Util';
|
||||
import { useEscapeHandling } from '../hooks/useEscapeHandling';
|
||||
|
||||
export type PropsType = {
|
||||
closeAbout: () => unknown;
|
||||
|
@ -17,21 +18,7 @@ export const About = ({
|
|||
environment,
|
||||
version,
|
||||
}: PropsType): JSX.Element => {
|
||||
useEffect(() => {
|
||||
const handler = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Escape') {
|
||||
closeAbout();
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
};
|
||||
document.addEventListener('keydown', handler);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('keydown', handler);
|
||||
};
|
||||
}, [closeAbout]);
|
||||
useEscapeHandling(closeAbout);
|
||||
|
||||
return (
|
||||
<div className="About">
|
||||
|
|
|
@ -9,7 +9,7 @@ import { Inbox } from './Inbox';
|
|||
import { Install } from './Install';
|
||||
import { StandaloneRegistration } from './StandaloneRegistration';
|
||||
import { ThemeType } from '../types/Util';
|
||||
import { usePageVisibility } from '../util/hooks';
|
||||
import { usePageVisibility } from '../hooks/usePageVisibility';
|
||||
|
||||
type PropsType = {
|
||||
appView: AppViewType;
|
||||
|
|
|
@ -5,7 +5,7 @@ import * as React from 'react';
|
|||
import classNames from 'classnames';
|
||||
|
||||
import { Avatar, Props as AvatarProps } from './Avatar';
|
||||
import { useRestoreFocus } from '../util/hooks/useRestoreFocus';
|
||||
import { useRestoreFocus } from '../hooks/useRestoreFocus';
|
||||
|
||||
import { LocalizerType } from '../types/Util';
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ import {
|
|||
import { AvatarColors } from '../types/Colors';
|
||||
import { SetRendererCanvasType } from '../state/ducks/calling';
|
||||
import { useGetCallingFrameBuffer } from '../calling/useGetCallingFrameBuffer';
|
||||
import { usePageVisibility } from '../util/hooks';
|
||||
import { usePageVisibility } from '../hooks/usePageVisibility';
|
||||
import { missingCaseError } from '../util/missingCaseError';
|
||||
import { nonRenderedRemoteParticipant } from '../util/ringrtc/nonRenderedRemoteParticipant';
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import React, {
|
|||
ReactNode,
|
||||
} from 'react';
|
||||
|
||||
import { usePrevious } from '../util/hooks';
|
||||
import { usePrevious } from '../hooks/usePrevious';
|
||||
import { scrollToBottom } from '../util/scrollToBottom';
|
||||
|
||||
type PropsType = {
|
||||
|
|
|
@ -22,7 +22,7 @@ import { Avatar, AvatarSize } from './Avatar';
|
|||
import { ConfirmationDialog } from './ConfirmationDialog';
|
||||
import { Intl } from './Intl';
|
||||
import { ContactName } from './conversation/ContactName';
|
||||
import { useIntersectionObserver } from '../util/hooks';
|
||||
import { useIntersectionObserver } from '../hooks/useIntersectionObserver';
|
||||
import { MAX_FRAME_SIZE } from '../calling/constants';
|
||||
|
||||
const MAX_TIME_TO_SHOW_STALE_VIDEO_FRAMES = 5000;
|
||||
|
|
|
@ -16,7 +16,7 @@ import {
|
|||
} from '../types/Calling';
|
||||
import { useGetCallingFrameBuffer } from '../calling/useGetCallingFrameBuffer';
|
||||
import { LocalizerType } from '../types/Util';
|
||||
import { usePageVisibility } from '../util/hooks';
|
||||
import { usePageVisibility } from '../hooks/usePageVisibility';
|
||||
import { nonRenderedRemoteParticipant } from '../util/ringrtc/nonRenderedRemoteParticipant';
|
||||
import * as log from '../logging/log';
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@ import {
|
|||
|
||||
import * as OS from '../OS';
|
||||
import { LocalizerType, ScrollBehavior } from '../types/Util';
|
||||
import { usePrevious } from '../util/hooks';
|
||||
import { usePrevious } from '../hooks/usePrevious';
|
||||
import { missingCaseError } from '../util/missingCaseError';
|
||||
|
||||
import { ConversationList } from './ConversationList';
|
||||
|
|
|
@ -21,7 +21,7 @@ import { IMAGE_PNG, isImage, isVideo } from '../types/MIME';
|
|||
import { LocalizerType } from '../types/Util';
|
||||
import { MediaItemType, MessageAttributesType } from '../types/MediaItem';
|
||||
import { formatDuration } from '../util/formatDuration';
|
||||
import { useRestoreFocus } from '../util/hooks/useRestoreFocus';
|
||||
import { useRestoreFocus } from '../hooks/useRestoreFocus';
|
||||
import * as log from '../logging/log';
|
||||
|
||||
export type PropsType = {
|
||||
|
|
|
@ -10,7 +10,7 @@ import { LocalizerType } from '../types/Util';
|
|||
import { ModalHost } from './ModalHost';
|
||||
import { Theme } from '../util/theme';
|
||||
import { getClassNamesFor } from '../util/getClassNamesFor';
|
||||
import { useHasWrapped } from '../util/hooks';
|
||||
import { useHasWrapped } from '../hooks/useHasWrapped';
|
||||
|
||||
type PropsType = {
|
||||
children: ReactNode;
|
||||
|
|
|
@ -5,6 +5,7 @@ import React, { useEffect } from 'react';
|
|||
import classNames from 'classnames';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { Theme, themeClassName } from '../util/theme';
|
||||
import { useEscapeHandling } from '../hooks/useEscapeHandling';
|
||||
|
||||
export type PropsType = {
|
||||
readonly noMouseClose?: boolean;
|
||||
|
@ -30,25 +31,7 @@ export const ModalHost = React.memo(
|
|||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const handler = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Escape') {
|
||||
if (onEscape) {
|
||||
onEscape();
|
||||
} else {
|
||||
onClose();
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
};
|
||||
document.addEventListener('keydown', handler);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('keydown', handler);
|
||||
};
|
||||
}, [onEscape, onClose]);
|
||||
useEscapeHandling(onEscape || onClose);
|
||||
|
||||
// This makes it easier to write dialogs to be hosted here; they won't have to worry
|
||||
// as much about preventing propagation of mouse events.
|
||||
|
|
51
ts/components/PermissionsPopup.tsx
Normal file
51
ts/components/PermissionsPopup.tsx
Normal file
|
@ -0,0 +1,51 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React from 'react';
|
||||
import { Button, ButtonVariant } from './Button';
|
||||
import { LocalizerType } from '../types/Util';
|
||||
import { useEscapeHandling } from '../hooks/useEscapeHandling';
|
||||
|
||||
export type PropsType = {
|
||||
i18n: LocalizerType;
|
||||
message: string;
|
||||
onAccept: () => unknown;
|
||||
onClose: () => unknown;
|
||||
};
|
||||
|
||||
function focusRef(el: HTMLElement | null) {
|
||||
if (el) {
|
||||
el.focus();
|
||||
}
|
||||
}
|
||||
|
||||
export const PermissionsPopup = ({
|
||||
i18n,
|
||||
message,
|
||||
onAccept,
|
||||
onClose,
|
||||
}: PropsType): JSX.Element => {
|
||||
useEscapeHandling(onClose);
|
||||
|
||||
return (
|
||||
<div className="PermissionsPopup">
|
||||
<div className="PermissionsPopup__body">{message}</div>
|
||||
<div className="PermissionsPopup__buttons">
|
||||
<Button
|
||||
onClick={onClose}
|
||||
ref={focusRef}
|
||||
variant={ButtonVariant.Secondary}
|
||||
>
|
||||
{i18n('confirmation-dialog--Cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={onAccept}
|
||||
ref={focusRef}
|
||||
variant={ButtonVariant.Primary}
|
||||
>
|
||||
{i18n('allowAccess')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -33,6 +33,7 @@ import {
|
|||
DEFAULT_DURATIONS_SET,
|
||||
format as formatExpirationTimer,
|
||||
} from '../util/expirationTimer';
|
||||
import { useEscapeHandling } from '../hooks/useEscapeHandling';
|
||||
|
||||
type CheckboxChangeHandlerType = (value: boolean) => unknown;
|
||||
type SelectChangeHandlerType<T = string | number> = (value: T) => unknown;
|
||||
|
@ -280,21 +281,7 @@ export const Preferences = ({
|
|||
doneRendering();
|
||||
}, [doneRendering]);
|
||||
|
||||
useEffect(() => {
|
||||
const handler = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Escape') {
|
||||
closeSettings();
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
};
|
||||
document.addEventListener('keydown', handler);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('keydown', handler);
|
||||
};
|
||||
}, [closeSettings]);
|
||||
useEscapeHandling(closeSettings);
|
||||
|
||||
const onZoomSelectChange = useCallback(
|
||||
(value: string) => {
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
import * as React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { useRestoreFocus } from '../util/hooks/useRestoreFocus';
|
||||
import { useRestoreFocus } from '../hooks/useRestoreFocus';
|
||||
import { LocalizerType } from '../types/Util';
|
||||
|
||||
export type Props = {
|
||||
|
|
|
@ -15,7 +15,7 @@ import {
|
|||
getCallingIcon,
|
||||
getCallingNotificationText,
|
||||
} from '../../util/callingNotification';
|
||||
import { usePrevious } from '../../util/hooks';
|
||||
import { usePrevious } from '../../hooks/usePrevious';
|
||||
import { missingCaseError } from '../../util/missingCaseError';
|
||||
import { Tooltip, TooltipPlacement } from '../Tooltip';
|
||||
import type { TimelineItemType } from './TimelineItem';
|
||||
|
|
|
@ -6,7 +6,7 @@ import classNames from 'classnames';
|
|||
|
||||
import { Modal } from '../Modal';
|
||||
|
||||
import { useRestoreFocus } from '../../util/hooks/useRestoreFocus';
|
||||
import { useRestoreFocus } from '../../hooks/useRestoreFocus';
|
||||
|
||||
import { LocalizerType } from '../../types/Util';
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import { Modal } from '../Modal';
|
|||
import { Intl } from '../Intl';
|
||||
import { Emojify } from './Emojify';
|
||||
|
||||
import { useRestoreFocus } from '../../util/hooks/useRestoreFocus';
|
||||
import { useRestoreFocus } from '../../hooks/useRestoreFocus';
|
||||
|
||||
import { LocalizerType } from '../../types/Util';
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
import * as React from 'react';
|
||||
import { convertShortName } from '../emoji/lib';
|
||||
import { Props as EmojiPickerProps } from '../emoji/EmojiPicker';
|
||||
import { useRestoreFocus } from '../../util/hooks/useRestoreFocus';
|
||||
import { useRestoreFocus } from '../../hooks/useRestoreFocus';
|
||||
import { LocalizerType } from '../../types/Util';
|
||||
import { canCustomizePreferredReactions } from '../../util/canCustomizePreferredReactions';
|
||||
import {
|
||||
|
|
|
@ -7,9 +7,10 @@ import classNames from 'classnames';
|
|||
import { ContactName } from './ContactName';
|
||||
import { Avatar, Props as AvatarProps } from '../Avatar';
|
||||
import { Emoji } from '../emoji/Emoji';
|
||||
import { useRestoreFocus } from '../../util/hooks/useRestoreFocus';
|
||||
import { useRestoreFocus } from '../../hooks/useRestoreFocus';
|
||||
import { ConversationType } from '../../state/ducks/conversations';
|
||||
import { emojiToData, EmojiData } from '../emoji/lib';
|
||||
import { useEscapeHandling } from '../../hooks/useEscapeHandling';
|
||||
|
||||
export type Reaction = {
|
||||
emoji: string;
|
||||
|
@ -124,20 +125,9 @@ export const ReactionViewer = React.forwardRef<HTMLDivElement, Props>(
|
|||
selectedReactionCategory,
|
||||
setSelectedReactionCategory,
|
||||
] = React.useState(pickedReaction || 'all');
|
||||
|
||||
// Handle escape key
|
||||
React.useEffect(() => {
|
||||
const handler = (e: KeyboardEvent) => {
|
||||
if (onClose && e.key === 'Escape') {
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('keydown', handler);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('keydown', handler);
|
||||
};
|
||||
}, [onClose]);
|
||||
useEscapeHandling(onClose);
|
||||
|
||||
// Focus first button and restore focus on unmount
|
||||
const [focusRef] = useRestoreFocus();
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
import * as React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { useRestoreFocus } from '../../util/hooks/useRestoreFocus';
|
||||
import { useRestoreFocus } from '../../hooks/useRestoreFocus';
|
||||
import { StickerPackType, StickerType } from '../../state/ducks/stickers';
|
||||
import { LocalizerType } from '../../types/Util';
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import { ConfirmationDialog } from '../ConfirmationDialog';
|
|||
import { LocalizerType } from '../../types/Util';
|
||||
import { StickerPackType } from '../../state/ducks/stickers';
|
||||
import { Spinner } from '../Spinner';
|
||||
import { useRestoreFocus } from '../../util/hooks/useRestoreFocus';
|
||||
import { useRestoreFocus } from '../../hooks/useRestoreFocus';
|
||||
|
||||
export type OwnProps = {
|
||||
readonly onClose: () => unknown;
|
||||
|
|
|
@ -1,51 +0,0 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
/* eslint-disable no-restricted-syntax */
|
||||
|
||||
import { NativeThemeState } from '../types/NativeThemeNotifier.d';
|
||||
|
||||
export type Callback = (change: NativeThemeState) => void;
|
||||
|
||||
export interface MinimalIPC {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
sendSync(channel: string): any;
|
||||
|
||||
on(
|
||||
channel: string,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
listener: (event: unknown, ...args: ReadonlyArray<any>) => void
|
||||
): this;
|
||||
}
|
||||
|
||||
export type SystemThemeHolder = { systemTheme: 'dark' | 'light' };
|
||||
|
||||
export class NativeThemeListener {
|
||||
private readonly subscribers = new Array<Callback>();
|
||||
|
||||
public theme: NativeThemeState;
|
||||
|
||||
constructor(ipc: MinimalIPC, private readonly holder: SystemThemeHolder) {
|
||||
this.theme = ipc.sendSync('native-theme:init');
|
||||
this.update();
|
||||
|
||||
ipc.on(
|
||||
'native-theme:changed',
|
||||
(_event: unknown, change: NativeThemeState) => {
|
||||
this.theme = change;
|
||||
this.update();
|
||||
|
||||
for (const fn of this.subscribers) {
|
||||
fn(change);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public subscribe(fn: Callback): void {
|
||||
this.subscribers.push(fn);
|
||||
}
|
||||
|
||||
private update(): void {
|
||||
this.holder.systemTheme = this.theme.shouldUseDarkColors ? 'dark' : 'light';
|
||||
}
|
||||
}
|
69
ts/context/createNativeThemeListener.ts
Normal file
69
ts/context/createNativeThemeListener.ts
Normal file
|
@ -0,0 +1,69 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
/* eslint-disable no-restricted-syntax */
|
||||
|
||||
import { NativeThemeState } from '../types/NativeThemeNotifier.d';
|
||||
|
||||
export type Callback = (change: NativeThemeState) => void;
|
||||
|
||||
export interface MinimalIPC {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
sendSync(channel: string): any;
|
||||
|
||||
on(
|
||||
channel: string,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
listener: (event: unknown, ...args: ReadonlyArray<any>) => void
|
||||
): this;
|
||||
}
|
||||
|
||||
type SystemThemeType = 'dark' | 'light';
|
||||
|
||||
export type SystemThemeHolder = { systemTheme: SystemThemeType };
|
||||
|
||||
type NativeThemeType = {
|
||||
getSystemTheme: () => SystemThemeType;
|
||||
subscribe: (fn: Callback) => void;
|
||||
update: () => SystemThemeType;
|
||||
};
|
||||
|
||||
export function createNativeThemeListener(
|
||||
ipc: MinimalIPC,
|
||||
holder: SystemThemeHolder
|
||||
): NativeThemeType {
|
||||
const subscribers = new Array<Callback>();
|
||||
|
||||
let theme = ipc.sendSync('native-theme:init');
|
||||
let systemTheme: SystemThemeType;
|
||||
|
||||
function update(): SystemThemeType {
|
||||
const nextSystemTheme = theme.shouldUseDarkColors ? 'dark' : 'light';
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
holder.systemTheme = nextSystemTheme;
|
||||
return nextSystemTheme;
|
||||
}
|
||||
|
||||
function subscribe(fn: Callback): void {
|
||||
subscribers.push(fn);
|
||||
}
|
||||
|
||||
ipc.on(
|
||||
'native-theme:changed',
|
||||
(_event: unknown, change: NativeThemeState) => {
|
||||
theme = change;
|
||||
systemTheme = update();
|
||||
|
||||
for (const fn of subscribers) {
|
||||
fn(change);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
systemTheme = update();
|
||||
|
||||
return {
|
||||
getSystemTheme: () => systemTheme,
|
||||
subscribe,
|
||||
update,
|
||||
};
|
||||
}
|
|
@ -2,7 +2,10 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { Bytes } from './Bytes';
|
||||
import { NativeThemeListener, MinimalIPC } from './NativeThemeListener';
|
||||
import {
|
||||
createNativeThemeListener,
|
||||
MinimalIPC,
|
||||
} from './createNativeThemeListener';
|
||||
|
||||
export class Context {
|
||||
public readonly bytes = new Bytes();
|
||||
|
@ -10,6 +13,6 @@ export class Context {
|
|||
public readonly nativeThemeListener;
|
||||
|
||||
constructor(ipc: MinimalIPC) {
|
||||
this.nativeThemeListener = new NativeThemeListener(ipc, window);
|
||||
this.nativeThemeListener = createNativeThemeListener(ipc, window);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { useEffect } from 'react';
|
||||
import { usePrevious } from '../util/hooks';
|
||||
import { usePrevious } from './usePrevious';
|
||||
|
||||
type RemoteParticipant = {
|
||||
hasRemoteVideo: boolean;
|
||||
|
|
16
ts/hooks/useBoundActions.ts
Normal file
16
ts/hooks/useBoundActions.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
// Copyright 2020-2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { ActionCreatorsMapObject, bindActionCreators } from 'redux';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
export const useBoundActions = <T extends ActionCreatorsMapObject>(
|
||||
actions: T
|
||||
): T => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
return useMemo(() => {
|
||||
return bindActionCreators(actions, dispatch);
|
||||
}, [actions, dispatch]);
|
||||
};
|
26
ts/hooks/useEscapeHandling.ts
Normal file
26
ts/hooks/useEscapeHandling.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export function useEscapeHandling(handleEscape?: () => unknown): void {
|
||||
useEffect(() => {
|
||||
if (!handleEscape) {
|
||||
return;
|
||||
}
|
||||
|
||||
const handler = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Escape') {
|
||||
handleEscape();
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
};
|
||||
document.addEventListener('keydown', handler);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('keydown', handler);
|
||||
};
|
||||
}, [handleEscape]);
|
||||
}
|
56
ts/hooks/useHasWrapped.ts
Normal file
56
ts/hooks/useHasWrapped.ts
Normal file
|
@ -0,0 +1,56 @@
|
|||
// Copyright 2020-2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { Ref, useEffect, useState } from 'react';
|
||||
import { first, last, noop } from 'lodash';
|
||||
|
||||
function getTop(element: Readonly<Element>): number {
|
||||
return element.getBoundingClientRect().top;
|
||||
}
|
||||
|
||||
function isWrapped(element: Readonly<null | HTMLElement>): boolean {
|
||||
if (!element) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { children } = element;
|
||||
const firstChild = first(children);
|
||||
const lastChild = last(children);
|
||||
|
||||
return Boolean(
|
||||
firstChild &&
|
||||
lastChild &&
|
||||
firstChild !== lastChild &&
|
||||
getTop(firstChild) !== getTop(lastChild)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* A hook that returns a ref (to put on your element) and a boolean. The boolean will be
|
||||
* `true` if the element's children have different `top`s, and `false` otherwise.
|
||||
*/
|
||||
export function useHasWrapped<T extends HTMLElement>(): [Ref<T>, boolean] {
|
||||
const [element, setElement] = useState<null | T>(null);
|
||||
|
||||
const [hasWrapped, setHasWrapped] = useState(isWrapped(element));
|
||||
|
||||
useEffect(() => {
|
||||
if (!element) {
|
||||
return noop;
|
||||
}
|
||||
|
||||
// We can remove this `any` when we upgrade to TypeScript 4.2+, which adds
|
||||
// `ResizeObserver` type definitions.
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const observer = new (window as any).ResizeObserver(() => {
|
||||
setHasWrapped(isWrapped(element));
|
||||
});
|
||||
observer.observe(element);
|
||||
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
};
|
||||
}, [element]);
|
||||
|
||||
return [setElement, hasWrapped];
|
||||
}
|
64
ts/hooks/useIntersectionObserver.ts
Normal file
64
ts/hooks/useIntersectionObserver.ts
Normal file
|
@ -0,0 +1,64 @@
|
|||
// Copyright 2020-2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import * as log from '../logging/log';
|
||||
|
||||
/**
|
||||
* A light hook wrapper around `IntersectionObserver`.
|
||||
*
|
||||
* Example usage:
|
||||
*
|
||||
* function MyComponent() {
|
||||
* const [intersectionRef, intersectionEntry] = useIntersectionObserver();
|
||||
* const isVisible = intersectionEntry
|
||||
* ? intersectionEntry.isIntersecting
|
||||
* : true;
|
||||
*
|
||||
* return (
|
||||
* <div ref={intersectionRef}>
|
||||
* I am {isVisible ? 'on the screen' : 'invisible'}
|
||||
* </div>
|
||||
* );
|
||||
* }
|
||||
*/
|
||||
export function useIntersectionObserver(): [
|
||||
(el?: Element | null) => void,
|
||||
IntersectionObserverEntry | null
|
||||
] {
|
||||
const [
|
||||
intersectionObserverEntry,
|
||||
setIntersectionObserverEntry,
|
||||
] = useState<IntersectionObserverEntry | null>(null);
|
||||
|
||||
const unobserveRef = useRef<(() => unknown) | null>(null);
|
||||
|
||||
const setRef = useCallback((el?: Element | null) => {
|
||||
if (unobserveRef.current) {
|
||||
unobserveRef.current();
|
||||
unobserveRef.current = null;
|
||||
}
|
||||
|
||||
if (!el) {
|
||||
return;
|
||||
}
|
||||
|
||||
const observer = new IntersectionObserver(entries => {
|
||||
if (entries.length !== 1) {
|
||||
log.error(
|
||||
'IntersectionObserverWrapper was observing the wrong number of elements'
|
||||
);
|
||||
return;
|
||||
}
|
||||
entries.forEach(entry => {
|
||||
setIntersectionObserverEntry(entry);
|
||||
});
|
||||
});
|
||||
|
||||
unobserveRef.current = observer.unobserve.bind(observer, el);
|
||||
|
||||
observer.observe(el);
|
||||
}, []);
|
||||
|
||||
return [setRef, intersectionObserverEntry];
|
||||
}
|
26
ts/hooks/usePageVisibility.ts
Normal file
26
ts/hooks/usePageVisibility.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
// Copyright 2020-2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
export function usePageVisibility(): boolean {
|
||||
const [result, setResult] = useState(!document.hidden);
|
||||
|
||||
useEffect(() => {
|
||||
const onVisibilityChange = () => {
|
||||
setResult(!document.hidden);
|
||||
};
|
||||
|
||||
document.addEventListener('visibilitychange', onVisibilityChange, false);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener(
|
||||
'visibilitychange',
|
||||
onVisibilityChange,
|
||||
false
|
||||
);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return result;
|
||||
}
|
11
ts/hooks/usePrevious.ts
Normal file
11
ts/hooks/usePrevious.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
// Copyright 2020-2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { useRef } from 'react';
|
||||
|
||||
export function usePrevious<T>(initialValue: T, currentValue: T): T {
|
||||
const previousValueRef = useRef<T>(initialValue);
|
||||
const result = previousValueRef.current;
|
||||
previousValueRef.current = currentValue;
|
||||
return result;
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { useBoundActions } from '../../util/hooks';
|
||||
import { useBoundActions } from '../../hooks/useBoundActions';
|
||||
|
||||
import {
|
||||
SwitchToAssociatedViewActionType,
|
||||
|
|
|
@ -5,7 +5,7 @@ import { take, uniq } from 'lodash';
|
|||
import { ThunkAction } from 'redux-thunk';
|
||||
import { EmojiPickDataType } from '../../components/emoji/EmojiPicker';
|
||||
import dataInterface from '../../sql/Client';
|
||||
import { useBoundActions } from '../../util/hooks';
|
||||
import { useBoundActions } from '../../hooks/useBoundActions';
|
||||
|
||||
const { updateEmojiUsage } = dataInterface;
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ import { v4 as getGuid } from 'uuid';
|
|||
import { ThunkAction } from 'redux-thunk';
|
||||
import { StateType as RootStateType } from '../reducer';
|
||||
import * as storageShim from '../../shims/storage';
|
||||
import { useBoundActions } from '../../util/hooks';
|
||||
import { useBoundActions } from '../../hooks/useBoundActions';
|
||||
import {
|
||||
ConversationColors,
|
||||
ConversationColorType,
|
||||
|
|
|
@ -6,7 +6,7 @@ import { omit } from 'lodash';
|
|||
import * as log from '../../logging/log';
|
||||
import * as Errors from '../../types/errors';
|
||||
import { replaceIndex } from '../../util/replaceIndex';
|
||||
import { useBoundActions } from '../../util/hooks';
|
||||
import { useBoundActions } from '../../hooks/useBoundActions';
|
||||
import type { StateType as RootStateType } from '../reducer';
|
||||
import { DEFAULT_PREFERRED_REACTION_EMOJI_SHORT_NAMES } from '../../reactions/constants';
|
||||
import { getPreferredReactionEmoji } from '../../reactions/preferredReactionEmoji';
|
||||
|
|
|
@ -5,10 +5,10 @@ import { assert } from 'chai';
|
|||
import { EventEmitter } from 'events';
|
||||
|
||||
import {
|
||||
NativeThemeListener,
|
||||
createNativeThemeListener,
|
||||
MinimalIPC,
|
||||
SystemThemeHolder,
|
||||
} from '../../context/NativeThemeListener';
|
||||
} from '../../context/createNativeThemeListener';
|
||||
import { NativeThemeState } from '../../types/NativeThemeNotifier.d';
|
||||
|
||||
class FakeIPC extends EventEmitter implements MinimalIPC {
|
||||
|
@ -26,7 +26,7 @@ describe('NativeThemeListener', () => {
|
|||
const holder: SystemThemeHolder = { systemTheme: 'dark' };
|
||||
|
||||
it('syncs the initial native theme', () => {
|
||||
const dark = new NativeThemeListener(
|
||||
const dark = createNativeThemeListener(
|
||||
new FakeIPC({
|
||||
shouldUseDarkColors: true,
|
||||
}),
|
||||
|
@ -34,9 +34,9 @@ describe('NativeThemeListener', () => {
|
|||
);
|
||||
|
||||
assert.strictEqual(holder.systemTheme, 'dark');
|
||||
assert.isTrue(dark.theme.shouldUseDarkColors);
|
||||
assert.strictEqual(dark.getSystemTheme(), 'dark');
|
||||
|
||||
const light = new NativeThemeListener(
|
||||
const light = createNativeThemeListener(
|
||||
new FakeIPC({
|
||||
shouldUseDarkColors: false,
|
||||
}),
|
||||
|
@ -44,7 +44,7 @@ describe('NativeThemeListener', () => {
|
|||
);
|
||||
|
||||
assert.strictEqual(holder.systemTheme, 'light');
|
||||
assert.isFalse(light.theme.shouldUseDarkColors);
|
||||
assert.strictEqual(light.getSystemTheme(), 'light');
|
||||
});
|
||||
|
||||
it('should react to native theme changes', () => {
|
||||
|
@ -52,14 +52,14 @@ describe('NativeThemeListener', () => {
|
|||
shouldUseDarkColors: true,
|
||||
});
|
||||
|
||||
const listener = new NativeThemeListener(ipc, holder);
|
||||
const listener = createNativeThemeListener(ipc, holder);
|
||||
|
||||
ipc.emit('native-theme:changed', null, <NativeThemeState>{
|
||||
shouldUseDarkColors: false,
|
||||
});
|
||||
|
||||
assert.strictEqual(holder.systemTheme, 'light');
|
||||
assert.isFalse(listener.theme.shouldUseDarkColors);
|
||||
assert.strictEqual(listener.getSystemTheme(), 'light');
|
||||
});
|
||||
|
||||
it('should notify subscribers of native theme changes', done => {
|
||||
|
@ -67,7 +67,7 @@ describe('NativeThemeListener', () => {
|
|||
shouldUseDarkColors: true,
|
||||
});
|
||||
|
||||
const listener = new NativeThemeListener(ipc, holder);
|
||||
const listener = createNativeThemeListener(ipc, holder);
|
||||
|
||||
listener.subscribe(state => {
|
||||
assert.isFalse(state.shouldUseDarkColors);
|
|
@ -1,160 +0,0 @@
|
|||
// Copyright 2020-2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import * as React from 'react';
|
||||
import { ActionCreatorsMapObject, bindActionCreators } from 'redux';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { first, last, noop } from 'lodash';
|
||||
import * as log from '../../logging/log';
|
||||
|
||||
export function usePrevious<T>(initialValue: T, currentValue: T): T {
|
||||
const previousValueRef = React.useRef<T>(initialValue);
|
||||
const result = previousValueRef.current;
|
||||
previousValueRef.current = currentValue;
|
||||
return result;
|
||||
}
|
||||
|
||||
export const useBoundActions = <T extends ActionCreatorsMapObject>(
|
||||
actions: T
|
||||
): T => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
return React.useMemo(() => {
|
||||
return bindActionCreators(actions, dispatch);
|
||||
}, [actions, dispatch]);
|
||||
};
|
||||
|
||||
export const usePageVisibility = (): boolean => {
|
||||
const [result, setResult] = React.useState(!document.hidden);
|
||||
|
||||
React.useEffect(() => {
|
||||
const onVisibilityChange = () => {
|
||||
setResult(!document.hidden);
|
||||
};
|
||||
|
||||
document.addEventListener('visibilitychange', onVisibilityChange, false);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener(
|
||||
'visibilitychange',
|
||||
onVisibilityChange,
|
||||
false
|
||||
);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* A light hook wrapper around `IntersectionObserver`.
|
||||
*
|
||||
* Example usage:
|
||||
*
|
||||
* function MyComponent() {
|
||||
* const [intersectionRef, intersectionEntry] = useIntersectionObserver();
|
||||
* const isVisible = intersectionEntry
|
||||
* ? intersectionEntry.isIntersecting
|
||||
* : true;
|
||||
*
|
||||
* return (
|
||||
* <div ref={intersectionRef}>
|
||||
* I am {isVisible ? 'on the screen' : 'invisible'}
|
||||
* </div>
|
||||
* );
|
||||
* }
|
||||
*/
|
||||
export function useIntersectionObserver(): [
|
||||
(el?: Element | null) => void,
|
||||
IntersectionObserverEntry | null
|
||||
] {
|
||||
const [
|
||||
intersectionObserverEntry,
|
||||
setIntersectionObserverEntry,
|
||||
] = React.useState<IntersectionObserverEntry | null>(null);
|
||||
|
||||
const unobserveRef = React.useRef<(() => unknown) | null>(null);
|
||||
|
||||
const setRef = React.useCallback((el?: Element | null) => {
|
||||
if (unobserveRef.current) {
|
||||
unobserveRef.current();
|
||||
unobserveRef.current = null;
|
||||
}
|
||||
|
||||
if (!el) {
|
||||
return;
|
||||
}
|
||||
|
||||
const observer = new IntersectionObserver(entries => {
|
||||
if (entries.length !== 1) {
|
||||
log.error(
|
||||
'IntersectionObserverWrapper was observing the wrong number of elements'
|
||||
);
|
||||
return;
|
||||
}
|
||||
entries.forEach(entry => {
|
||||
setIntersectionObserverEntry(entry);
|
||||
});
|
||||
});
|
||||
|
||||
unobserveRef.current = observer.unobserve.bind(observer, el);
|
||||
|
||||
observer.observe(el);
|
||||
}, []);
|
||||
|
||||
return [setRef, intersectionObserverEntry];
|
||||
}
|
||||
|
||||
function getTop(element: Readonly<Element>): number {
|
||||
return element.getBoundingClientRect().top;
|
||||
}
|
||||
|
||||
function isWrapped(element: Readonly<null | HTMLElement>): boolean {
|
||||
if (!element) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { children } = element;
|
||||
const firstChild = first(children);
|
||||
const lastChild = last(children);
|
||||
|
||||
return Boolean(
|
||||
firstChild &&
|
||||
lastChild &&
|
||||
firstChild !== lastChild &&
|
||||
getTop(firstChild) !== getTop(lastChild)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* A hook that returns a ref (to put on your element) and a boolean. The boolean will be
|
||||
* `true` if the element's children have different `top`s, and `false` otherwise.
|
||||
*/
|
||||
export function useHasWrapped<T extends HTMLElement>(): [
|
||||
React.Ref<T>,
|
||||
boolean
|
||||
] {
|
||||
const [element, setElement] = React.useState<null | T>(null);
|
||||
|
||||
const [hasWrapped, setHasWrapped] = React.useState(isWrapped(element));
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!element) {
|
||||
return noop;
|
||||
}
|
||||
|
||||
// We can remove this `any` when we upgrade to TypeScript 4.2+, which adds
|
||||
// `ResizeObserver` type definitions.
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const observer = new (window as any).ResizeObserver(() => {
|
||||
setHasWrapped(isWrapped(element));
|
||||
});
|
||||
observer.observe(element);
|
||||
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
};
|
||||
}, [element]);
|
||||
|
||||
return [setElement, hasWrapped];
|
||||
}
|
|
@ -144,22 +144,6 @@
|
|||
"reasonCategory": "falseMatch",
|
||||
"updated": "2020-07-21T18:34:59.251Z"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-$(",
|
||||
"path": "js/permissions_popup_start.js",
|
||||
"line": "$(document).on('keydown', e => {",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2018-09-19T21:59:32.770Z",
|
||||
"reasonDetail": "Protected from arbitrary input"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-$(",
|
||||
"path": "js/permissions_popup_start.js",
|
||||
"line": "const $body = $(document.body);",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2018-09-19T21:59:32.770Z",
|
||||
"reasonDetail": "Protected from arbitrary input"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-$(",
|
||||
"path": "js/views/key_verification_view.js",
|
||||
|
@ -12955,6 +12939,49 @@
|
|||
"updated": "2019-11-21T06:13:49.384Z",
|
||||
"reasonDetail": "Used for setting focus only"
|
||||
},
|
||||
{
|
||||
"rule": "React-useRef",
|
||||
"path": "ts/hooks/useIntersectionObserver.ts",
|
||||
"line": " const unobserveRef = useRef<(() => unknown) | null>(null);",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2021-09-17T20:16:37.959Z"
|
||||
},
|
||||
{
|
||||
"rule": "React-useRef",
|
||||
"path": "ts/hooks/usePrevious.ts",
|
||||
"line": " const previousValueRef = useRef<T>(initialValue);",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2021-09-17T20:16:37.959Z"
|
||||
},
|
||||
{
|
||||
"rule": "React-useRef",
|
||||
"path": "ts/hooks/useRestoreFocus.js",
|
||||
"line": " const lastFocusedRef = React.useRef(null);",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2021-07-30T01:08:01.309Z",
|
||||
"reasonDetail": "Used to store the previous-focused item, again to set focus"
|
||||
},
|
||||
{
|
||||
"rule": "React-useRef",
|
||||
"path": "ts/hooks/useRestoreFocus.js",
|
||||
"line": " const toFocusRef = React.useRef(null);",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2021-09-17T17:37:46.279Z"
|
||||
},
|
||||
{
|
||||
"rule": "React-useRef",
|
||||
"path": "ts/hooks/useRestoreFocus.ts",
|
||||
"line": " const toFocusRef = React.useRef<HTMLElement | null>(null);",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2021-07-30T16:57:33.618Z"
|
||||
},
|
||||
{
|
||||
"rule": "React-useRef",
|
||||
"path": "ts/hooks/useRestoreFocus.ts",
|
||||
"line": " const lastFocusedRef = React.useRef<HTMLElement | null>(null);",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2021-07-30T16:57:33.618Z"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-append(",
|
||||
"path": "ts/logging/debuglogs.js",
|
||||
|
@ -13206,65 +13233,6 @@
|
|||
"updated": "2021-08-18T18:22:55.307Z",
|
||||
"reasonDetail": "Legacy code"
|
||||
},
|
||||
{
|
||||
"rule": "React-useRef",
|
||||
"path": "ts/util/hooks/index.js",
|
||||
"line": " const unobserveRef = React.useRef(null);",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2021-01-08T15:46:32.143Z",
|
||||
"reasonDetail": "Doesn't manipulate the DOM. This is just a function."
|
||||
},
|
||||
{
|
||||
"rule": "React-useRef",
|
||||
"path": "ts/util/hooks/index.js",
|
||||
"line": " const previousValueRef = React.useRef(initialValue);",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2021-03-18T21:41:28.361Z",
|
||||
"reasonDetail": "A generic hook. Typically not to be used with non-DOM values."
|
||||
},
|
||||
{
|
||||
"rule": "React-useRef",
|
||||
"path": "ts/util/hooks/index.ts",
|
||||
"line": " const previousValueRef = React.useRef<T>(initialValue);",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2021-07-30T16:57:33.618Z"
|
||||
},
|
||||
{
|
||||
"rule": "React-useRef",
|
||||
"path": "ts/util/hooks/index.ts",
|
||||
"line": " const unobserveRef = React.useRef<(() => unknown) | null>(null);",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2021-07-30T16:57:33.618Z"
|
||||
},
|
||||
{
|
||||
"rule": "React-useRef",
|
||||
"path": "ts/util/hooks/useRestoreFocus.js",
|
||||
"line": " const lastFocusedRef = React.useRef(null);",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2021-07-30T01:08:01.309Z",
|
||||
"reasonDetail": "Used to store the previous-focused item, again to set focus"
|
||||
},
|
||||
{
|
||||
"rule": "React-useRef",
|
||||
"path": "ts/util/hooks/useRestoreFocus.js",
|
||||
"line": " const toFocusRef = React.useRef(null);",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2021-09-17T17:37:46.279Z"
|
||||
},
|
||||
{
|
||||
"rule": "React-useRef",
|
||||
"path": "ts/util/hooks/useRestoreFocus.ts",
|
||||
"line": " const toFocusRef = React.useRef<HTMLElement | null>(null);",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2021-07-30T16:57:33.618Z"
|
||||
},
|
||||
{
|
||||
"rule": "React-useRef",
|
||||
"path": "ts/util/hooks/useRestoreFocus.ts",
|
||||
"line": " const lastFocusedRef = React.useRef<HTMLElement | null>(null);",
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2021-07-30T16:57:33.618Z"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-$(",
|
||||
"path": "ts/views/debug_log_view.js",
|
||||
|
|
7
ts/window.d.ts
vendored
7
ts/window.d.ts
vendored
|
@ -116,10 +116,11 @@ import { UUID } from './types/UUID';
|
|||
import { Address } from './types/Address';
|
||||
import { QualifiedAddress } from './types/QualifiedAddress';
|
||||
import { CI } from './CI';
|
||||
import { IPCEventsType } from './util/createIPCEvents';
|
||||
import { IPCEventsType, IPCEventsValuesType } from './util/createIPCEvents';
|
||||
import { ConversationView } from './views/conversation_view';
|
||||
import { DebugLogView } from './views/debug_log_view';
|
||||
import { LoggerType } from './types/Logging';
|
||||
import { SettingType } from './util/preload';
|
||||
|
||||
export { Long } from 'long';
|
||||
|
||||
|
@ -496,7 +497,11 @@ declare global {
|
|||
|
||||
// Context Isolation
|
||||
SignalWindow: {
|
||||
Settings: {
|
||||
themeSetting: SettingType<IPCEventsValuesType['themeSetting']>;
|
||||
};
|
||||
config: string;
|
||||
context: SignalContext;
|
||||
getAppInstance: () => string | undefined;
|
||||
getEnvironment: () => string;
|
||||
getVersion: () => string;
|
||||
|
|
21
ts/windows/applyTheme.ts
Normal file
21
ts/windows/applyTheme.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
async function applyTheme() {
|
||||
const theme = await window.SignalWindow.Settings.themeSetting.getValue();
|
||||
document.body.classList.remove('light-theme');
|
||||
document.body.classList.remove('dark-theme');
|
||||
document.body.classList.add(
|
||||
`${
|
||||
theme === 'system'
|
||||
? window.SignalWindow.context.nativeThemeListener.getSystemTheme()
|
||||
: theme
|
||||
}-theme`
|
||||
);
|
||||
}
|
||||
|
||||
applyTheme();
|
||||
|
||||
window.SignalWindow.context.nativeThemeListener.subscribe(() => {
|
||||
applyTheme();
|
||||
});
|
|
@ -11,6 +11,7 @@ import {
|
|||
setEnvironment,
|
||||
} from '../environment';
|
||||
import { strictAssert } from '../util/assert';
|
||||
import { createSetting } from '../util/preload';
|
||||
|
||||
const config = url.parse(window.location.toString(), true).query;
|
||||
const { locale } = config;
|
||||
|
@ -20,8 +21,14 @@ strictAssert(typeof locale === 'string', 'locale is not a string');
|
|||
const localeMessages = ipcRenderer.sendSync('locale-data');
|
||||
setEnvironment(parseEnvironment(config.environment));
|
||||
|
||||
strictAssert(Boolean(window.SignalContext), 'context must be defined');
|
||||
|
||||
export const SignalWindow = {
|
||||
Settings: {
|
||||
themeSetting: createSetting('themeSetting', { setter: false }),
|
||||
},
|
||||
config,
|
||||
context: window.SignalContext,
|
||||
getAppInstance: (): string | undefined =>
|
||||
config.appInstance ? String(config.appInstance) : undefined,
|
||||
getEnvironment,
|
||||
|
|
68
ts/windows/permissions/preload.ts
Normal file
68
ts/windows/permissions/preload.ts
Normal file
|
@ -0,0 +1,68 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { contextBridge, ipcRenderer } from 'electron';
|
||||
|
||||
// It is important to call this as early as possible
|
||||
import '../context';
|
||||
|
||||
import { createSetting } from '../../util/preload';
|
||||
import { SignalWindow } from '../configure';
|
||||
import { PermissionsPopup } from '../../components/PermissionsPopup';
|
||||
import { initialize as initializeLogging } from '../../logging/set_up_renderer_logging';
|
||||
|
||||
const mediaCameraPermissions = createSetting('mediaCameraPermissions', {
|
||||
getter: false,
|
||||
});
|
||||
const mediaPermissions = createSetting('mediaPermissions', {
|
||||
getter: false,
|
||||
});
|
||||
|
||||
contextBridge.exposeInMainWorld(
|
||||
'nativeThemeListener',
|
||||
window.SignalContext.nativeThemeListener
|
||||
);
|
||||
|
||||
contextBridge.exposeInMainWorld('SignalWindow', {
|
||||
...SignalWindow,
|
||||
renderWindow: () => {
|
||||
const forCalling = SignalWindow.config.forCalling === 'true';
|
||||
const forCamera = SignalWindow.config.forCamera === 'true';
|
||||
|
||||
let message;
|
||||
if (forCalling) {
|
||||
if (forCamera) {
|
||||
message = SignalWindow.i18n('videoCallingPermissionNeeded');
|
||||
} else {
|
||||
message = SignalWindow.i18n('audioCallingPermissionNeeded');
|
||||
}
|
||||
} else {
|
||||
message = SignalWindow.i18n('audioPermissionNeeded');
|
||||
}
|
||||
|
||||
function onClose() {
|
||||
ipcRenderer.send('close-permissions-popup');
|
||||
}
|
||||
|
||||
ReactDOM.render(
|
||||
React.createElement(PermissionsPopup, {
|
||||
i18n: SignalWindow.i18n,
|
||||
message,
|
||||
onAccept: () => {
|
||||
if (!forCamera) {
|
||||
mediaPermissions.setValue(true);
|
||||
} else {
|
||||
mediaCameraPermissions.setValue(true);
|
||||
}
|
||||
onClose();
|
||||
},
|
||||
onClose,
|
||||
}),
|
||||
document.getElementById('app')
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
initializeLogging();
|
Loading…
Add table
Reference in a new issue