Enables ContextIsolation

This commit is contained in:
Josh Perez 2023-01-12 19:24:59 -05:00 committed by GitHub
parent 4bbf5eb5d4
commit 9374832ea4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
83 changed files with 1009 additions and 1073 deletions

View File

@ -675,7 +675,7 @@ async function createWindow() {
nodeIntegration: false,
nodeIntegrationInWorker: false,
sandbox: false,
contextIsolation: false,
contextIsolation: !isTestEnvironment(getEnvironment()),
preload: join(
__dirname,
usePreloadBundle
@ -1138,7 +1138,7 @@ async function showScreenShareWindow(sourceName: string) {
...defaultWebPrefs,
nodeIntegration: false,
nodeIntegrationInWorker: false,
sandbox: false,
sandbox: true,
contextIsolation: true,
preload: join(__dirname, '../ts/windows/screenShare/preload.js'),
},
@ -1192,7 +1192,7 @@ async function showAbout() {
...defaultWebPrefs,
nodeIntegration: false,
nodeIntegrationInWorker: false,
sandbox: false,
sandbox: true,
contextIsolation: true,
preload: join(__dirname, '../ts/windows/about/preload.js'),
nativeWindowOpen: true,
@ -1240,7 +1240,7 @@ async function showSettingsWindow() {
...defaultWebPrefs,
nodeIntegration: false,
nodeIntegrationInWorker: false,
sandbox: false,
sandbox: true,
contextIsolation: true,
preload: join(__dirname, '../ts/windows/settings/preload.js'),
nativeWindowOpen: true,
@ -1379,7 +1379,7 @@ async function showDebugLogWindow() {
...defaultWebPrefs,
nodeIntegration: false,
nodeIntegrationInWorker: false,
sandbox: false,
sandbox: true,
contextIsolation: true,
preload: join(__dirname, '../ts/windows/debuglog/preload.js'),
nativeWindowOpen: true,
@ -1443,7 +1443,7 @@ function showPermissionsPopupWindow(forCalling: boolean, forCamera: boolean) {
...defaultWebPrefs,
nodeIntegration: false,
nodeIntegrationInWorker: false,
sandbox: false,
sandbox: true,
contextIsolation: true,
preload: join(__dirname, '../ts/windows/permissions/preload.js'),
nativeWindowOpen: true,
@ -1784,7 +1784,7 @@ app.on('ready', async () => {
webPreferences: {
...defaultWebPrefs,
nodeIntegration: false,
sandbox: false,
sandbox: true,
contextIsolation: true,
preload: join(__dirname, '../ts/windows/loading/preload.js'),
},

View File

@ -118,22 +118,6 @@
.addEventListener('dblclick', () => window.showDebugLog());
</script>
<script type="text/javascript" src="js/components.js"></script>
<script type="text/javascript" src="ts/set_os_class.js"></script>
<script
type="text/javascript"
src="ts/manage_full_screen_class.js"
></script>
<script
type="text/javascript"
src="ts/backbone/reliable_trigger.js"
></script>
<script
type="text/javascript"
src="ts/shims/showConfirmationDialog.js"
></script>
<!--
Note: this inline script cannot be changed without also changing the hash in
the CSP at the top of this file

View File

@ -1,199 +0,0 @@
(function(window) {
// internal: same as jQuery.extend(true, args...)
var extend = function() {
var target = arguments[0],
sources = [].slice.call(arguments, 1);
for (var i = 0; i < sources.length; ++i) {
var src = sources[i];
for (key in src) {
var val = src[key];
target[key] = typeof val === "object"
? extend(typeof target[key] === "object" ? target[key] : {}, val)
: val;
}
}
return target;
};
var WORKER_FILE = {
wav: "WebAudioRecorderWav.js",
ogg: "WebAudioRecorderOgg.js",
mp3: "WebAudioRecorderMp3.js"
};
// default configs
var CONFIGS = {
workerDir: "/", // worker scripts dir (end with /)
numChannels: 2, // number of channels
encoding: "wav", // encoding (can be changed at runtime)
// runtime options
options: {
timeLimit: 300, // recording time limit (sec)
encodeAfterRecord: false, // process encoding after recording
progressInterval: 1000, // encoding progress report interval (millisec)
bufferSize: undefined, // buffer size (use browser default)
// encoding-specific options
wav: {
mimeType: "audio/wav"
},
ogg: {
mimeType: "audio/ogg",
quality: 0.5 // (VBR only): quality = [-0.1 .. 1]
},
mp3: {
mimeType: "audio/mpeg",
bitRate: 160 // (CBR only): bit rate = [64 .. 320]
}
}
};
// constructor
var WebAudioRecorder = function(sourceNode, configs) {
extend(this, CONFIGS, configs || {});
this.context = sourceNode.context;
if (this.context.createScriptProcessor == null)
this.context.createScriptProcessor = this.context.createJavaScriptNode;
this.input = this.context.createGain();
sourceNode.connect(this.input);
this.buffer = [];
this.initWorker();
};
// instance methods
extend(WebAudioRecorder.prototype, {
isRecording: function() { return this.processor != null; },
setEncoding: function(encoding) {
if (this.isRecording())
this.error("setEncoding: cannot set encoding during recording");
else if (this.encoding !== encoding) {
this.encoding = encoding;
this.initWorker();
}
},
setOptions: function(options) {
if (this.isRecording())
this.error("setOptions: cannot set options during recording");
else {
extend(this.options, options);
this.worker.postMessage({ command: "options", options: this.options });
}
},
startRecording: function() {
if (this.isRecording())
this.error("startRecording: previous recording is running");
else {
var numChannels = this.numChannels,
buffer = this.buffer,
worker = this.worker;
this.processor = this.context.createScriptProcessor(
this.options.bufferSize,
this.numChannels, this.numChannels);
this.input.connect(this.processor);
this.processor.connect(this.context.destination);
this.processor.onaudioprocess = function(event) {
for (var ch = 0; ch < numChannels; ++ch)
buffer[ch] = event.inputBuffer.getChannelData(ch);
worker.postMessage({ command: "record", buffer: buffer });
};
this.worker.postMessage({
command: "start",
bufferSize: this.processor.bufferSize
});
this.startTime = Date.now();
}
},
recordingTime: function() {
return this.isRecording() ? (Date.now() - this.startTime) * 0.001 : null;
},
cancelRecording: function() {
if (this.isRecording()) {
this.input.disconnect();
this.processor.disconnect();
delete this.processor;
this.worker.postMessage({ command: "cancel" });
} else
this.error("cancelRecording: no recording is running");
},
finishRecording: function() {
if (this.isRecording()) {
this.input.disconnect();
this.processor.disconnect();
delete this.processor;
this.worker.postMessage({ command: "finish" });
} else
this.error("finishRecording: no recording is running");
},
cancelEncoding: function() {
if (this.options.encodeAfterRecord)
if (this.isRecording())
this.error("cancelEncoding: recording is not finished");
else {
this.onEncodingCanceled(this);
this.initWorker();
}
else
this.error("cancelEncoding: invalid method call");
},
initWorker: function() {
if (this.worker != null)
this.worker.terminate();
this.onEncoderLoading(this, this.encoding);
this.worker = new Worker(this.workerDir + WORKER_FILE[this.encoding]);
var _this = this;
this.worker.onmessage = function(event) {
var data = event.data;
switch (data.command) {
case "loaded":
_this.onEncoderLoaded(_this, _this.encoding);
break;
case "timeout":
_this.onTimeout(_this);
break;
case "progress":
_this.onEncodingProgress(_this, data.progress);
break;
case "complete":
_this.onComplete(_this, data.blob);
break;
case "error":
_this.error(data.message);
}
};
this.worker.postMessage({
command: "init",
config: {
sampleRate: this.context.sampleRate,
numChannels: this.numChannels
},
options: this.options
});
},
error: function(message) {
this.onError(this, "WebAudioRecorder.js:" + message);
},
// event handlers
onEncoderLoading: function(recorder, encoding) {},
onEncoderLoaded: function(recorder, encoding) {},
onTimeout: function(recorder) { recorder.finishRecording(); },
onEncodingProgress: function (recorder, progress) {},
onEncodingCanceled: function(recorder) {},
onComplete: function(recorder, blob) {
recorder.onError(recorder, "WebAudioRecorder.js: You must override .onComplete event");
},
onError: function(recorder, message) { console.log(message); }
});
window.WebAudioRecorder = WebAudioRecorder;
})(window);

View File

@ -16,14 +16,14 @@
"postinstall": "yarn build:acknowledgments && patch-package && yarn electron:install-app-deps && rimraf node_modules/dtrace-provider",
"postuninstall": "yarn build:acknowledgments",
"start": "electron .",
"generate": "npm-run-all build-protobuf build:esbuild sass get-expire-time copy-and-concat",
"generate": "npm-run-all build-protobuf build:esbuild sass get-expire-time copy-components",
"build-release": "yarn run build",
"sign-release": "node ts/updater/generateSignature.js",
"notarize": "echo 'No longer necessary'",
"get-strings": "node ts/scripts/get-strings.js",
"push-strings": "node ts/scripts/push-strings.js",
"get-expire-time": "node ts/scripts/get-expire-time.js",
"copy-and-concat": "node ts/scripts/copy-and-concat.js",
"copy-components": "node ts/scripts/copy.js",
"sass": "sass stylesheets/manifest.scss:stylesheets/manifest.css stylesheets/manifest_bridge.scss:stylesheets/manifest_bridge.css",
"build-module-protobuf": "pbjs --target static-module --force-long --no-verify --no-create --wrap commonjs --out ts/protobuf/compiled.js protos/*.proto && pbts --out ts/protobuf/compiled.d.ts ts/protobuf/compiled.js",
"clean-module-protobuf": "rm -f ts/protobuf/compiled.d.ts ts/protobuf/compiled.js",
@ -190,7 +190,7 @@
"@babel/preset-typescript": "7.17.12",
"@electron/fuses": "1.5.0",
"@mixer/parallel-prettier": "2.0.1",
"@signalapp/mock-server": "2.12.0",
"@signalapp/mock-server": "2.12.1",
"@storybook/addon-a11y": "6.5.6",
"@storybook/addon-actions": "6.5.6",
"@storybook/addon-controls": "6.5.6",
@ -229,7 +229,6 @@
"@types/lru-cache": "5.1.0",
"@types/memoizee": "0.4.2",
"@types/mocha": "9.0.0",
"@types/mustache": "4.1.2",
"@types/node": "16.18.3",
"@types/node-fetch": "2.6.2",
"@types/normalize-path": "3.0.0",

View File

@ -11,53 +11,10 @@
<body>
<div id="mocha"></div>
<div id="tests"></div>
<script type="text/javascript" src="../js/components.js"></script>
<script
type="text/javascript"
src="../ts/backbone/reliable_trigger.js"
></script>
<script
type="text/javascript"
src="../node_modules/mocha/mocha.js"
></script>
<script type="text/javascript">
mocha.setup('bdd');
</script>
<script type="text/javascript" src="test.js"></script>
<script type="text/javascript">
window.Signal.conversationControllerStart();
window.testUtilities.prepareTests();
delete window.testUtilities.prepareTests;
!(function () {
const passed = [];
const failed = [];
class Reporter extends Mocha.reporters.HTML {
constructor(runner, options) {
super(runner, options);
runner.on('pass', test => passed.push(test.fullTitle()));
runner.on('fail', (test, error) => {
failed.push({
testName: test.fullTitle(),
error: error?.stack || String(error),
});
});
runner.on('end', () =>
window.testUtilities.onComplete({ passed, failed })
);
}
}
mocha.reporter(Reporter);
mocha.run();
})();
</script>
</body>
</html>

View File

@ -7,6 +7,7 @@
* global helpers for tests
*/
mocha.setup('bdd');
mocha.setup({ timeout: 10000 });
function deleteIndexedDB() {
@ -38,7 +39,34 @@ before(async () => {
await window.storage.fetch();
});
window.Whisper = window.Whisper || {};
window.Whisper.events = { ...Backbone.Events };
window.textsecure.storage.protocol = window.getSignalProtocolStore();
window.textsecure.storage.protocol = new window.SignalProtocolStore();
window.testUtilities.prepareTests();
delete window.testUtilities.prepareTests;
!(function () {
const passed = [];
const failed = [];
class Reporter extends Mocha.reporters.HTML {
constructor(runner, options) {
super(runner, options);
runner.on('pass', test => passed.push(test.fullTitle()));
runner.on('fail', (test, error) => {
failed.push({
testName: test.fullTitle(),
error: error?.stack || String(error),
});
});
runner.on('end', () =>
window.testUtilities.onComplete({ passed, failed })
);
}
}
mocha.reporter(Reporter);
mocha.run();
})();

View File

@ -10,28 +10,30 @@ import type { IPCResponse as ChallengeResponseType } from './challenge';
type ResolveType = (data: unknown) => void;
export class CI {
private readonly eventListeners = new Map<string, Array<ResolveType>>();
export type CIType = {
deviceName: string;
handleEvent: (event: string, data: unknown) => unknown;
setProvisioningURL: (url: string) => unknown;
solveChallenge: (response: ChallengeResponseType) => unknown;
waitForEvent: (event: string, timeout?: number) => unknown;
};
private readonly completedEvents = new Map<string, Array<unknown>>();
export function getCI(deviceName: string): CIType {
const eventListeners = new Map<string, Array<ResolveType>>();
const completedEvents = new Map<string, Array<unknown>>();
constructor(public readonly deviceName: string) {
ipcRenderer.on('ci:event', (_, event, data) => {
this.handleEvent(event, data);
});
}
ipcRenderer.on('ci:event', (_, event, data) => {
handleEvent(event, data);
});
public async waitForEvent(
event: string,
timeout = 60 * SECOND
): Promise<unknown> {
const pendingCompleted = this.completedEvents.get(event) || [];
function waitForEvent(event: string, timeout = 60 * SECOND) {
const pendingCompleted = completedEvents.get(event) || [];
const pending = pendingCompleted.shift();
if (pending) {
log.info(`CI: resolving pending result for ${event}`, pending);
if (pendingCompleted.length === 0) {
this.completedEvents.delete(event);
completedEvents.delete(event);
}
return pending;
@ -44,10 +46,10 @@ export class CI {
reject(new Error('Timed out'));
}, timeout);
let list = this.eventListeners.get(event);
let list = eventListeners.get(event);
if (!list) {
list = [];
this.eventListeners.set(event, list);
eventListeners.set(event, list);
}
list.push((value: unknown) => {
@ -58,17 +60,17 @@ export class CI {
return promise;
}
public setProvisioningURL(url: string): void {
this.handleEvent('provisioning-url', url);
function setProvisioningURL(url: string): void {
handleEvent('provisioning-url', url);
}
public handleEvent(event: string, data: unknown): void {
const list = this.eventListeners.get(event) || [];
function handleEvent(event: string, data: unknown): void {
const list = eventListeners.get(event) || [];
const resolve = list.shift();
if (resolve) {
if (list.length === 0) {
this.eventListeners.delete(event);
eventListeners.delete(event);
}
log.info(`CI: got event ${event} with data`, data);
@ -78,15 +80,23 @@ export class CI {
log.info(`CI: postponing event ${event}`);
let resultList = this.completedEvents.get(event);
let resultList = completedEvents.get(event);
if (!resultList) {
resultList = [];
this.completedEvents.set(event, resultList);
completedEvents.set(event, resultList);
}
resultList.push(data);
}
public solveChallenge(response: ChallengeResponseType): void {
function solveChallenge(response: ChallengeResponseType): void {
window.Signal.challengeHandler?.onResponse(response);
}
return {
deviceName,
handleEvent,
setProvisioningURL,
solveChallenge,
waitForEvent,
};
}

View File

@ -194,13 +194,13 @@ export class ConversationController {
drop(window.storage.put('unreadCount', newUnreadCount));
if (newUnreadCount > 0) {
window.setBadgeCount(newUnreadCount);
window.IPC.setBadgeCount(newUnreadCount);
window.document.title = `${window.getTitle()} (${newUnreadCount})`;
} else {
window.setBadgeCount(0);
window.IPC.setBadgeCount(0);
window.document.title = window.getTitle();
}
window.updateTrayIcon(newUnreadCount);
window.IPC.updateTrayIcon(newUnreadCount);
}
onEmpty(): void {

View File

@ -2320,4 +2320,8 @@ export class SignalProtocolStore extends EventEmitter {
}
}
export function getSignalProtocolStore(): SignalProtocolStore {
return new SignalProtocolStore();
}
window.SignalProtocolStore = SignalProtocolStore;

161
ts/WebAudioRecorder.ts Normal file
View File

@ -0,0 +1,161 @@
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
const DEFAULT_OPTIONS = {
bufferSize: undefined, // buffer size (use browser default)
encodeAfterRecord: false,
mp3: {
mimeType: 'audio/mpeg',
bitRate: 160, // (CBR only): bit rate = [64 .. 320]
},
numChannels: 2, // number of channels
progressInterval: 1000, // encoding progress report interval (millisec)
timeLimit: 300, // recording time limit (sec)
};
type OptionsType = {
bufferSize: number | undefined;
numChannels: number;
timeLimit?: number;
};
export class WebAudioRecorder {
private buffer: Array<Float32Array>;
private options: OptionsType;
private context: BaseAudioContext;
private input: GainNode;
private onComplete: (recorder: WebAudioRecorder, blob: Blob) => unknown;
private onError: (recorder: WebAudioRecorder, error: string) => unknown;
private processor?: ScriptProcessorNode;
public worker?: Worker;
constructor(
sourceNode: GainNode,
options: Pick<OptionsType, 'timeLimit'>,
callbacks: {
onComplete: (recorder: WebAudioRecorder, blob: Blob) => unknown;
onError: (recorder: WebAudioRecorder, error: string) => unknown;
}
) {
this.options = {
...DEFAULT_OPTIONS,
...options,
};
this.context = sourceNode.context;
this.input = this.context.createGain();
sourceNode.connect(this.input);
this.buffer = [];
this.initWorker();
this.onComplete = callbacks.onComplete;
this.onError = callbacks.onError;
}
isRecording(): boolean {
return this.processor != null;
}
startRecording(): void {
if (this.isRecording()) {
this.error('startRecording: previous recording is running');
return;
}
const { buffer, worker } = this;
const { bufferSize, numChannels } = this.options;
if (!worker) {
this.error('startRecording: worker not initialized');
return;
}
this.processor = this.context.createScriptProcessor(
bufferSize,
numChannels,
numChannels
);
this.input.connect(this.processor);
this.processor.connect(this.context.destination);
this.processor.onaudioprocess = event => {
// eslint-disable-next-line no-plusplus
for (let ch = 0; ch < numChannels; ++ch) {
buffer[ch] = event.inputBuffer.getChannelData(ch);
}
worker.postMessage({ command: 'record', buffer });
};
worker.postMessage({
command: 'start',
bufferSize: this.processor.bufferSize,
});
}
cancelRecording(): void {
if (!this.isRecording()) {
this.error('cancelRecording: no recording is running');
return;
}
if (!this.worker || !this.processor) {
this.error('startRecording: worker not initialized');
return;
}
this.input.disconnect();
this.processor.disconnect();
delete this.processor;
this.worker.postMessage({ command: 'cancel' });
}
finishRecording(): void {
if (!this.isRecording()) {
this.error('finishRecording: no recording is running');
return;
}
if (!this.worker || !this.processor) {
this.error('startRecording: worker not initialized');
return;
}
this.input.disconnect();
this.processor.disconnect();
delete this.processor;
this.worker.postMessage({ command: 'finish' });
}
private initWorker(): void {
if (this.worker != null) {
this.worker.terminate();
}
this.worker = new Worker('js/WebAudioRecorderMp3.js');
this.worker.onmessage = event => {
const { data } = event;
switch (data.command) {
case 'complete':
this.onComplete(this, data.blob);
break;
case 'error':
this.error(data.message);
break;
default:
break;
}
};
this.worker.postMessage({
command: 'init',
config: {
sampleRate: this.context.sampleRate,
numChannels: this.options.numChannels,
},
options: this.options,
});
}
error(message: string): void {
this.onError(this, `WebAudioRecorder.js: ${message}`);
}
}
window.WebAudioRecorder = WebAudioRecorder;

View File

@ -2,7 +2,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
import { webFrame } from 'electron';
import { isNumber, clone, debounce } from 'lodash';
import { isNumber, debounce } from 'lodash';
import { bindActionCreators } from 'redux';
import { render } from 'react-dom';
import { batch as batchDispatch } from 'react-redux';
@ -56,7 +56,7 @@ import { GROUP_CREDENTIALS_KEY } from './services/groupCredentialFetcher';
import * as KeyboardLayout from './services/keyboardLayout';
import * as StorageService from './services/storage';
import { RoutineProfileRefresher } from './routineProfileRefresh';
import { isMoreRecentThan, isOlderThan, toDayMillis } from './util/timestamp';
import { isOlderThan, toDayMillis } from './util/timestamp';
import { isValidReactionEmoji } from './reactions/isValidReactionEmoji';
import type { ConversationModel } from './models/conversations';
import { getContact, isIncoming } from './messages/helpers';
@ -161,10 +161,11 @@ import { clearConversationDraftAttachments } from './util/clearConversationDraft
import { removeLinkPreview } from './services/LinkPreview';
import { PanelType } from './types/Panels';
import { getQuotedMessageSelector } from './state/selectors/composer';
import { flushAttachmentDownloadQueue } from './util/attachmentDownloadQueue';
import { StartupQueue } from './util/StartupQueue';
import { showConfirmationDialog } from './util/showConfirmationDialog';
import { onCallEventSync } from './util/onCallEventSync';
const MAX_ATTACHMENT_DOWNLOAD_AGE = 3600 * 72 * 1000;
export function isOverHourIntoPast(timestamp: number): boolean {
const HOUR = 1000 * 60 * 60;
return isNumber(timestamp) && isOlderThan(timestamp, HOUR);
@ -201,15 +202,11 @@ export async function startApp(): Promise<void> {
await KeyboardLayout.initialize();
window.Whisper.events = clone(window.Backbone.Events);
window.Signal.Util.MessageController.install();
window.Signal.conversationControllerStart();
window.startupProcessingQueue = new window.Signal.Util.StartupQueue();
StartupQueue.initialize();
notificationService.initialize({
i18n: window.i18n,
storage: window.storage,
});
window.attachmentDownloadQueue = [];
await window.Signal.Util.initializeMessageCounter();
@ -249,8 +246,8 @@ export async function startApp(): Promise<void> {
},
requestChallenge(request) {
if (window.CI) {
window.CI.handleEvent('challenge', request);
if (window.Signal.CI) {
window.Signal.CI.handleEvent('challenge', request);
return;
}
window.sendChallengeRequest(request);
@ -494,7 +491,7 @@ export async function startApp(): Promise<void> {
window.addEventListener('dblclick', (event: Event) => {
const target = event.target as HTMLElement;
if (isWindowDragElement(target)) {
window.titleBarDoubleClick();
window.IPC.titleBarDoubleClick();
}
});
}
@ -529,7 +526,7 @@ export async function startApp(): Promise<void> {
}
}
const builtInImages = await window.getBuiltInImages();
const builtInImages = await window.IPC.getBuiltInImages();
preload(builtInImages);
// We add this to window here because the default Node context is erased at the end
@ -608,7 +605,7 @@ export async function startApp(): Promise<void> {
try {
await new Promise<void>((resolve, reject) => {
window.showConfirmationDialog({
showConfirmationDialog({
dialogName: 'deleteOldIndexedDBData',
onTopOfEverything: true,
cancelText: window.i18n('quit'),
@ -624,7 +621,7 @@ export async function startApp(): Promise<void> {
'User chose not to delete old data. Shutting down.',
Errors.toLogFormat(error)
);
window.shutdown();
window.IPC.shutdown();
return;
}
@ -852,7 +849,7 @@ export async function startApp(): Promise<void> {
// This one should always be last - it could restart the app
if (window.isBeforeVersion(lastVersion, 'v5.30.0-alpha')) {
await deleteAllLogs();
window.restart();
window.IPC.restart();
return;
}
}
@ -1253,22 +1250,6 @@ export async function startApp(): Promise<void> {
}
});
window.Whisper.events.on(
'setWindowStats',
({
isFullScreen,
isMaximized,
}: {
isFullScreen: boolean;
isMaximized: boolean;
}) => {
window.reduxActions.user.userChanged({
isMainWindowMaximized: isMaximized,
isMainWindowFullScreen: isFullScreen,
});
}
);
window.Whisper.events.on('setMenuOptions', (options: MenuOptionsType) => {
window.reduxActions.user.userChanged({ menuOptions: options });
});
@ -1677,7 +1658,7 @@ export async function startApp(): Promise<void> {
event.preventDefault();
event.stopPropagation();
window.showConfirmationDialog({
showConfirmationDialog({
dialogName: 'deleteMessage',
confirmStyle: 'negative',
message: window.i18n('deleteWarning'),
@ -1895,8 +1876,8 @@ export async function startApp(): Promise<void> {
document.getElementById('app-container')
);
const hideMenuBar = window.storage.get('hide-menu-bar', false);
window.setAutoHideMenuBar(hideMenuBar);
window.setMenuBarVisibility(!hideMenuBar);
window.IPC.setAutoHideMenuBar(hideMenuBar);
window.IPC.setMenuBarVisibility(!hideMenuBar);
startTimeTravelDetector(() => {
window.Whisper.events.trigger('timetravel');
@ -1927,7 +1908,7 @@ export async function startApp(): Promise<void> {
window.addEventListener('unload', () => notificationService.fastClear());
notificationService.on('click', (id, messageId, storyId) => {
window.showWindow();
window.IPC.showWindow();
if (id) {
if (storyId) {
@ -2491,7 +2472,7 @@ export async function startApp(): Promise<void> {
window.flushAllWaitBatchers(),
]);
log.info('onEmpty: All outstanding database requests complete');
window.readyForUpdates();
window.IPC.readyForUpdates();
window.ConversationController.onEmpty();
// Start listeners here, after we get through our queue.
@ -2512,7 +2493,7 @@ export async function startApp(): Promise<void> {
window.reduxActions.app.initialLoadComplete();
const processedCount = messageReceiver?.getAndResetProcessedCount() || 0;
window.logAppLoadedEvent?.({
window.IPC.logAppLoadedEvent?.({
processedCount,
});
if (messageReceiver) {
@ -2520,53 +2501,12 @@ export async function startApp(): Promise<void> {
}
window.Signal.Util.setBatchingStrategy(false);
const attachmentDownloadQueue = window.attachmentDownloadQueue || [];
// NOTE: ts/models/messages.ts expects this global to become undefined
// once we stop processing the queue.
window.attachmentDownloadQueue = undefined;
const MAX_ATTACHMENT_MSGS_TO_DOWNLOAD = 250;
const attachmentsToDownload = attachmentDownloadQueue.filter(
(message, index) =>
index <= MAX_ATTACHMENT_MSGS_TO_DOWNLOAD ||
isMoreRecentThan(
message.getReceivedAt(),
MAX_ATTACHMENT_DOWNLOAD_AGE
) ||
// Stickers and long text attachments has to be downloaded for UI
// to display the message properly.
message.hasRequiredAttachmentDownloads()
);
log.info(
'Downloading recent attachments of total attachments',
attachmentsToDownload.length,
attachmentDownloadQueue.length
);
if (window.startupProcessingQueue) {
window.startupProcessingQueue.flush();
window.startupProcessingQueue = undefined;
}
const messagesWithDownloads = await Promise.all(
attachmentsToDownload.map(message => message.queueAttachmentDownloads())
);
const messagesToSave: Array<MessageAttributesType> = [];
messagesWithDownloads.forEach((shouldSave, messageKey) => {
if (shouldSave) {
const message = attachmentsToDownload[messageKey];
messagesToSave.push(message.attributes);
}
});
await window.Signal.Data.saveMessages(messagesToSave, {
ourUuid: storage.user.getCheckedUuid().toString(),
});
StartupQueue.flush();
await flushAttachmentDownloadQueue();
// Process crash reports if any
window.reduxActions.crashReports.setCrashReportCount(
await window.crashReports.getCount()
await window.IPC.crashReports.getCount()
);
// Kick off a profile refresh if necessary, but don't wait for it, as failure is

View File

@ -43,6 +43,7 @@ type PropsType = {
onUndoArchive: (conversationId: string) => unknown;
openFileInFolder: (target: string) => unknown;
hasCustomTitleBar: boolean;
osClassName: string;
hideMenuBar: boolean;
executeMenuRole: ExecuteMenuRoleType;
@ -75,6 +76,7 @@ export function App({
onUndoArchive,
openFileInFolder,
openInbox,
osClassName,
registerSingleDevice,
renderCallManager,
renderGlobalModalContainer,
@ -95,7 +97,7 @@ export function App({
contents = <SmartInstallScreen />;
} else if (appView === AppViewType.Standalone) {
const onComplete = () => {
window.removeSetupMenuItems();
window.IPC.removeSetupMenuItems();
openInbox();
};
contents = (
@ -123,6 +125,19 @@ export function App({
}
}, [theme]);
useEffect(() => {
document.body.classList.add(osClassName);
}, [osClassName]);
useEffect(() => {
document.body.classList.toggle('os-has-custom-titlebar', hasCustomTitleBar);
}, [hasCustomTitleBar]);
useEffect(() => {
document.body.classList.toggle('full-screen', isFullScreen);
document.body.classList.toggle('maximized', isMaximized);
}, [isFullScreen, isMaximized]);
const isPageVisible = usePageVisibility();
useEffect(() => {
document.body.classList.toggle('page-is-visible', isPageVisible);

View File

@ -668,7 +668,11 @@ export function CompositionInput(props: Props): React.ReactElement {
<Manager>
<Reference>
{({ ref }) => (
<div className={getClassName('__input')} ref={ref}>
<div
className={getClassName('__input')}
ref={ref}
data-testid="CompositionInput"
>
{children}
<div
ref={

View File

@ -321,6 +321,7 @@ export function ConversationList({
'badges',
'color',
'draftPreview',
'groupId',
'id',
'isMe',
'isSelected',

View File

@ -106,7 +106,7 @@ export function StandaloneRegistration({
registerSingleDevice: (number: string, code: string) => Promise<void>;
}): JSX.Element {
useEffect(() => {
window.readyForUpdates();
window.IPC.readyForUpdates();
}, []);
const [isValidNumber, setIsValidNumber] = useState(false);

View File

@ -162,7 +162,7 @@ export function ToastManager({
onClose={hideToast}
toastAction={{
label: i18n('Toast--error--action'),
onClick: () => window.showDebugLog(),
onClick: () => window.IPC.showDebugLog(),
}}
>
{i18n('Toast--error')}

View File

@ -16,6 +16,7 @@ import {
} from '../../types/Attachment';
import * as Errors from '../../types/errors';
import * as log from '../../logging/log';
import { useReducedMotion } from '../../hooks/useReducedMotion';
const MAX_GIF_REPEAT = 4;
const MAX_GIF_TIME = 8;
@ -28,8 +29,6 @@ export type Props = {
readonly i18n: LocalizerType;
readonly theme?: ThemeType;
readonly reducedMotion?: boolean;
onError(): void;
showVisualAttachment(): void;
kickOffAttachmentDownload(): void;
@ -46,16 +45,12 @@ export function GIF(props: Props): JSX.Element {
i18n,
theme,
reducedMotion = Boolean(
window.Accessibility && window.Accessibility.reducedMotionSetting
),
onError,
showVisualAttachment,
kickOffAttachmentDownload,
} = props;
const tapToPlay = reducedMotion;
const tapToPlay = useReducedMotion();
const videoRef = useRef<HTMLVideoElement | null>(null);
const { height, width } = getImageDimensions(attachment, size);

View File

@ -221,7 +221,6 @@ export type PropsData = {
| 'title'
| 'unblurredAvatarPath'
>;
reducedMotion?: boolean;
conversationType: ConversationTypeType;
attachments?: ReadonlyArray<AttachmentType>;
giftBadge?: GiftBadgeType;
@ -521,7 +520,7 @@ export class Message extends React.PureComponent<Props, State> {
status === 'viewed')
) {
const delta = Date.now() - timestamp;
window.CI?.handleEvent('message:send-complete', {
window.Signal.CI?.handleEvent('message:send-complete', {
timestamp,
delta,
});
@ -838,7 +837,6 @@ export class Message extends React.PureComponent<Props, State> {
pushPanelForConversation,
quote,
readStatus,
reducedMotion,
renderAudioAttachment,
renderingContext,
shouldCollapseAbove,
@ -889,7 +887,6 @@ export class Message extends React.PureComponent<Props, State> {
theme={theme}
i18n={i18n}
tabIndex={0}
reducedMotion={reducedMotion}
onError={this.handleImageError}
showVisualAttachment={() => {
showLightbox({
@ -2529,10 +2526,11 @@ export class Message extends React.PureComponent<Props, State> {
attachments,
direction,
isSticker,
onKeyDown,
renderMenu,
shouldCollapseAbove,
shouldCollapseBelow,
renderMenu,
onKeyDown,
timestamp,
} = this.props;
const { expired, expiring, isSelected, imageBroken } = this.state;
@ -2554,6 +2552,7 @@ export class Message extends React.PureComponent<Props, State> {
isSelected ? 'module-message--selected' : null,
expiring ? 'module-message--expired' : null
)}
data-testid={timestamp}
tabIndex={0}
// We need to have a role because screenreaders need to be able to focus here to
// read the message, but we can't be a button; that would break inner buttons.

View File

@ -1147,5 +1147,5 @@ function getRowIndexFromElement(
}
function showDebugLog() {
window.showDebugLog();
window.IPC.showDebugLog();
}

View File

@ -224,7 +224,6 @@ const renderAudioAttachment: Props['renderAudioAttachment'] = props => (
const createProps = (overrideProps: Partial<Props> = {}): Props => ({
attachments: overrideProps.attachments,
author: overrideProps.author || getDefaultConversation(),
reducedMotion: boolean('reducedMotion', false),
bodyRanges: overrideProps.bodyRanges,
canReact: true,
canReply: true,

View File

@ -148,7 +148,7 @@ export function ConversationDetailsHeader({
if (canEdit) {
return (
<div className={bem('root')}>
<div className={bem('root')} data-testid="ConversationDetailsHeader">
{modal}
{avatar}
<button
@ -182,7 +182,7 @@ export function ConversationDetailsHeader({
}
return (
<div className={bem('root')}>
<div className={bem('root')} data-testid="ConversationDetailsHeader">
{modal}
{avatar}
{contents}

View File

@ -5,7 +5,6 @@ import type { ReactNode, FunctionComponent } from 'react';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import classNames from 'classnames';
import { isBoolean, isNumber } from 'lodash';
import { v4 as uuid } from 'uuid';
import { Avatar, AvatarSize } from '../Avatar';
import type { BadgeType } from '../../badges/types';
@ -17,6 +16,7 @@ import { Spinner } from '../Spinner';
import { Time } from '../Time';
import { formatDateTimeShort } from '../../util/timestamp';
import * as durations from '../../util/durations';
import { UUID } from '../../types/UUID';
const BASE_CLASS_NAME =
'module-conversation-list__item--contact-or-conversation';
@ -52,11 +52,13 @@ type PropsType = {
shouldShowSpinner?: boolean;
unreadCount?: number;
avatarSize?: AvatarSize;
testId?: string;
} & Pick<
ConversationType,
| 'acceptedMessageRequest'
| 'avatarPath'
| 'color'
| 'groupId'
| 'isMe'
| 'markedUnread'
| 'phoneNumber'
@ -64,6 +66,7 @@ type PropsType = {
| 'sharedGroupNames'
| 'title'
| 'unblurredAvatarPath'
| 'uuid'
> &
(
| { badge?: undefined; theme?: ThemeType }
@ -80,6 +83,7 @@ export const BaseConversationListItem: FunctionComponent<PropsType> =
color,
conversationType,
disabled,
groupId,
headerDate,
headerName,
i18n,
@ -97,13 +101,16 @@ export const BaseConversationListItem: FunctionComponent<PropsType> =
profileName,
sharedGroupNames,
shouldShowSpinner,
testId: overrideTestId,
title,
unblurredAvatarPath,
unreadCount,
uuid,
} = props;
const identifier = id ? cleanId(id) : undefined;
const htmlId = useMemo(() => uuid(), []);
const htmlId = useMemo(() => UUID.generate().toString(), []);
const testId = overrideTestId || groupId || uuid;
const isUnread = isConversationUnread({ markedUnread, unreadCount });
const isAvatarNoteToSelf = isBoolean(isNoteToSelf)
@ -222,6 +229,7 @@ export const BaseConversationListItem: FunctionComponent<PropsType> =
{ [`${BASE_CLASS_NAME}--is-checkbox--disabled`]: disabled }
)}
data-id={identifier}
data-testid={testId}
htmlFor={htmlId}
// `onClick` is will double-fire if we're enabled. We want it to fire when we're
// disabled so we can show any "can't add contact" modals, etc. This won't
@ -242,6 +250,7 @@ export const BaseConversationListItem: FunctionComponent<PropsType> =
`${BASE_CLASS_NAME}--is-button`
)}
data-id={identifier}
data-testid={testId}
disabled={disabled}
onClick={onClick}
type="button"
@ -252,7 +261,11 @@ export const BaseConversationListItem: FunctionComponent<PropsType> =
}
return (
<div className={commonClassNames} data-id={identifier}>
<div
className={commonClassNames}
data-id={identifier}
data-testid={testId}
>
{contents}
</div>
);

View File

@ -30,6 +30,7 @@ export type PropsDataType = {
| 'acceptedMessageRequest'
| 'avatarPath'
| 'color'
| 'groupId'
| 'id'
| 'isMe'
| 'phoneNumber'
@ -38,6 +39,7 @@ export type PropsDataType = {
| 'title'
| 'type'
| 'unblurredAvatarPath'
| 'uuid'
>;
type PropsHousekeepingType = {
@ -59,6 +61,7 @@ export const ContactCheckbox: FunctionComponent<PropsType> = React.memo(
badge,
color,
disabledReason,
groupId,
i18n,
id,
isChecked,
@ -71,6 +74,7 @@ export const ContactCheckbox: FunctionComponent<PropsType> = React.memo(
title,
type,
unblurredAvatarPath,
uuid,
}) {
const disabled = Boolean(disabledReason);
@ -104,6 +108,7 @@ export const ContactCheckbox: FunctionComponent<PropsType> = React.memo(
color={color}
conversationType={type}
disabled={disabled}
groupId={groupId}
headerName={headerName}
i18n={i18n}
id={id}
@ -117,6 +122,7 @@ export const ContactCheckbox: FunctionComponent<PropsType> = React.memo(
theme={theme}
title={title}
unblurredAvatarPath={unblurredAvatarPath}
uuid={uuid}
/>
);
}

View File

@ -22,6 +22,7 @@ export type ContactListItemConversationType = Pick<
| 'avatarPath'
| 'badges'
| 'color'
| 'groupId'
| 'id'
| 'isMe'
| 'phoneNumber'
@ -54,6 +55,7 @@ export const ContactListItem: FunctionComponent<PropsType> = React.memo(
avatarPath,
badge,
color,
groupId,
i18n,
id,
isMe,
@ -89,6 +91,7 @@ export const ContactListItem: FunctionComponent<PropsType> = React.memo(
badge={badge}
color={color}
conversationType={type}
groupId={groupId}
headerName={headerName}
i18n={i18n}
id={id}
@ -102,6 +105,7 @@ export const ContactListItem: FunctionComponent<PropsType> = React.memo(
theme={theme}
title={title}
unblurredAvatarPath={unblurredAvatarPath}
uuid={uuid}
/>
);
}

View File

@ -41,6 +41,7 @@ export type PropsData = Pick<
| 'badges'
| 'color'
| 'draftPreview'
| 'groupId'
| 'id'
| 'isMe'
// NOTE: Passed for CI, not used for rendering
@ -79,6 +80,7 @@ export const ConversationListItem: FunctionComponent<Props> = React.memo(
badge,
color,
draftPreview,
groupId,
i18n,
id,
isMe,
@ -180,6 +182,7 @@ export const ConversationListItem: FunctionComponent<Props> = React.memo(
badge={badge}
color={color}
conversationType={type}
groupId={groupId}
headerDate={lastUpdated}
headerName={headerName}
i18n={i18n}
@ -198,6 +201,7 @@ export const ConversationListItem: FunctionComponent<Props> = React.memo(
title={title}
unreadCount={unreadCount}
unblurredAvatarPath={unblurredAvatarPath}
uuid={uuid}
/>
);
}

View File

@ -26,6 +26,7 @@ export const CreateNewGroupButton: FunctionComponent<PropsType> = React.memo(
isSelected={false}
onClick={onClick}
sharedGroupNames={[]}
testId="CreateNewGroupButton"
title={title}
/>
);

View File

@ -82,7 +82,12 @@ export const StickerManager = React.memo(function StickerManagerInner({
uninstallStickerPack={uninstallStickerPack}
/>
) : null}
<div className="module-sticker-manager" tabIndex={-1} ref={focusRef}>
<div
className="module-sticker-manager"
data-testid="StickerManager"
tabIndex={-1}
ref={focusRef}
>
<Tabs
initialSelectedTab={TabViews.Available}
tabs={[

View File

@ -113,6 +113,7 @@ export const StickerManagerPackRow = React.memo(
onKeyDown={handleKeyDown}
onClick={handleClickPreview}
className="module-sticker-manager__pack-row"
data-testid={id}
>
{pack.cover ? (
<img

View File

@ -2137,7 +2137,10 @@ export async function getGroupMigrationMembers(
`getGroupMigrationMembers/${logId}: membersV2 - missing local contact for ${e164}, skipping.`
);
}
if (!isMe(contact.attributes) && window.GV2_MIGRATION_DISABLE_ADD) {
if (
!isMe(contact.attributes) &&
window.Flags.GV2_MIGRATION_DISABLE_ADD
) {
log.warn(
`getGroupMigrationMembers/${logId}: membersV2 - skipping ${e164} due to GV2_MIGRATION_DISABLE_ADD flag`
);
@ -2205,7 +2208,10 @@ export async function getGroupMigrationMembers(
return null;
}
if (!isMe(contact.attributes) && window.GV2_MIGRATION_DISABLE_INVITE) {
if (
!isMe(contact.attributes) &&
window.Flags.GV2_MIGRATION_DISABLE_INVITE
) {
log.warn(
`getGroupMigrationMembers/${logId}: pendingMembersV2 - skipping ${e164} due to GV2_MIGRATION_DISABLE_INVITE flag`
);
@ -3402,7 +3408,7 @@ async function getGroupUpdates({
newRevision === currentRevision + 1;
if (
window.GV2_ENABLE_SINGLE_CHANGE_PROCESSING &&
window.Flags.GV2_ENABLE_SINGLE_CHANGE_PROCESSING &&
wrappedGroupChange &&
isNumber(newRevision) &&
(isInitialCreationMessage || weAreAwaitingApproval || isOneVersionUp)
@ -3455,7 +3461,7 @@ async function getGroupUpdates({
if (
(!isFirstFetch || isNumber(newRevision)) &&
window.GV2_ENABLE_CHANGE_PROCESSING
window.Flags.GV2_ENABLE_CHANGE_PROCESSING
) {
try {
return await updateGroupViaLogs({
@ -3483,7 +3489,7 @@ async function getGroupUpdates({
}
}
if (window.GV2_ENABLE_STATE_PROCESSING) {
if (window.Flags.GV2_ENABLE_STATE_PROCESSING) {
try {
return await updateGroupViaState({
dropInitialJoinMessage,
@ -3506,7 +3512,7 @@ async function getGroupUpdates({
}
}
if (window.GV2_ENABLE_PRE_JOIN_FETCH) {
if (window.Flags.GV2_ENABLE_PRE_JOIN_FETCH) {
try {
return await updateGroupViaPreJoinInfo({
group,

View File

@ -1,14 +0,0 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
{
const updateFullScreenClass = (
isFullScreen: boolean,
isMaximized: boolean
) => {
document.body.classList.toggle('full-screen', isFullScreen);
document.body.classList.toggle('maximized', isMaximized);
};
updateFullScreenClass(window.isFullScreen(), window.isMaximized());
window.onFullScreenChange = updateFullScreenClass;
}

View File

@ -266,7 +266,9 @@ export class MessageReceipts extends Collection<MessageReceiptModel> {
// We want the above call to not be delayed when testing with
// CI.
window.CI ? deleteSentProtoBatcher.flushAndWait() : Promise.resolve(),
window.Signal.CI
? deleteSentProtoBatcher.flushAndWait()
: Promise.resolve(),
]);
} else {
log.warn(

View File

@ -11,6 +11,7 @@ import { isMessageUnread } from '../util/isMessageUnread';
import { notificationService } from '../services/notifications';
import * as log from '../logging/log';
import * as Errors from '../types/errors';
import { StartupQueue } from '../util/StartupQueue';
export type ReadSyncAttributesType = {
senderId: string;
@ -119,10 +120,10 @@ export class ReadSyncs extends Collection {
void message.getConversation()?.onReadMessage(message, readAt);
};
if (window.startupProcessingQueue) {
if (StartupQueue.isReady()) {
const conversation = message.getConversation();
if (conversation) {
window.startupProcessingQueue.add(
StartupQueue.add(
conversation.get('id'),
message.get('sent_at'),
updateConversation

View File

@ -158,6 +158,7 @@ import { isSignalConversation } from '../util/isSignalConversation';
import { isMemberRequestingToJoin } from '../util/isMemberRequestingToJoin';
import { removePendingMember } from '../util/removePendingMember';
import { isMemberPending } from '../util/isMemberPending';
import { imageToBlurHash } from '../util/imageToBlurHash';
const EMPTY_ARRAY: Readonly<[]> = [];
const EMPTY_GROUP_COLLISIONS: GroupNameCollisionsWithIdsByTitle = {};
@ -3890,7 +3891,7 @@ export class ConversationModel extends window.Backbone
contentType,
width,
height,
blurHash: await window.imageToBlurHash(
blurHash: await imageToBlurHash(
new Blob([data], {
type: IMAGE_JPEG,
})
@ -5527,7 +5528,7 @@ export class ConversationModel extends window.Backbone
const delta = now - startedAt;
log.info(`conversation ${this.idForLogging()} open took ${delta}ms`);
window.CI?.handleEvent('conversation:open', { delta });
window.Signal.CI?.handleEvent('conversation:open', { delta });
}
async flushDebouncedUpdates(): Promise<void> {

View File

@ -173,6 +173,10 @@ import { GiftBadgeStates } from '../components/conversation/Message';
import { downloadAttachment } from '../util/downloadAttachment';
import type { StickerWithHydratedData } from '../types/Stickers';
import { getStringForConversationMerge } from '../util/getStringForConversationMerge';
import {
addToAttachmentDownloadQueue,
shouldUseAttachmentDownloadQueue,
} from '../util/attachmentDownloadQueue';
import { getTitleNoDefault, getNumber } from '../util/getTitle';
import dataInterface from '../sql/Client';
@ -346,6 +350,10 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
}
notifyRedux(): void {
if (!window.reduxActions) {
return;
}
const { storyChanged } = window.reduxActions.stories;
if (isStory(this.attributes)) {
@ -2989,12 +2997,8 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
(conversation.getAccepted() || isOutgoing(message.attributes)) &&
!shouldHoldOffDownload
) {
if (window.attachmentDownloadQueue) {
window.attachmentDownloadQueue.unshift(message);
log.info(
`${idLog}: Adding to attachmentDownloadQueue`,
message.get('sent_at')
);
if (shouldUseAttachmentDownloadQueue()) {
addToAttachmentDownloadQueue(idLog, message);
} else {
await message.queueAttachmentDownloads();
}

View File

@ -1,30 +1,10 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { basename, join } from 'path';
import { copyFileSync, readFileSync, writeFileSync } from 'fs';
// Concat
console.log('Concatenating...');
import { join } from 'path';
import { copyFileSync } from 'fs';
const BASE_BOWER = join(__dirname, '../../components');
const CONCAT_TARGET = join(__dirname, '../../js/components.js');
const CONCAT_SOURCES = [
join(BASE_BOWER, 'webaudiorecorder/lib/WebAudioRecorder.js'),
];
let concat = '// concatenated components.js';
CONCAT_SOURCES.forEach(source => {
const contents = readFileSync(source, 'utf8');
const name = basename(source);
console.log(`Concatenating ${source}`);
concat += `\n\n// ${name}\n${contents}`;
});
console.log(`Writing to ${CONCAT_TARGET}`);
writeFileSync(CONCAT_TARGET, concat);
// Copy

View File

@ -24,6 +24,7 @@ import { SECOND } from '../util/durations';
import { autoScale } from '../util/handleImageAttachment';
import { dropNull } from '../util/dropNull';
import { fileToBytes } from '../util/fileToBytes';
import { imageToBlurHash } from '../util/imageToBlurHash';
import { maybeParseUrl } from '../util/url';
import { sniffImageMimeType } from '../util/sniffImageMimeType';
import { drop } from '../util/drop';
@ -343,7 +344,7 @@ async function getPreview(
const data = await fileToBytes(withBlob.file);
objectUrl = URL.createObjectURL(withBlob.file);
const blurHash = await window.imageToBlurHash(withBlob.file);
const blurHash = await imageToBlurHash(withBlob.file);
const dimensions = await VisualAttachment.getImageDimensions({
objectUrl,
@ -535,7 +536,7 @@ async function getGroupPreview(
data,
size: data.byteLength,
contentType: IMAGE_JPEG,
blurHash: await window.imageToBlurHash(
blurHash: await imageToBlurHash(
new Blob([data], {
type: IMAGE_JPEG,
})

View File

@ -3,13 +3,13 @@
import { requestMicrophonePermissions } from '../util/requestMicrophonePermissions';
import * as log from '../logging/log';
import type { WebAudioRecorderClass } from '../window.d';
import type { WebAudioRecorder } from '../WebAudioRecorder';
import * as Errors from '../types/errors';
export class RecorderClass {
private context?: AudioContext;
private input?: GainNode;
private recorder?: WebAudioRecorderClass;
private recorder?: WebAudioRecorder;
private source?: MediaStreamAudioSourceNode;
private stream?: MediaStream;
private blob?: Blob;
@ -31,7 +31,7 @@ export class RecorderClass {
// Reach in and terminate the web worker used by WebAudioRecorder, otherwise
// it gets leaked due to a reference cycle with its onmessage listener
this.recorder.worker.terminate();
this.recorder.worker?.terminate();
this.recorder = undefined;
}
@ -58,15 +58,16 @@ export class RecorderClass {
this.context = new AudioContext();
this.input = this.context.createGain();
this.recorder = new window.WebAudioRecorder(this.input, {
encoding: 'mp3',
workerDir: 'js/', // must end with slash
options: {
this.recorder = new window.WebAudioRecorder(
this.input,
{
timeLimit: 60 + 3600, // one minute more than our UI-imposed limit
},
});
this.recorder.onComplete = this.onComplete.bind(this);
this.recorder.onError = this.onError.bind(this);
{
onComplete: this.onComplete.bind(this),
onError: this.onError.bind(this),
}
);
try {
const stream = await navigator.mediaDevices.getUserMedia({
@ -78,7 +79,7 @@ export class RecorderClass {
const err = new Error(
'Recorder/getUserMedia/stream: Missing context or input!'
);
this.onError(this.recorder, err);
this.onError(this.recorder, String(err));
throw err;
}
this.source = this.context.createMediaStreamSource(stream);
@ -120,12 +121,12 @@ export class RecorderClass {
return promise;
}
onComplete(_recorder: WebAudioRecorderClass, blob: Blob): void {
onComplete(_recorder: WebAudioRecorder, blob: Blob): void {
this.blob = blob;
this.resolve?.(blob);
}
onError(_recorder: WebAudioRecorderClass, error: Error): void {
onError(_recorder: WebAudioRecorder, error: string): void {
if (!this.recorder) {
log.warn('Recorder/onError: Called with no recorder');
return;

View File

@ -1620,12 +1620,12 @@ export class CallingClass {
}
private async requestCameraPermissions(): Promise<boolean> {
const cameraPermission = await window.getMediaCameraPermissions();
const cameraPermission = await window.IPC.getMediaCameraPermissions();
if (!cameraPermission) {
await window.showPermissionsPopup(true, true);
await window.IPC.showPermissionsPopup(true, true);
// Check the setting again (from the source of truth).
return window.getMediaCameraPermissions();
return window.IPC.getMediaCameraPermissions();
}
return true;

View File

@ -260,7 +260,7 @@ class NotificationService extends EventEmitter {
);
if (shouldDrawAttention) {
log.info('NotificationService: drawing attention');
window.drawAttention();
window.IPC.drawAttention();
}
let notificationTitle: string;

View File

@ -1733,8 +1733,8 @@ async function sync(
// We now know that we've successfully completed a storage service fetch
await window.storage.put('storageFetchComplete', true);
if (window.CI) {
window.CI.handleEvent('storageServiceComplete', {
if (window.Signal.CI) {
window.Signal.CI.handleEvent('storageServiceComplete', {
manifestVersion: version,
});
}

View File

@ -1,21 +0,0 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
{
let className: string;
if (window.SignalContext.OS.isWindows()) {
className = 'os-windows';
} else if (window.SignalContext.OS.isMacOS()) {
className = 'os-macos';
} else if (window.SignalContext.OS.isLinux()) {
className = 'os-linux';
} else {
throw new Error('Unexpected operating system; not applying ');
}
document.body.classList.add(className);
if (window.SignalContext.OS.hasCustomTitleBar()) {
document.body.classList.add('os-has-custom-titlebar');
}
}

View File

@ -2,5 +2,5 @@
// SPDX-License-Identifier: AGPL-3.0-only
export function showSettings(): void {
window.showSettings();
window.IPC.showSettings();
}

View File

@ -32,5 +32,5 @@ export async function deleteAllData(): Promise<void> {
Errors.toLogFormat(error)
);
}
window.restart();
window.IPC.restart();
}

View File

@ -13,36 +13,13 @@ import * as RemoteConfig from './RemoteConfig';
import * as Util from './util';
// Components
import { AttachmentList } from './components/conversation/AttachmentList';
import { ChatColorPicker } from './components/ChatColorPicker';
import { ConfirmationDialog } from './components/ConfirmationDialog';
import { ContactModal } from './components/conversation/ContactModal';
import { Emojify } from './components/conversation/Emojify';
import { MessageDetail } from './components/conversation/MessageDetail';
import { Quote } from './components/conversation/Quote';
import { StagedLinkPreview } from './components/conversation/StagedLinkPreview';
import { DisappearingTimeDialog } from './components/DisappearingTimeDialog';
// State
import { createApp } from './state/roots/createApp';
import { createSafetyNumberViewer } from './state/roots/createSafetyNumberViewer';
import { createStore } from './state/createStore';
import * as appDuck from './state/ducks/app';
import * as callingDuck from './state/ducks/calling';
import * as conversationsDuck from './state/ducks/conversations';
import * as emojisDuck from './state/ducks/emojis';
import * as expirationDuck from './state/ducks/expiration';
import * as itemsDuck from './state/ducks/items';
import * as linkPreviewsDuck from './state/ducks/linkPreviews';
import * as networkDuck from './state/ducks/network';
import * as searchDuck from './state/ducks/search';
import * as stickersDuck from './state/ducks/stickers';
import * as updatesDuck from './state/ducks/updates';
import * as userDuck from './state/ducks/user';
import * as conversationsSelectors from './state/selectors/conversations';
import * as searchSelectors from './state/selectors/search';
// Types
import * as TypesAttachment from './types/Attachment';
@ -378,15 +355,7 @@ export const setup = (options: {
});
const Components = {
AttachmentList,
ChatColorPicker,
ConfirmationDialog,
ContactModal,
Emojify,
MessageDetail,
Quote,
StagedLinkPreview,
DisappearingTimeDialog,
};
const Roots = {
@ -394,26 +363,6 @@ export const setup = (options: {
createSafetyNumberViewer,
};
const Ducks = {
app: appDuck,
calling: callingDuck,
conversations: conversationsDuck,
emojis: emojisDuck,
expiration: expirationDuck,
items: itemsDuck,
linkPreviews: linkPreviewsDuck,
network: networkDuck,
updates: updatesDuck,
user: userDuck,
search: searchDuck,
stickers: stickersDuck,
};
const Selectors = {
conversations: conversationsSelectors,
search: searchSelectors,
};
const Services = {
calling,
initializeGroupCredentialFetcher,
@ -427,8 +376,6 @@ export const setup = (options: {
const State = {
createStore,
Roots,
Ducks,
Selectors,
};
const Types = {

View File

@ -85,7 +85,7 @@ function openInstaller(): ThunkAction<
OpenInstallerActionType
> {
return dispatch => {
window.addSetupMenuItems();
window.IPC.addSetupMenuItems();
dispatch({
type: OPEN_INSTALLER,
@ -104,7 +104,7 @@ function openStandalone(): ThunkAction<
return;
}
window.addSetupMenuItems();
window.IPC.addSetupMenuItems();
dispatch({
type: OPEN_STANDALONE,
});

View File

@ -44,11 +44,11 @@ function setCrashReportCount(count: number): SetCrashReportCountActionType {
}
function uploadCrashReports(): PromiseAction<typeof UPLOAD> {
return { type: UPLOAD, payload: window.crashReports.upload() };
return { type: UPLOAD, payload: window.IPC.crashReports.upload() };
}
function eraseCrashReports(): PromiseAction<typeof ERASE> {
return { type: ERASE, payload: window.crashReports.erase() };
return { type: ERASE, payload: window.IPC.crashReports.erase() };
}
// Reducer

View File

@ -3,32 +3,34 @@
import { trigger } from '../../shims/events';
import type { NoopActionType } from './noop';
import type { LocalizerType } from '../../types/Util';
import type { LocaleMessagesType } from '../../types/I18N';
import { ThemeType } from '../../types/Util';
import type { UUIDStringType } from '../../types/UUID';
import type { LocalizerType } from '../../types/Util';
import type { MenuOptionsType } from '../../types/menu';
import type { NoopActionType } from './noop';
import type { UUIDStringType } from '../../types/UUID';
import * as OS from '../../OS';
import { ThemeType } from '../../types/Util';
// State
export type UserStateType = {
attachmentsPath: string;
stickersPath: string;
tempPath: string;
i18n: LocalizerType;
interactionMode: 'mouse' | 'keyboard';
isMainWindowFullScreen: boolean;
isMainWindowMaximized: boolean;
localeMessages: LocaleMessagesType;
menuOptions: MenuOptionsType;
osName: 'linux' | 'macos' | 'windows' | undefined;
ourACI: UUIDStringType | undefined;
ourConversationId: string | undefined;
ourDeviceId: number | undefined;
ourACI: UUIDStringType | undefined;
ourPNI: UUIDStringType | undefined;
ourNumber: string | undefined;
ourPNI: UUIDStringType | undefined;
platform: string;
regionCode: string | undefined;
i18n: LocalizerType;
localeMessages: LocaleMessagesType;
interactionMode: 'mouse' | 'keyboard';
isMainWindowMaximized: boolean;
isMainWindowFullScreen: boolean;
menuOptions: MenuOptionsType;
stickersPath: string;
tempPath: string;
theme: ThemeType;
version: string;
};
@ -96,20 +98,27 @@ const intlNotSetup = () => {
// Reducer
export function getEmptyState(): UserStateType {
let osName: 'windows' | 'macos' | 'linux' | undefined;
if (OS.isWindows()) {
osName = 'windows';
} else if (OS.isMacOS()) {
osName = 'macos';
} else if (OS.isLinux()) {
osName = 'linux';
}
return {
attachmentsPath: 'missing',
stickersPath: 'missing',
tempPath: 'missing',
ourConversationId: 'missing',
ourDeviceId: 0,
ourACI: undefined,
ourPNI: undefined,
ourNumber: 'missing',
regionCode: 'missing',
platform: 'missing',
i18n: Object.assign(intlNotSetup, {
getLocale: intlNotSetup,
getIntl: intlNotSetup,
isLegacyFormat: intlNotSetup,
}),
interactionMode: 'mouse',
isMainWindowMaximized: false,
isMainWindowFullScreen: false,
localeMessages: {},
menuOptions: {
development: false,
devTools: false,
@ -117,13 +126,17 @@ export function getEmptyState(): UserStateType {
isProduction: true,
platform: 'unknown',
},
osName,
ourACI: undefined,
ourConversationId: 'missing',
ourDeviceId: 0,
ourNumber: 'missing',
ourPNI: undefined,
platform: 'missing',
regionCode: 'missing',
stickersPath: 'missing',
tempPath: 'missing',
theme: ThemeType.light,
i18n: Object.assign(intlNotSetup, {
getLocale: intlNotSetup,
getIntl: intlNotSetup,
isLegacyFormat: intlNotSetup,
}),
localeMessages: {},
version: '0.0.0',
};
}

View File

@ -26,15 +26,15 @@ import { getEmptyState as user } from './ducks/user';
import { getEmptyState as username } from './ducks/username';
import type { StateType } from './reducer';
import type { BadgesStateType } from './ducks/badges';
import type { MainWindowStatsType } from '../windows/context';
import type { MenuOptionsType } from '../types/menu';
import type { StoryDataType } from './ducks/stories';
import type { StoryDistributionListDataType } from './ducks/storyDistributionLists';
import { getInitialState as stickers } from '../types/Stickers';
import type { MenuOptionsType } from '../types/menu';
import * as OS from '../OS';
import { UUIDKind } from '../types/UUID';
import { getEmojiReducerState as emojis } from '../util/loadRecentEmojis';
import type { MainWindowStatsType } from '../windows/context';
import { getInitialState as stickers } from '../types/Stickers';
import { getThemeType } from '../util/getThemeType';
export function getInitialState({
@ -69,6 +69,16 @@ export function getInitialState({
const theme = getThemeType();
let osName: 'windows' | 'macos' | 'linux' | undefined;
if (OS.isWindows()) {
osName = 'windows';
} else if (OS.isMacOS()) {
osName = 'macos';
} else if (OS.isLinux()) {
osName = 'linux';
}
return {
accounts: accounts(),
app: app(),
@ -125,24 +135,25 @@ export function getInitialState({
updates: updates(),
user: {
...user(),
attachmentsPath: window.baseAttachmentsPath,
stickersPath: window.baseStickersPath,
tempPath: window.baseTempPath,
regionCode: window.storage.get('regionCode'),
attachmentsPath: window.BasePaths.attachments,
i18n: window.i18n,
interactionMode: window.getInteractionMode(),
isMainWindowFullScreen: mainWindowStats.isFullScreen,
isMainWindowMaximized: mainWindowStats.isMaximized,
localeMessages: window.SignalContext.localeMessages,
menuOptions,
osName,
ourACI,
ourConversationId,
ourDeviceId,
ourNumber,
ourACI,
ourPNI,
platform: window.platform,
i18n: window.i18n,
localeMessages: window.SignalContext.localeMessages,
interactionMode: window.getInteractionMode(),
regionCode: window.storage.get('regionCode'),
stickersPath: window.BasePaths.stickers,
tempPath: window.BasePaths.temp,
theme,
version: window.getVersion(),
isMainWindowMaximized: mainWindowStats.isMaximized,
isMainWindowFullScreen: mainWindowStats.isFullScreen,
menuOptions,
},
username: username(),
};

View File

@ -38,6 +38,18 @@ function renderInbox(): JSX.Element {
const mapStateToProps = (state: StateType) => {
const i18n = getIntl(state);
const { osName } = state.user;
let osClassName = '';
if (osName === 'windows') {
osClassName = 'os-windows';
} else if (osName === 'macos') {
osClassName = 'os-macos';
} else if (osName === 'linux') {
osClassName = 'os-linux';
}
return {
...state.app,
i18n,
@ -46,6 +58,7 @@ const mapStateToProps = (state: StateType) => {
isFullScreen: getIsMainWindowFullScreen(state),
menuOptions: getMenuOptions(state),
hasCustomTitleBar: window.SignalContext.OS.hasCustomTitleBar(),
osClassName,
hideMenuBar: getHideMenuBar(state),
renderCallManager: () => (
<ModalContainer className="module-calling__modal-container">
@ -92,7 +105,7 @@ const mapStateToProps = (state: StateType) => {
void window.SignalContext.executeMenuAction(action);
},
titleBarDoubleClick: (): void => {
window.titleBarDoubleClick();
window.IPC.titleBarDoubleClick();
},
toast: state.toast.toast,
};

View File

@ -87,7 +87,7 @@ async function notifyForCall(
isVideoCall ? 'incomingVideoCall' : 'incomingAudioCall'
),
onNotificationClick: () => {
window.showWindow();
window.IPC.showWindow();
},
silent: false,
});

View File

@ -164,8 +164,10 @@ export function SmartInstallScreen(): ReactElement {
}
onQrCodeScanned();
if (window.CI) {
chooseDeviceNamePromiseWrapperRef.current.resolve(window.CI.deviceName);
if (window.Signal.CI) {
chooseDeviceNamePromiseWrapperRef.current.resolve(
window.Signal.CI.deviceName
);
}
const result = await chooseDeviceNamePromiseWrapperRef.current.promise;
@ -203,7 +205,7 @@ export function SmartInstallScreen(): ReactElement {
confirmNumber
);
window.removeSetupMenuItems();
window.IPC.removeSetupMenuItems();
} catch (error) {
log.error(
'account.registerSecondDevice: got an error',
@ -233,7 +235,7 @@ export function SmartInstallScreen(): ReactElement {
screenSpecificProps: {
i18n,
error: state.error,
quit: () => window.shutdown(),
quit: () => window.IPC.shutdown(),
tryAgain: () => setState(INITIAL_STATE),
},
};

View File

@ -69,8 +69,7 @@ void (async () => {
const openConvo = async (contact: PrimaryDevice): Promise<void> => {
debug('opening conversation', contact.profileName);
const item = leftPane.locator(
'_react=BaseConversationListItem' +
`[title = ${JSON.stringify(contact.profileName)}]`
`[data-testid="${contact.toContact().uuid}"]`
);
await item.click();

View File

@ -122,11 +122,9 @@ void (async () => {
{
const leftPane = window.locator('.left-pane-wrapper');
const item = leftPane.locator(
'_react=BaseConversationListItem' +
`[title = ${JSON.stringify(group.title)}] ` +
`>> text=${LAST_MESSAGE}`
);
const item = leftPane
.locator('.module-conversation-list__item--contact-or-conversation')
.first();
await item.click();
}
@ -141,7 +139,7 @@ void (async () => {
'.composition-area-wrapper, .conversation .ConversationView'
);
const input = composeArea.locator('_react=CompositionInput');
const input = composeArea.locator('[data-testid=CompositionInput]');
debug('entering message text');
await input.type(`my message ${runId}`);
@ -166,9 +164,7 @@ void (async () => {
await server.send(desktop, delivery);
debug('waiting for message state change');
const message = timeline.locator(
`_react=Message[timestamp = ${timestamp}][status = "delivered"]`
);
const message = timeline.locator(`[data-testid="${timestamp}"]`);
await message.waitFor();
if (runId >= DISCARD_COUNT) {

View File

@ -75,9 +75,7 @@ void (async () => {
{
const leftPane = window.locator('.left-pane-wrapper');
const item = leftPane.locator(
'_react=BaseConversationListItem' +
`[title = ${JSON.stringify(first.profileName)}]` +
`>> ${JSON.stringify(LAST_MESSAGE)}`
`[data-testid="${first.toContact().uuid}"]`
);
await item.click();
}
@ -92,7 +90,7 @@ void (async () => {
const composeArea = window.locator(
'.composition-area-wrapper, .conversation .ConversationView'
);
const input = composeArea.locator('_react=CompositionInput');
const input = composeArea.locator('[data-testid=CompositionInput]');
debug('entering message text');
await input.type(`my message ${runId}`);
@ -116,9 +114,7 @@ void (async () => {
await server.send(desktop, delivery);
debug('waiting for message state change');
const message = timeline.locator(
`_react=Message[timestamp = ${timestamp}][status = "delivered"]`
);
const message = timeline.locator(`[data-testid="${timestamp}"]`);
await message.waitFor();
if (runId >= DISCARD_COUNT) {

View File

@ -2,6 +2,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
/* eslint-disable no-await-in-loop, no-console */
import type { PrimaryDevice } from '@signalapp/mock-server';
import { StorageState } from '@signalapp/mock-server';
import type { App } from './fixtures';
@ -23,6 +24,7 @@ void (async () => {
const { phone, server } = bootstrap;
let state = StorageState.getEmpty();
let lastContact: PrimaryDevice | undefined;
for (const [i, profileName] of contactNames.entries()) {
const contact = await server.createPrimaryDevice({
profileName,
@ -39,6 +41,10 @@ void (async () => {
if (i >= contactNames.length - 4) {
state = state.pin(contact);
}
if (i === contactNames.length - 1) {
lastContact = contact;
}
}
await phone.setStorageState(state);
@ -52,8 +58,7 @@ void (async () => {
const leftPane = window.locator('.left-pane-wrapper');
const item = leftPane.locator(
'_react=BaseConversationListItem' +
`[title = ${JSON.stringify(contactNames[contactNames.length - 1])}]`
`[data-testid="${lastContact?.toContact().uuid}"]`
);
await item.waitFor();

View File

@ -89,7 +89,7 @@ export class App {
const window = await this.getWindow();
await window.evaluate(
`window.CI.solveChallenge(${JSON.stringify(response)})`
`window.Signal.CI.solveChallenge(${JSON.stringify(response)})`
);
}
@ -105,7 +105,7 @@ export class App {
const window = await this.getWindow();
const result = await window.evaluate(
`window.CI.waitForEvent(${JSON.stringify(event)})`
`window.Signal.CI.waitForEvent(${JSON.stringify(event)})`
);
return result as T;

View File

@ -52,12 +52,7 @@ describe('pnp/accept gv2 invite', function needsName() {
const leftPane = window.locator('.left-pane-wrapper');
debug('Opening group');
await leftPane
.locator(
'_react=ConversationListItem' +
`[title = ${JSON.stringify(group.title)}]`
)
.click();
await leftPane.locator(`[data-testid="${group.id}"]`).click();
});
afterEach(async function after() {
@ -296,12 +291,7 @@ describe('pnp/accept gv2 invite', function needsName() {
const leftPane = window.locator('.left-pane-wrapper');
debug('Opening new group');
await leftPane
.locator(
'_react=ConversationListItem' +
`[title = ${JSON.stringify(group.title)}]`
)
.click();
await leftPane.locator(`[data-testid="${group.id}"]`).click();
debug('Accepting remote invite');
await second.acceptPniInvite(group, desktop, {

View File

@ -64,13 +64,7 @@ describe('pnp/change number', function needsName() {
]);
debug('opening conversation with the first contact');
await leftPane
.locator(
'_react=ConversationListItem' +
`[title = ${JSON.stringify(first.profileName)}] ` +
' >> "After"'
)
.click();
await leftPane.locator(`[data-testid="${first.toContact().uuid}"]`).click();
debug('done');
});

View File

@ -109,7 +109,7 @@ describe('pnp/merge', function needsName() {
debug('opening conversation with the aci contact');
await leftPane
.locator('_react=ConversationListItem[title = "ACI Contact"]')
.locator(`[data-testid="${pniContact.toContact().uuid}"]`)
.click();
await window.locator('.module-conversation-hero').waitFor();
@ -119,7 +119,9 @@ describe('pnp/merge', function needsName() {
const composeArea = window.locator(
'.composition-area-wrapper, .conversation .ConversationView'
);
const compositionInput = composeArea.locator('_react=CompositionInput');
const compositionInput = composeArea.locator(
'[data-testid=CompositionInput]'
);
await compositionInput.type('Hello ACI');
await compositionInput.press('Enter');
@ -127,7 +129,8 @@ describe('pnp/merge', function needsName() {
debug('opening conversation with the pni contact');
await leftPane
.locator('_react=ConversationListItem[title = "PNI Contact"]')
.locator('.module-conversation-list__item--contact-or-conversation')
.first()
.click();
await window.locator('.module-conversation-hero').waitFor();
@ -152,7 +155,9 @@ describe('pnp/merge', function needsName() {
const composeArea = window.locator(
'.composition-area-wrapper, .conversation .ConversationView'
);
const compositionInput = composeArea.locator('_react=CompositionInput');
const compositionInput = composeArea.locator(
'[data-testid=CompositionInput]'
);
await compositionInput.type('Hello PNI');
await compositionInput.press('Enter');
@ -161,7 +166,7 @@ describe('pnp/merge', function needsName() {
if (finalContact === UUIDKind.ACI) {
debug('switching back to ACI conversation');
await leftPane
.locator('_react=ConversationListItem[title = "ACI Contact"]')
.locator(`[data-testid="${pniContact.toContact().uuid}"]`)
.click();
await window.locator('.module-conversation-hero').waitFor();

View File

@ -80,7 +80,9 @@ describe('pnp/PNI Change', function needsName() {
const leftPane = window.locator('.left-pane-wrapper');
await leftPane
.locator('_react=ConversationListItem[title = "ContactA"]')
.locator(
`[data-testid="${contactA.device.getUUIDByKind(UUIDKind.PNI)}"]`
)
.click();
await window.locator('.module-conversation-hero').waitFor();
@ -102,7 +104,9 @@ describe('pnp/PNI Change', function needsName() {
const composeArea = window.locator(
'.composition-area-wrapper, .conversation .ConversationView'
);
const compositionInput = composeArea.locator('_react=CompositionInput');
const compositionInput = composeArea.locator(
'[data-testid=CompositionInput]'
);
await compositionInput.type('message to contactA');
await compositionInput.press('Enter');
@ -181,7 +185,9 @@ describe('pnp/PNI Change', function needsName() {
const leftPane = window.locator('.left-pane-wrapper');
await leftPane
.locator('_react=ConversationListItem[title = "ContactA"]')
.locator(
`[data-testid="${contactA.device.getUUIDByKind(UUIDKind.PNI)}"]`
)
.click();
await window.locator('.module-conversation-hero').waitFor();
@ -203,7 +209,9 @@ describe('pnp/PNI Change', function needsName() {
const composeArea = window.locator(
'.composition-area-wrapper, .conversation .ConversationView'
);
const compositionInput = composeArea.locator('_react=CompositionInput');
const compositionInput = composeArea.locator(
'[data-testid=CompositionInput]'
);
await compositionInput.type('message to contactA');
await compositionInput.press('Enter');
@ -284,7 +292,9 @@ describe('pnp/PNI Change', function needsName() {
const leftPane = window.locator('.left-pane-wrapper');
await leftPane
.locator('_react=ConversationListItem[title = "ContactA"]')
.locator(
`[data-testid="${contactA.device.getUUIDByKind(UUIDKind.PNI)}"]`
)
.click();
await window.locator('.module-conversation-hero').waitFor();
@ -306,7 +316,9 @@ describe('pnp/PNI Change', function needsName() {
const composeArea = window.locator(
'.composition-area-wrapper, .conversation .ConversationView'
);
const compositionInput = composeArea.locator('_react=CompositionInput');
const compositionInput = composeArea.locator(
'[data-testid=CompositionInput]'
);
await compositionInput.type('message to contactA');
await compositionInput.press('Enter');
@ -366,7 +378,9 @@ describe('pnp/PNI Change', function needsName() {
const composeArea = window.locator(
'.composition-area-wrapper, .conversation .ConversationView'
);
const compositionInput = composeArea.locator('_react=CompositionInput');
const compositionInput = composeArea.locator(
'[data-testid=CompositionInput]'
);
await compositionInput.type('message to contactB');
await compositionInput.press('Enter');
@ -420,7 +434,9 @@ describe('pnp/PNI Change', function needsName() {
const leftPane = window.locator('.left-pane-wrapper');
await leftPane
.locator('_react=ConversationListItem[title = "ContactA"]')
.locator(
`[data-testid="${contactA.device.getUUIDByKind(UUIDKind.PNI)}"]`
)
.click();
await window.locator('.module-conversation-hero').waitFor();
@ -442,7 +458,9 @@ describe('pnp/PNI Change', function needsName() {
const composeArea = window.locator(
'.composition-area-wrapper, .conversation .ConversationView'
);
const compositionInput = composeArea.locator('_react=CompositionInput');
const compositionInput = composeArea.locator(
'[data-testid=CompositionInput]'
);
await compositionInput.type('message to contactA');
await compositionInput.press('Enter');
@ -533,7 +551,9 @@ describe('pnp/PNI Change', function needsName() {
const composeArea = window.locator(
'.composition-area-wrapper, .conversation .ConversationView'
);
const compositionInput = composeArea.locator('_react=CompositionInput');
const compositionInput = composeArea.locator(
'[data-testid=CompositionInput]'
);
await compositionInput.type('second message to contactA');
await compositionInput.press('Enter');

View File

@ -153,10 +153,7 @@ describe('pnp/PNI Signature', function needsName() {
debug('opening conversation with the stranger');
await leftPane
.locator(
'_react=ConversationListItem' +
`[title = ${JSON.stringify(stranger.profileName)}]`
)
.locator(`[data-testid="${stranger.toContact().uuid}"]`)
.click();
debug('Accept conversation from a stranger');
@ -173,7 +170,9 @@ describe('pnp/PNI Signature', function needsName() {
}
debug('Enter first message text');
const compositionInput = composeArea.locator('_react=CompositionInput');
const compositionInput = composeArea.locator(
'[data-testid=CompositionInput]'
);
await compositionInput.type('first');
await compositionInput.press('Enter');
@ -273,11 +272,14 @@ describe('pnp/PNI Signature', function needsName() {
debug('opening conversation with the pni contact');
await leftPane
.locator('_react=ConversationListItem[title = "PNI Contact"]')
.locator('.module-conversation-list__item--contact-or-conversation')
.first()
.click();
debug('Enter a PNI message text');
const compositionInput = composeArea.locator('_react=CompositionInput');
const compositionInput = composeArea.locator(
'[data-testid=CompositionInput]'
);
await compositionInput.type('Hello PNI');
await compositionInput.press('Enter');
@ -313,13 +315,11 @@ describe('pnp/PNI Signature', function needsName() {
debug('Wait for merge to happen');
await leftPane
.locator('_react=ConversationListItem[title = "ACI Contact"]')
.locator(`[data-testid="${pniContact.toContact().uuid}"]`)
.waitFor();
debug('Wait for composition input to clear');
await composeArea
.locator('_react=CompositionInput[draftText = ""]')
.waitFor();
await composeArea.locator('[data-testid=CompositionInput]').waitFor();
debug('Enter an ACI message text');
await compositionInput.type('Hello ACI');

View File

@ -105,9 +105,7 @@ describe('pnp/send gv2 invite', function needsName() {
await leftPane.locator('.module-main-header__compose-icon').click();
await leftPane
.locator('_react=BaseConversationListItem[title = "New group"]')
.click();
await leftPane.locator('[data-testid=CreateNewGroupButton]').click();
debug('inviting ACI member');
@ -116,7 +114,7 @@ describe('pnp/send gv2 invite', function needsName() {
.fill('ACI');
await leftPane
.locator('_react=BaseConversationListItem[title = "ACI Contact"]')
.locator(`[data-testid="${aciContact.toContact().uuid}"]`)
.click();
debug('inviting PNI member');
@ -126,7 +124,9 @@ describe('pnp/send gv2 invite', function needsName() {
.fill('PNI');
await leftPane
.locator('_react=BaseConversationListItem[title = "PNI Contact"]')
.locator(
`[data-testid="${pniContact.device.getUUIDByKind(UUIDKind.PNI)}"]`
)
.click();
await leftPane
@ -180,7 +180,7 @@ describe('pnp/send gv2 invite', function needsName() {
debug('editing group title');
{
const detailsHeader = conversationStack.locator(
'_react=ConversationDetailsHeader'
'[data-testid=ConversationDetailsHeader]'
);
await detailsHeader.locator('button >> "My group"').click();

View File

@ -53,10 +53,7 @@ describe('storage service', function needsName() {
});
await leftPane
.locator(
'_react=ConversationListItem' +
`[title = ${JSON.stringify(firstContact.profileName)}]`
)
.locator(`[data-testid="${firstContact.toContact().uuid}"]`)
.waitFor({ state: 'hidden' });
await leftPane
@ -76,11 +73,7 @@ describe('storage service', function needsName() {
});
await leftPane
.locator(
'_react=ConversationListItem' +
'[isPinned = true]' +
`[title = ${JSON.stringify(firstContact.profileName)}]`
)
.locator(`[data-testid="${firstContact.toContact().uuid}"]`)
.waitFor();
await leftPane
@ -93,10 +86,7 @@ describe('storage service', function needsName() {
const state = await phone.expectStorageState('consistency check');
await leftPane
.locator(
'_react=ConversationListItem' +
`[title = ${JSON.stringify(firstContact.profileName)}]`
)
.locator(`[data-testid="${firstContact.toContact().uuid}"]`)
.click();
const moreButton = conversationStack.locator(

View File

@ -48,11 +48,7 @@ describe('storage service', function needsName() {
debug('wait for first contact to be pinned in the left pane');
await leftPane
.locator(
'_react=ConversationListItem' +
'[isPinned = true] ' +
`[title = ${JSON.stringify(firstContact.profileName)}]`
)
.locator(`[data-testid="${firstContact.toContact().uuid}"]`)
.waitFor();
{
@ -83,11 +79,7 @@ describe('storage service', function needsName() {
debug('wait for last contact to be pinned in the left pane');
await leftPane
.locator(
'_react=ConversationListItem' +
'[isPinned = true] ' +
`[title = ${JSON.stringify(lastContact.profileName)}]`
)
.locator(`[data-testid="${lastContact.toContact().uuid}"]`)
.waitFor({ timeout: durations.MINUTE });
debug('Verifying the final manifest version');

View File

@ -55,11 +55,9 @@ describe('storage service', function needsName() {
const conversationStack = window.locator('.conversation-stack');
debug('Opening conversation with a stranger');
debug(stranger.toContact().uuid);
await leftPane
.locator(
'_react=ConversationListItem' +
`[title = ${JSON.stringify(stranger.profileName)}]`
)
.locator(`[data-testid="${stranger.toContact().uuid}"]`)
.click();
debug("Verify that we stored stranger's profile key");
@ -124,7 +122,7 @@ describe('storage service', function needsName() {
const composeArea = window.locator(
'.composition-area-wrapper, .conversation .ConversationView'
);
const input = composeArea.locator('_react=CompositionInput');
const input = composeArea.locator('[data-testid=CompositionInput]');
await input.type('hello stranger!');
await input.press('Enter');

View File

@ -43,13 +43,7 @@ describe('storage service', function needsName() {
const conversationStack = window.locator('.conversation-stack');
debug('Verifying that the group is pinned on startup');
await leftPane
.locator(
'_react=ConversationListItem' +
'[isPinned = true] ' +
`[title = ${JSON.stringify(group.title)}]`
)
.waitFor();
await leftPane.locator(`[data-testid="${group.id}"]`).waitFor();
debug('Unpinning group via storage service');
{
@ -60,24 +54,14 @@ describe('storage service', function needsName() {
timestamp: bootstrap.getTimestamp(),
});
await leftPane
.locator(
'_react=ConversationListItem' +
'[isPinned = false] ' +
`[title = ${JSON.stringify(group.title)}]`
)
.waitFor();
await leftPane.locator(`[data-testid="${group.id}"]`).waitFor();
}
debug('Pinning group in the app');
{
const state = await phone.expectStorageState('consistency check');
const convo = leftPane.locator(
'_react=ConversationListItem' +
'[isPinned = false] ' +
`[title = ${JSON.stringify(group.title)}]`
);
const convo = leftPane.locator(`[data-testid="${group.id}"]`);
await convo.click();
const moreButton = conversationStack.locator(
@ -120,8 +104,7 @@ describe('storage service', function needsName() {
debug('pinning contact=%d', i);
const convo = leftPane.locator(
'_react=ConversationListItem' +
`[title = ${JSON.stringify(contact.profileName)}]`
`[data-testid="${contact.toContact().uuid}"]`
);
await convo.click();

View File

@ -129,10 +129,7 @@ describe('storage service', function needsName() {
);
await leftPane
.locator(
'_react=ConversationListItem' +
`[title = ${JSON.stringify(firstContact.profileName)}]`
)
.locator(`[data-testid="${firstContact.toContact().uuid}"]`)
.click();
{
@ -235,7 +232,7 @@ describe('storage service', function needsName() {
.click();
const stickerManager = conversationView.locator(
'_react=StickerManagerInner'
'[data-testid=StickerManager]'
);
debug('switching to Installed tab');
@ -265,10 +262,7 @@ describe('storage service', function needsName() {
debug('waiting for sticker pack to become visible');
await stickerManager
.locator(
'_react=StickerManagerPackRowInner' +
`[pack.id="${STICKER_PACKS[0].id.toString('hex')}"]`
)
.locator(`[data-testid="${STICKER_PACKS[0].id.toString('hex')}"]`)
.waitFor();
}
@ -285,10 +279,7 @@ describe('storage service', function needsName() {
debug('waiting for sticker pack to become visible');
await stickerManager
.locator(
'_react=StickerManagerPackRowInner' +
`[pack.id="${STICKER_PACKS[1].id.toString('hex')}"]`
)
.locator(`[data-testid="${STICKER_PACKS[1].id.toString('hex')}"]`)
.waitFor();
debug('waiting for storage service update');

View File

@ -224,7 +224,7 @@ export default class AccountManager extends EventTarget {
}
const url = getProvisioningUrl(uuid, pubKey);
window.CI?.setProvisioningURL(url);
window.Signal.CI?.setProvisioningURL(url);
setProvisioningUrl(url);
request.respond(200, 'OK');

View File

@ -9,6 +9,8 @@ type EntryType = Readonly<{
callback(): void;
}>;
let startupProcessingQueue: StartupQueue | undefined;
export class StartupQueue {
private readonly map = new Map<string, EntryType>();
@ -38,4 +40,21 @@ export class StartupQueue {
}
}
}
static initialize(): void {
startupProcessingQueue = new StartupQueue();
}
static isReady(): boolean {
return Boolean(startupProcessingQueue);
}
static add(id: string, value: number, f: () => void): void {
startupProcessingQueue?.add(id, value, f);
}
static flush(): void {
startupProcessingQueue?.flush();
startupProcessingQueue = undefined;
}
}

View File

@ -0,0 +1,74 @@
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { MessageAttributesType } from '../model-types.d';
import type { MessageModel } from '../models/messages';
import * as log from '../logging/log';
import { isMoreRecentThan } from './timestamp';
const MAX_ATTACHMENT_DOWNLOAD_AGE = 3600 * 72 * 1000;
const MAX_ATTACHMENT_MSGS_TO_DOWNLOAD = 250;
let isEnabled = true;
let attachmentDownloadQueue: Array<MessageModel> | undefined = [];
export function shouldUseAttachmentDownloadQueue(): boolean {
return isEnabled;
}
export function addToAttachmentDownloadQueue(
idLog: string,
message: MessageModel
): void {
if (!attachmentDownloadQueue) {
return;
}
attachmentDownloadQueue.unshift(message);
log.info(
`${idLog}: Adding to attachmentDownloadQueue`,
message.get('sent_at')
);
}
export async function flushAttachmentDownloadQueue(): Promise<void> {
if (!attachmentDownloadQueue) {
return;
}
// NOTE: ts/models/messages.ts expects this global to become undefined
// once we stop processing the queue.
isEnabled = false;
const attachmentsToDownload = attachmentDownloadQueue.filter(
(message, index) =>
index <= MAX_ATTACHMENT_MSGS_TO_DOWNLOAD ||
isMoreRecentThan(message.getReceivedAt(), MAX_ATTACHMENT_DOWNLOAD_AGE) ||
// Stickers and long text attachments has to be downloaded for UI
// to display the message properly.
message.hasRequiredAttachmentDownloads()
);
log.info(
'Downloading recent attachments of total attachments',
attachmentsToDownload.length,
attachmentDownloadQueue.length
);
const messagesWithDownloads = await Promise.all(
attachmentsToDownload.map(message => message.queueAttachmentDownloads())
);
const messagesToSave: Array<MessageAttributesType> = [];
messagesWithDownloads.forEach((shouldSave, messageKey) => {
if (shouldSave) {
const message = attachmentsToDownload[messageKey];
messagesToSave.push(message.attributes);
}
});
await window.Signal.Data.saveMessages(messagesToSave, {
ourUuid: window.storage.user.getCheckedUuid().toString(),
});
attachmentDownloadQueue = undefined;
}

View File

@ -2,11 +2,11 @@
// SPDX-License-Identifier: AGPL-3.0-only
export async function requestCameraPermissions(): Promise<boolean> {
if (!(await window.getMediaCameraPermissions())) {
await window.showPermissionsPopup(true, true);
if (!(await window.IPC.getMediaCameraPermissions())) {
await window.IPC.showPermissionsPopup(true, true);
// Check the setting again (from the source of truth).
return window.getMediaCameraPermissions();
return window.IPC.getMediaCameraPermissions();
}
return true;

View File

@ -308,15 +308,15 @@ export function createIPCEvents(
getHideMenuBar: () => window.storage.get('hide-menu-bar'),
setHideMenuBar: value => {
const promise = window.storage.put('hide-menu-bar', value);
window.setAutoHideMenuBar(value);
window.setMenuBarVisibility(!value);
window.IPC.setAutoHideMenuBar(value);
window.IPC.setMenuBarVisibility(!value);
return promise;
},
getSystemTraySetting: () =>
parseSystemTraySetting(window.storage.get('system-tray-setting')),
setSystemTraySetting: value => {
const promise = window.storage.put('system-tray-setting', value);
window.updateSystemTraySetting(value);
window.IPC.updateSystemTraySetting(value);
return promise;
},
@ -361,9 +361,9 @@ export function createIPCEvents(
setAlwaysRelayCalls: value =>
window.storage.put('always-relay-calls', value),
getAutoLaunch: () => window.getAutoLaunch(),
getAutoLaunch: () => window.IPC.getAutoLaunch(),
setAutoLaunch: async (value: boolean) => {
return window.setAutoLaunch(value);
return window.IPC.setAutoLaunch(value);
},
isPhoneNumberSharingEnabled: () => isPhoneNumberSharingEnabled(),
@ -526,8 +526,8 @@ export function createIPCEvents(
showWhatsNewModal();
},
getMediaPermissions: window.getMediaPermissions,
getMediaCameraPermissions: window.getMediaCameraPermissions,
getMediaPermissions: window.IPC.getMediaPermissions,
getMediaCameraPermissions: window.IPC.getMediaCameraPermissions,
persistZoomFactor: zoomFactor =>
window.storage.put('zoomFactor', zoomFactor),

View File

@ -212,7 +212,7 @@ function maybeShowDecryptionToast(
kind: ToastInternalErrorKind.DecryptionError,
deviceId,
name,
onShowDebugLog: () => window.showDebugLog(),
onShowDebugLog: () => window.IPC.showDebugLog(),
});
}

View File

@ -38,7 +38,6 @@ import {
sessionStructureToBytes,
} from './sessionTranslation';
import * as zkgroup from './zkgroup';
import { StartupQueue } from './StartupQueue';
import { sendToGroup, sendContentMessageToGroup } from './sendToGroup';
import { RetryPlaceholders } from './retryPlaceholders';
import * as expirationTimer from './expirationTimer';
@ -47,7 +46,6 @@ import { MessageController } from './MessageController';
export {
GoogleChrome,
Registration,
StartupQueue,
arrayBufferToObjectURL,
combineNames,
createBatcher,

View File

@ -4,12 +4,12 @@
export async function requestMicrophonePermissions(
forCalling: boolean
): Promise<boolean> {
const microphonePermission = await window.getMediaPermissions();
const microphonePermission = await window.IPC.getMediaPermissions();
if (!microphonePermission) {
await window.showPermissionsPopup(forCalling, false);
await window.IPC.showPermissionsPopup(forCalling, false);
// Check the setting again (from the source of truth).
return window.getMediaPermissions();
return window.IPC.getMediaPermissions();
}
return true;

View File

@ -1,12 +1,9 @@
// Copyright 2015 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
// This file is here temporarily while we're switching off of Backbone into
// React. In the future, and in React-land, please just import and use
// ConfirmationDialog directly. This is the thin API layer to bridge the gap
// while we convert things over. Please delete this file once all usages are
// ported over. Note: this file cannot have any imports/exports since it is
// being included in a <script /> tag.
import React from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
import { ConfirmationDialog } from '../components/ConfirmationDialog';
type ConfirmationDialogViewProps = {
onTopOfEverything?: boolean;
@ -27,7 +24,7 @@ function removeConfirmationDialog() {
return;
}
window.ReactDOM.unmountComponentAtNode(confirmationDialogViewNode);
unmountComponentAtNode(confirmationDialogViewNode);
document.body.removeChild(confirmationDialogViewNode);
if (
@ -39,7 +36,9 @@ function removeConfirmationDialog() {
confirmationDialogViewNode = undefined;
}
function showConfirmationDialog(options: ConfirmationDialogViewProps) {
export function showConfirmationDialog(
options: ConfirmationDialogViewProps
): void {
if (confirmationDialogViewNode) {
removeConfirmationDialog();
}
@ -49,9 +48,8 @@ function showConfirmationDialog(options: ConfirmationDialogViewProps) {
confirmationDialogPreviousFocus = document.activeElement as HTMLElement;
window.ReactDOM.render(
// eslint-disable-next-line react/react-in-jsx-scope, react/jsx-no-undef
<window.Signal.Components.ConfirmationDialog
render(
<ConfirmationDialog
dialogName={options.dialogName}
onTopOfEverything={options.onTopOfEverything}
actions={[
@ -78,5 +76,3 @@ function showConfirmationDialog(options: ConfirmationDialogViewProps) {
confirmationDialogViewNode
);
}
window.showConfirmationDialog = showConfirmationDialog;

243
ts/window.d.ts vendored
View File

@ -7,10 +7,8 @@ import type { Store } from 'redux';
import type * as Backbone from 'backbone';
import type PQueue from 'p-queue/dist';
import type { assert } from 'chai';
import type * as Mustache from 'mustache';
import type { PhoneNumber, PhoneNumberFormat } from 'google-libphonenumber';
import type { imageToBlurHash } from './util/imageToBlurHash';
import type * as Util from './util';
import type {
ConversationModelCollectionType,
@ -22,6 +20,7 @@ import type {
ChallengeHandler,
IPCRequest as IPCChallengeRequest,
} from './challenge';
import type AccountManager from './textsecure/AccountManager';
import type { WebAPIConnectType } from './textsecure/WebAPI';
import type { CallingClass } from './services/calling';
import type * as StorageService from './services/storage';
@ -37,36 +36,12 @@ import type { ConversationController } from './ConversationController';
import type { ReduxActions } from './state/types';
import type { createStore } from './state/createStore';
import type { createApp } from './state/roots/createApp';
import type { createSafetyNumberViewer } from './state/roots/createSafetyNumberViewer';
import type * as appDuck from './state/ducks/app';
import type * as callingDuck from './state/ducks/calling';
import type * as conversationsDuck from './state/ducks/conversations';
import type * as emojisDuck from './state/ducks/emojis';
import type * as expirationDuck from './state/ducks/expiration';
import type * as itemsDuck from './state/ducks/items';
import type * as linkPreviewsDuck from './state/ducks/linkPreviews';
import type * as networkDuck from './state/ducks/network';
import type * as updatesDuck from './state/ducks/updates';
import type * as userDuck from './state/ducks/user';
import type * as searchDuck from './state/ducks/search';
import type * as stickersDuck from './state/ducks/stickers';
import type * as conversationsSelectors from './state/selectors/conversations';
import type * as searchSelectors from './state/selectors/search';
import type AccountManager from './textsecure/AccountManager';
import type Data from './sql/Client';
import type { MessageModel } from './models/messages';
import type { ConversationModel } from './models/conversations';
import type { BatcherType } from './util/batcher';
import type { AttachmentList } from './components/conversation/AttachmentList';
import type { ChatColorPicker } from './components/ChatColorPicker';
import type { ConfirmationDialog } from './components/ConfirmationDialog';
import type { ContactModal } from './components/conversation/ContactModal';
import type { MessageDetail } from './components/conversation/MessageDetail';
import type { Quote } from './components/conversation/Quote';
import type { StagedLinkPreview } from './components/conversation/StagedLinkPreview';
import type { DisappearingTimeDialog } from './components/DisappearingTimeDialog';
import type { SignalProtocolStore } from './SignalProtocolStore';
import type { StartupQueue } from './util/StartupQueue';
import type { SocketStatus } from './types/SocketStatus';
import type SyncRequest from './textsecure/SyncRequest';
import type { MessageController } from './util/MessageController';
@ -75,49 +50,49 @@ import type { SystemTraySetting } from './types/SystemTraySetting';
import type { UUID } from './types/UUID';
import type { Address } from './types/Address';
import type { QualifiedAddress } from './types/QualifiedAddress';
import type { CI } from './CI';
import type { CIType } from './CI';
import type { IPCEventsType } from './util/createIPCEvents';
import type { SignalContextType } from './windows/context';
import type * as Message2 from './types/Message2';
import type { initializeMigrations } from './signal';
import type { WebAudioRecorder } from './WebAudioRecorder';
export { Long } from 'long';
// Synced with the type in ts/shims/showConfirmationDialog
// we are duplicating it here because that file cannot import/export.
type ConfirmationDialogViewProps = {
dialogName: string;
cancelText?: string;
confirmStyle?: 'affirmative' | 'negative';
message: string;
okText: string;
reject?: (error: Error) => void;
resolve: () => void;
export type IPCType = {
addSetupMenuItems: () => void;
closeAbout: () => void;
crashReports: {
getCount: () => Promise<number>;
upload: () => Promise<void>;
erase: () => Promise<void>;
};
drawAttention: () => void;
getAutoLaunch: () => Promise<boolean>;
getBuiltInImages: () => Promise<Array<string>>;
getMediaCameraPermissions: () => Promise<boolean>;
getMediaPermissions: () => Promise<boolean>;
logAppLoadedEvent?: (options: { processedCount?: number }) => void;
readyForUpdates: () => void;
removeSetupMenuItems: () => unknown;
restart: () => void;
setAutoHideMenuBar: (value: boolean) => void;
setAutoLaunch: (value: boolean) => Promise<void>;
setBadgeCount: (count: number) => void;
setMenuBarVisibility: (value: boolean) => void;
showDebugLog: () => void;
showPermissionsPopup: (
forCalling: boolean,
forCamera: boolean
) => Promise<void>;
showSettings: () => void;
showWindow: () => void;
shutdown: () => void;
titleBarDoubleClick: () => void;
updateSystemTraySetting: (value: SystemTraySetting) => void;
updateTrayIcon: (count: number) => void;
};
export declare class WebAudioRecorderClass {
constructor(
node: GainNode,
options: {
encoding: string;
workerDir: string;
options?: { timeLimit?: number };
}
);
// Callbacks
onComplete?: (recorder: WebAudioRecorderClass, blob: Blob) => unknown;
onError?: (recorder: WebAudioRecorderClass, error: Error) => unknown;
onTimeout?: () => unknown;
// Class properties
startRecording: () => unknown;
finishRecording: () => unknown;
isRecording: () => boolean;
cancelRecording: () => unknown;
worker: Worker;
}
export type SignalCoreType = {
Crypto: typeof Crypto;
Curve: typeof Curve;
@ -142,43 +117,20 @@ export type SignalCoreType = {
};
Util: typeof Util;
Components: {
AttachmentList: typeof AttachmentList;
ChatColorPicker: typeof ChatColorPicker;
ConfirmationDialog: typeof ConfirmationDialog;
ContactModal: typeof ContactModal;
DisappearingTimeDialog: typeof DisappearingTimeDialog;
MessageDetail: typeof MessageDetail;
Quote: typeof Quote;
StagedLinkPreview: typeof StagedLinkPreview;
};
OS: typeof OS;
State: {
createStore: typeof createStore;
Roots: {
createApp: typeof createApp;
createSafetyNumberViewer: typeof createSafetyNumberViewer;
};
Ducks: {
app: typeof appDuck;
calling: typeof callingDuck;
conversations: typeof conversationsDuck;
emojis: typeof emojisDuck;
expiration: typeof expirationDuck;
items: typeof itemsDuck;
linkPreviews: typeof linkPreviewsDuck;
network: typeof networkDuck;
updates: typeof updatesDuck;
user: typeof userDuck;
search: typeof searchDuck;
stickers: typeof stickersDuck;
};
Selectors: {
conversations: typeof conversationsSelectors;
search: typeof searchSelectors;
};
};
conversationControllerStart: () => void;
challengeHandler?: ChallengeHandler;
// Test only
CI?: CIType;
};
declare global {
@ -190,48 +142,12 @@ declare global {
// Used for sticker creator localization
localeMessages: { [key: string]: { message: string } };
// Note: used in background.html, and not type-checked
startApp: () => void;
preloadStartTime: number;
preloadEndTime: number;
removeSetupMenuItems: () => unknown;
showPermissionsPopup: (
forCalling: boolean,
forCamera: boolean
) => Promise<void>;
FontFace: typeof FontFace;
$: typeof jQuery;
imageToBlurHash: typeof imageToBlurHash;
isBehindProxy: () => boolean;
getAutoLaunch: () => Promise<boolean>;
setAutoLaunch: (value: boolean) => Promise<void>;
Mustache: typeof Mustache;
WebAudioRecorder: typeof WebAudioRecorderClass;
addSetupMenuItems: () => void;
attachmentDownloadQueue: Array<MessageModel> | undefined;
startupProcessingQueue: StartupQueue | undefined;
baseAttachmentsPath: string;
baseStickersPath: string;
baseTempPath: string;
baseDraftPath: string;
closeAbout: () => void;
crashReports: {
getCount: () => Promise<number>;
upload: () => Promise<void>;
erase: () => Promise<void>;
};
drawAttention: () => void;
enterKeyboardMode: () => void;
enterMouseMode: () => void;
getAccountManager: () => AccountManager;
getAppInstance: () => string | undefined;
getBuiltInImages: () => Promise<Array<string>>;
getConversations: () => ConversationModelCollectionType;
getBuildCreation: () => number;
getEnvironment: typeof getEnvironment;
@ -239,8 +155,6 @@ declare global {
getHostName: () => string;
getInteractionMode: () => 'mouse' | 'keyboard';
getLocale: () => string;
getMediaCameraPermissions: () => Promise<boolean>;
getMediaPermissions: () => Promise<boolean>;
getServerPublicParams: () => string;
getSfuUrl: () => string;
getSocketStatus: () => SocketStatus;
@ -248,11 +162,8 @@ declare global {
getTitle: () => string;
waitForEmptyEventQueue: () => Promise<void>;
getVersion: () => string;
i18n: LocalizerType;
isAfterVersion: (version: string, anotherVersion: string) => boolean;
isBeforeVersion: (version: string, anotherVersion: string) => boolean;
isFullScreen: () => boolean;
isMaximized: () => boolean;
initialTheme?: ThemeType;
libphonenumberInstance: {
parse: (number: string) => PhoneNumber;
@ -261,66 +172,74 @@ declare global {
};
libphonenumberFormat: typeof PhoneNumberFormat;
nodeSetImmediate: typeof setImmediate;
onFullScreenChange: (fullScreen: boolean, maximized: boolean) => void;
platform: string;
preloadedImages: Array<HTMLImageElement>;
reduxActions: ReduxActions;
reduxStore: Store<StateType>;
restart: () => void;
setImmediate: typeof setImmediate;
showWindow: () => void;
showSettings: () => void;
shutdown: () => void;
showDebugLog: () => void;
sendChallengeRequest: (request: IPCChallengeRequest) => void;
setAutoHideMenuBar: (value: boolean) => void;
setBadgeCount: (count: number) => void;
setMenuBarVisibility: (value: boolean) => void;
updateSystemTraySetting: (value: SystemTraySetting) => void;
showConfirmationDialog: (options: ConfirmationDialogViewProps) => void;
showKeyboardShortcuts: () => void;
storage: Storage;
systemTheme: ThemeType;
textsecure: typeof textsecure;
titleBarDoubleClick: () => void;
updateTrayIcon: (count: number) => void;
Backbone: typeof Backbone;
CI?: CI;
Accessibility: {
reducedMotionSetting: boolean;
};
Signal: SignalCoreType;
getServerTrustRoot: () => string;
logAuthenticatedConnect?: () => void;
// ========================================================================
// The types below have been somewhat organized. See DESKTOP-4801
// ========================================================================
// Backbone
Backbone: typeof Backbone;
ConversationController: ConversationController;
Events: IPCEventsType;
FontFace: typeof FontFace;
MessageController: MessageController;
SignalProtocolStore: typeof SignalProtocolStore;
WebAPI: WebAPIConnectType;
WebAudioRecorder: typeof WebAudioRecorder;
Whisper: WhisperType;
getSignalProtocolStore: () => SignalProtocolStore;
i18n: LocalizerType;
// Note: used in background.html, and not type-checked
startApp: () => void;
textsecure: typeof textsecure;
getServerTrustRoot: () => string;
readyForUpdates: () => void;
logAppLoadedEvent?: (options: { processedCount?: number }) => void;
logAuthenticatedConnect?: () => void;
// IPC
IPC: IPCType;
// Runtime Flags
isShowingModal?: boolean;
// State
reduxActions: ReduxActions;
reduxStore: Store<StateType>;
// Feature Flags
GV2_ENABLE_SINGLE_CHANGE_PROCESSING: boolean;
GV2_ENABLE_CHANGE_PROCESSING: boolean;
GV2_ENABLE_STATE_PROCESSING: boolean;
GV2_ENABLE_PRE_JOIN_FETCH: boolean;
GV2_MIGRATION_DISABLE_ADD: boolean;
GV2_MIGRATION_DISABLE_INVITE: boolean;
Flags: {
GV2_ENABLE_SINGLE_CHANGE_PROCESSING: boolean;
GV2_ENABLE_CHANGE_PROCESSING: boolean;
GV2_ENABLE_STATE_PROCESSING: boolean;
GV2_ENABLE_PRE_JOIN_FETCH: boolean;
GV2_MIGRATION_DISABLE_ADD: boolean;
GV2_MIGRATION_DISABLE_INVITE: boolean;
};
RETRY_DELAY: boolean;
// Paths
BasePaths: {
attachments: string;
draft: string;
stickers: string;
temp: string;
};
// Context Isolation
// TODO DESKTOP-4801
SignalContext: SignalContextType;
// Used only in preload to calculate load time
preloadStartTime: number;
preloadEndTime: number;
// Test only
RETRY_DELAY: boolean;
assert: typeof assert;
testUtilities: {
onComplete: (info: unknown) => void;

View File

@ -3,8 +3,9 @@
import { ipcRenderer as ipc } from 'electron';
import * as semver from 'semver';
import { mapValues, noop } from 'lodash';
import { mapValues } from 'lodash';
import type { IPCType } from '../../window.d';
import { parseIntWithFallback } from '../../util/parseIntWithFallback';
import { UUIDKind } from '../../types/UUID';
import { ThemeType } from '../../types/Util';
@ -22,13 +23,16 @@ window.i18n = SignalContext.i18n;
const { config } = window.SignalContext;
// Flags for testing
window.GV2_ENABLE_SINGLE_CHANGE_PROCESSING = true;
window.GV2_ENABLE_CHANGE_PROCESSING = true;
window.GV2_ENABLE_STATE_PROCESSING = true;
window.GV2_ENABLE_PRE_JOIN_FETCH = true;
const Flags = {
GV2_ENABLE_CHANGE_PROCESSING: true,
GV2_ENABLE_PRE_JOIN_FETCH: true,
GV2_ENABLE_SINGLE_CHANGE_PROCESSING: true,
GV2_ENABLE_STATE_PROCESSING: true,
GV2_MIGRATION_DISABLE_ADD: false,
GV2_MIGRATION_DISABLE_INVITE: false,
};
window.GV2_MIGRATION_DISABLE_ADD = false;
window.GV2_MIGRATION_DISABLE_INVITE = false;
window.Flags = Flags;
window.RETRY_DELAY = false;
@ -55,9 +59,6 @@ window.getExpiration = () => {
}
return localBuildExpiration;
};
window.Accessibility = {
reducedMotionSetting: Boolean(config.reducedMotionSetting),
};
window.getHostName = () => config.hostname;
window.getServerTrustRoot = () => config.serverTrustRoot;
window.getServerPublicParams = () => config.serverPublicParams;
@ -78,13 +79,78 @@ if (config.theme === 'light') {
window.initialTheme = ThemeType.dark;
}
window.getAutoLaunch = () => {
return ipc.invoke('get-auto-launch');
};
window.setAutoLaunch = value => {
return ipc.invoke('set-auto-launch', value);
const IPC: IPCType = {
addSetupMenuItems: () => ipc.send('add-setup-menu-items'),
closeAbout: () => ipc.send('close-about'),
crashReports: {
getCount: () => ipc.invoke('crash-reports:get-count'),
upload: () => ipc.invoke('crash-reports:upload'),
erase: () => ipc.invoke('crash-reports:erase'),
},
drawAttention: () => {
log.info('draw attention');
ipc.send('draw-attention');
},
getAutoLaunch: () => ipc.invoke('get-auto-launch'),
getBuiltInImages: () =>
new Promise((resolve, reject) => {
ipc.once('get-success-built-in-images', (_event, error, value) => {
if (error) {
return reject(new Error(error));
}
return resolve(value);
});
ipc.send('get-built-in-images');
}),
getMediaPermissions: () => ipc.invoke('settings:get:mediaPermissions'),
getMediaCameraPermissions: () =>
ipc.invoke('settings:get:mediaCameraPermissions'),
logAppLoadedEvent: ({ processedCount }) =>
ipc.send('signal-app-loaded', {
preloadTime: window.preloadEndTime - window.preloadStartTime,
connectTime: preloadConnectTime - window.preloadEndTime,
processedCount,
}),
readyForUpdates: () => ipc.send('ready-for-updates'),
removeSetupMenuItems: () => ipc.send('remove-setup-menu-items'),
restart: () => {
log.info('restart');
ipc.send('restart');
},
setAutoHideMenuBar: autoHide => ipc.send('set-auto-hide-menu-bar', autoHide),
setAutoLaunch: value => ipc.invoke('set-auto-launch', value),
setBadgeCount: count => ipc.send('set-badge-count', count),
setMenuBarVisibility: visibility =>
ipc.send('set-menu-bar-visibility', visibility),
showDebugLog: () => {
log.info('showDebugLog');
ipc.send('show-debug-log');
},
showPermissionsPopup: (forCalling, forCamera) =>
ipc.invoke('show-permissions-popup', forCalling, forCamera),
showSettings: () => ipc.send('show-settings'),
showWindow: () => {
log.info('show window');
ipc.send('show-window');
},
shutdown: () => {
log.info('shutdown');
ipc.send('shutdown');
},
titleBarDoubleClick: () => {
ipc.send('title-bar-double-click');
},
updateSystemTraySetting: (
systemTraySetting /* : Readonly<SystemTraySetting> */
) => {
void ipc.invoke('update-system-tray-setting', systemTraySetting);
},
updateTrayIcon: unreadCount => ipc.send('update-tray-icon', unreadCount),
};
window.IPC = IPC;
window.isBeforeVersion = (toCheck, baseVersion) => {
try {
return semver.lt(toCheck, baseVersion);
@ -108,8 +174,6 @@ window.isAfterVersion = (toCheck, baseVersion) => {
}
};
window.setBadgeCount = count => ipc.send('set-badge-count', count);
let preloadConnectTime = 0;
window.logAuthenticatedConnect = () => {
if (preloadConnectTime === 0) {
@ -117,13 +181,6 @@ window.logAuthenticatedConnect = () => {
}
};
window.logAppLoadedEvent = ({ processedCount }) =>
ipc.send('signal-app-loaded', {
preloadTime: window.preloadEndTime - window.preloadStartTime,
connectTime: preloadConnectTime - window.preloadEndTime,
processedCount,
});
// We never do these in our code, so we'll prevent it everywhere
window.open = () => null;
@ -133,50 +190,6 @@ if (!config.enableCI && config.environment !== 'test') {
window.eval = global.eval = () => null;
}
window.drawAttention = () => {
log.info('draw attention');
ipc.send('draw-attention');
};
window.showWindow = () => {
log.info('show window');
ipc.send('show-window');
};
window.titleBarDoubleClick = () => {
ipc.send('title-bar-double-click');
};
window.setAutoHideMenuBar = autoHide =>
ipc.send('set-auto-hide-menu-bar', autoHide);
window.setMenuBarVisibility = visibility =>
ipc.send('set-menu-bar-visibility', visibility);
window.updateSystemTraySetting = (
systemTraySetting /* : Readonly<SystemTraySetting> */
) => {
void ipc.invoke('update-system-tray-setting', systemTraySetting);
};
window.restart = () => {
log.info('restart');
ipc.send('restart');
};
window.shutdown = () => {
log.info('shutdown');
ipc.send('shutdown');
};
window.showDebugLog = () => {
log.info('showDebugLog');
ipc.send('show-debug-log');
};
window.closeAbout = () => ipc.send('close-about');
window.readyForUpdates = () => ipc.send('ready-for-updates');
window.updateTrayIcon = unreadCount =>
ipc.send('update-tray-icon', unreadCount);
ipc.on('additional-log-data-request', async event => {
const ourConversation = window.ConversationController.getOurConversation();
const ourCapabilities = ourConversation
@ -238,10 +251,14 @@ ipc.on('power-channel:lock-screen', () => {
});
ipc.on('window:set-window-stats', (_event, stats) => {
if (!window.Whisper.events) {
if (!window.reduxActions) {
return;
}
window.Whisper.events.trigger('setWindowStats', stats);
window.reduxActions.user.userChanged({
isMainWindowMaximized: stats.isMaximized,
isMainWindowFullScreen: stats.isFullScreen,
});
});
ipc.on('window:set-menu-options', (_event, options) => {
@ -253,28 +270,8 @@ ipc.on('window:set-menu-options', (_event, options) => {
window.sendChallengeRequest = request => ipc.send('challenge:request', request);
{
let isFullScreen = Boolean(config.isMainWindowFullScreen);
let isMaximized = Boolean(config.isMainWindowMaximized);
window.isFullScreen = () => isFullScreen;
window.isMaximized = () => isMaximized;
// This is later overwritten.
window.onFullScreenChange = noop;
ipc.on('window:set-window-stats', (_event, stats) => {
isFullScreen = Boolean(stats.isFullScreen);
isMaximized = Boolean(stats.isMaximized);
window.onFullScreenChange(isFullScreen, isMaximized);
});
}
// Settings-related events
window.showSettings = () => ipc.send('show-settings');
window.showPermissionsPopup = (forCalling, forCamera) =>
ipc.invoke('show-permissions-popup', forCalling, forCamera);
ipc.on('show-keyboard-shortcuts', () => {
window.Events.showKeyboardShortcuts();
});
@ -285,18 +282,6 @@ ipc.on('remove-dark-overlay', () => {
window.Events.removeDarkOverlay();
});
window.getBuiltInImages = () =>
new Promise((resolve, reject) => {
ipc.once('get-success-built-in-images', (_event, error, value) => {
if (error) {
return reject(new Error(error));
}
return resolve(value);
});
ipc.send('get-built-in-images');
});
ipc.on('delete-all-data', async () => {
const { deleteAllData } = window.Events;
if (!deleteAllData) {
@ -373,6 +358,3 @@ ipc.on('show-release-notes', () => {
showReleaseNotes();
}
});
window.addSetupMenuItems = () => ipc.send('add-setup-menu-items');
window.removeSetupMenuItems = () => ipc.send('remove-setup-menu-items');

View File

@ -9,7 +9,6 @@ import * as moment from 'moment';
import 'moment/min/locales.min';
import { textsecure } from '../../textsecure';
import { imageToBlurHash } from '../../util/imageToBlurHash';
import * as Attachments from '../attachments';
import { setup } from '../../signal';
import { addSensitivePath } from '../../util/privacy';
@ -38,7 +37,6 @@ window.WebAPI = window.textsecure.WebAPI.initialize({
version: config.version,
});
window.imageToBlurHash = imageToBlurHash;
window.libphonenumberInstance = PhoneNumberUtil.getInstance();
window.libphonenumberFormat = PhoneNumberFormat;
@ -56,12 +54,14 @@ moment.updateLocale(locale, {
moment.locale(locale);
const userDataPath = SignalContext.getPath('userData');
window.baseAttachmentsPath = Attachments.getPath(userDataPath);
window.baseStickersPath = Attachments.getStickersPath(userDataPath);
window.baseTempPath = Attachments.getTempPath(userDataPath);
window.baseDraftPath = Attachments.getDraftPath(userDataPath);
window.BasePaths = {
attachments: Attachments.getPath(userDataPath),
draft: Attachments.getDraftPath(userDataPath),
stickers: Attachments.getStickersPath(userDataPath),
temp: Attachments.getTempPath(userDataPath),
};
addSensitivePath(window.baseAttachmentsPath);
addSensitivePath(window.BasePaths.attachments);
if (config.crashDumpsPath) {
addSensitivePath(config.crashDumpsPath);
}

View File

@ -13,6 +13,6 @@ if (config.environment === 'test') {
}
if (config.enableCI) {
console.log('Importing CI infrastructure...');
const { CI } = require('../../CI');
window.CI = new CI(window.getTitle());
const { getCI } = require('../../CI');
window.Signal.CI = getCI(window.getTitle());
}

View File

@ -1,13 +1,23 @@
// Copyright 2017 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { clone } from 'lodash';
import { contextBridge } from 'electron';
import * as log from '../../logging/log';
import { SignalContext } from '../context';
import './phase1-ipc';
import '../preload';
import './phase2-dependencies';
import './phase3-post-signal';
import './phase4-test';
import '../../backbone/reliable_trigger';
import { WebAudioRecorder } from '../../WebAudioRecorder';
import { getSignalProtocolStore } from '../../SignalProtocolStore';
import { start as startConversationController } from '../../ConversationController';
import { MessageController } from '../../util/MessageController';
window.addEventListener('contextmenu', e => {
const node = e.target as Element | null;
@ -27,3 +37,122 @@ window.addEventListener('contextmenu', e => {
if (window.SignalContext.config.proxyUrl) {
log.info('Using provided proxy url');
}
const isTestElectron = process.env.TEST_QUIT_ON_COMPLETE;
window.Whisper.events = clone(window.Backbone.Events);
MessageController.install();
startConversationController();
if (isTestElectron) {
window.getSignalProtocolStore = getSignalProtocolStore;
} else {
contextBridge.exposeInMainWorld('SignalContext', SignalContext);
contextBridge.exposeInMainWorld('Backbone', window.Backbone);
contextBridge.exposeInMainWorld('BasePaths', window.BasePaths);
contextBridge.exposeInMainWorld(
'ConversationController',
window.ConversationController
);
contextBridge.exposeInMainWorld('Events', window.Events);
contextBridge.exposeInMainWorld('Flags', window.Flags);
contextBridge.exposeInMainWorld('IPC', window.IPC);
contextBridge.exposeInMainWorld(
'SignalProtocolStore',
window.SignalProtocolStore
);
contextBridge.exposeInMainWorld(
'getSignalProtocolStore',
getSignalProtocolStore
);
contextBridge.exposeInMainWorld(
'MessageController',
window.MessageController
);
contextBridge.exposeInMainWorld('WebAudioRecorder', WebAudioRecorder);
contextBridge.exposeInMainWorld('WebAPI', window.WebAPI);
contextBridge.exposeInMainWorld('Whisper', window.Whisper);
contextBridge.exposeInMainWorld('i18n', window.i18n);
contextBridge.exposeInMainWorld('reduxActions', window.reduxActions);
contextBridge.exposeInMainWorld('reduxStore', window.reduxStore);
contextBridge.exposeInMainWorld('startApp', window.startApp);
contextBridge.exposeInMainWorld('textsecure', window.textsecure);
// TODO DESKTOP-4801
contextBridge.exposeInMainWorld('ROOT_PATH', window.ROOT_PATH);
contextBridge.exposeInMainWorld('Signal', window.Signal);
contextBridge.exposeInMainWorld(
'enterKeyboardMode',
window.enterKeyboardMode
);
contextBridge.exposeInMainWorld('enterMouseMode', window.enterMouseMode);
contextBridge.exposeInMainWorld(
'getAccountManager',
window.getAccountManager
);
contextBridge.exposeInMainWorld('getAppInstance', window.getAppInstance);
contextBridge.exposeInMainWorld('getBuildCreation', window.getBuildCreation);
contextBridge.exposeInMainWorld('getConversations', window.getConversations);
contextBridge.exposeInMainWorld('getEnvironment', window.getEnvironment);
contextBridge.exposeInMainWorld('getExpiration', window.getExpiration);
contextBridge.exposeInMainWorld('getHostName', window.getHostName);
contextBridge.exposeInMainWorld(
'getInteractionMode',
window.getInteractionMode
);
contextBridge.exposeInMainWorld('getLocale', window.getLocale);
contextBridge.exposeInMainWorld(
'getServerPublicParams',
window.getServerPublicParams
);
contextBridge.exposeInMainWorld(
'getServerTrustRoot',
window.getServerTrustRoot
);
contextBridge.exposeInMainWorld('getSfuUrl', window.getSfuUrl);
contextBridge.exposeInMainWorld('getSocketStatus', window.getSocketStatus);
contextBridge.exposeInMainWorld('getSyncRequest', window.getSyncRequest);
contextBridge.exposeInMainWorld('getTitle', window.getTitle);
contextBridge.exposeInMainWorld('getVersion', window.getVersion);
contextBridge.exposeInMainWorld('initialTheme', window.initialTheme);
contextBridge.exposeInMainWorld('isAfterVersion', window.isAfterVersion);
contextBridge.exposeInMainWorld('isBeforeVersion', window.isBeforeVersion);
contextBridge.exposeInMainWorld('isBehindProxy', window.isBehindProxy);
contextBridge.exposeInMainWorld(
'libphonenumberFormat',
window.libphonenumberFormat
);
contextBridge.exposeInMainWorld(
'libphonenumberInstance',
window.libphonenumberInstance
);
contextBridge.exposeInMainWorld('localeMessages', window.localeMessages);
contextBridge.exposeInMainWorld(
'logAuthenticatedConnect',
window.logAuthenticatedConnect
);
contextBridge.exposeInMainWorld('nodeSetImmediate', window.nodeSetImmediate);
contextBridge.exposeInMainWorld('platform', window.platform);
contextBridge.exposeInMainWorld('preloadedImages', window.preloadedImages);
contextBridge.exposeInMainWorld(
'sendChallengeRequest',
window.sendChallengeRequest
);
contextBridge.exposeInMainWorld('setImmediate', window.setImmediate);
contextBridge.exposeInMainWorld(
'showKeyboardShortcuts',
window.showKeyboardShortcuts
);
contextBridge.exposeInMainWorld('storage', window.storage);
contextBridge.exposeInMainWorld('systemTheme', window.systemTheme);
contextBridge.exposeInMainWorld(
'waitForEmptyEventQueue',
window.waitForEmptyEventQueue
);
contextBridge.exposeInMainWorld('assert', window.assert);
contextBridge.exposeInMainWorld('RETRY_DELAY', window.RETRY_DELAY);
contextBridge.exposeInMainWorld('testUtilities', window.testUtilities);
}

View File

@ -1,8 +1,6 @@
// Copyright 2017 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { ipcRenderer as ipc } from 'electron';
import { installCallback, installSetting } from '../util/preload';
// ChatColorPicker redux hookups
@ -71,14 +69,3 @@ installCallback('getAvailableIODevices');
installSetting('preferredAudioInputDevice');
installSetting('preferredAudioOutputDevice');
installSetting('preferredVideoInputDevice');
window.getMediaPermissions = () => ipc.invoke('settings:get:mediaPermissions');
window.getMediaCameraPermissions = () =>
ipc.invoke('settings:get:mediaCameraPermissions');
window.crashReports = {
getCount: () => ipc.invoke('crash-reports:get-count'),
upload: () => ipc.invoke('crash-reports:upload'),
erase: () => ipc.invoke('crash-reports:erase'),
};

View File

@ -2178,10 +2178,10 @@
node-gyp-build "^4.2.3"
uuid "^8.3.0"
"@signalapp/mock-server@2.12.0":
version "2.12.0"
resolved "https://registry.yarnpkg.com/@signalapp/mock-server/-/mock-server-2.12.0.tgz#ca7ed46406603746d2cb49349d3fb843fe4b45eb"
integrity sha512-d6OFulnOWG0U3Xj0ChBGHThyBlno54Va+Cb/E0ggEx7PhD1Y9GX8gLc0V8HHP203b+1JThX4SdK03dpsOILxfQ==
"@signalapp/mock-server@2.12.1":
version "2.12.1"
resolved "https://registry.yarnpkg.com/@signalapp/mock-server/-/mock-server-2.12.1.tgz#456ee3c9458363d333bd803910f874d388f28d04"
integrity sha512-c8ndwTtDoRPRZAWjbBeVH79DQLA4UvKPztgJUqtDOqFCju1+NMKbjxrK+v1PJQvhhOMANbG6Z3qTCl4UGcm/zQ==
dependencies:
"@signalapp/libsignal-client" "^0.20.0"
debug "^4.3.2"
@ -4032,11 +4032,6 @@
resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197"
integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==
"@types/mustache@4.1.2":
version "4.1.2"
resolved "https://registry.yarnpkg.com/@types/mustache/-/mustache-4.1.2.tgz#d0e158013c81674a5b6d8780bc3fe234e1804eaf"
integrity sha512-c4OVMMcyodKQ9dpwBwh3ofK9P6U9ZktKU9S+p33UqwMNN1vlv2P0zJZUScTshnx7OEoIIRcCFNQ904sYxZz8kg==
"@types/node-fetch@2.6.2":
version "2.6.2"
resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.2.tgz#d1a9c5fd049d9415dce61571557104dec3ec81da"