Use single WebAPI instance across the app
This commit is contained in:
parent
79633a9e7b
commit
fdec47d637
19 changed files with 218 additions and 308 deletions
|
@ -2,7 +2,8 @@
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import { get, throttle } from 'lodash';
|
import { get, throttle } from 'lodash';
|
||||||
import { connectToServerWithStoredCredentials } from './util/connectToServerWithStoredCredentials';
|
|
||||||
|
import type { WebAPIType } from './textsecure/WebAPI';
|
||||||
|
|
||||||
export type ConfigKeyType =
|
export type ConfigKeyType =
|
||||||
| 'desktop.announcementGroup'
|
| 'desktop.announcementGroup'
|
||||||
|
@ -38,9 +39,9 @@ type ConfigListenersMapType = {
|
||||||
let config: ConfigMapType = {};
|
let config: ConfigMapType = {};
|
||||||
const listeners: ConfigListenersMapType = {};
|
const listeners: ConfigListenersMapType = {};
|
||||||
|
|
||||||
export async function initRemoteConfig(): Promise<void> {
|
export async function initRemoteConfig(server: WebAPIType): Promise<void> {
|
||||||
config = window.storage.get('remoteConfig') || {};
|
config = window.storage.get('remoteConfig') || {};
|
||||||
await maybeRefreshRemoteConfig();
|
await maybeRefreshRemoteConfig(server);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function onChange(
|
export function onChange(
|
||||||
|
@ -56,12 +57,10 @@ export function onChange(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const refreshRemoteConfig = async (): Promise<void> => {
|
export const refreshRemoteConfig = async (
|
||||||
|
server: WebAPIType
|
||||||
|
): Promise<void> => {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const server = connectToServerWithStoredCredentials(
|
|
||||||
window.WebAPI,
|
|
||||||
window.storage
|
|
||||||
);
|
|
||||||
const newConfig = await server.getConfig();
|
const newConfig = await server.getConfig();
|
||||||
|
|
||||||
// Process new configuration in light of the old configuration
|
// Process new configuration in light of the old configuration
|
||||||
|
|
125
ts/background.ts
125
ts/background.ts
|
@ -60,7 +60,7 @@ import {
|
||||||
ContactEvent,
|
ContactEvent,
|
||||||
GroupEvent,
|
GroupEvent,
|
||||||
} from './textsecure/messageReceiverEvents';
|
} from './textsecure/messageReceiverEvents';
|
||||||
import { connectToServerWithStoredCredentials } from './util/connectToServerWithStoredCredentials';
|
import type { WebAPIType } from './textsecure/WebAPI';
|
||||||
import * as universalExpireTimer from './util/universalExpireTimer';
|
import * as universalExpireTimer from './util/universalExpireTimer';
|
||||||
import { isDirectConversation, isGroupV2 } from './util/whatTypeOfConversation';
|
import { isDirectConversation, isGroupV2 } from './util/whatTypeOfConversation';
|
||||||
import { getSendOptions } from './util/getSendOptions';
|
import { getSendOptions } from './util/getSendOptions';
|
||||||
|
@ -132,7 +132,24 @@ export async function startApp(): Promise<void> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
initializeAllJobQueues();
|
// Initialize WebAPI as early as possible
|
||||||
|
let server: WebAPIType | undefined;
|
||||||
|
window.storage.onready(() => {
|
||||||
|
server = window.WebAPI.connect(
|
||||||
|
window.textsecure.storage.user.getWebAPICredentials()
|
||||||
|
);
|
||||||
|
|
||||||
|
window.textsecure.storage.user.on('credentialsChange', async () => {
|
||||||
|
strictAssert(server !== undefined, 'WebAPI not ready');
|
||||||
|
await server.authenticate(
|
||||||
|
window.textsecure.storage.user.getWebAPICredentials()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
initializeAllJobQueues({
|
||||||
|
server,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
ourProfileKeyService.initialize(window.storage);
|
ourProfileKeyService.initialize(window.storage);
|
||||||
|
|
||||||
|
@ -153,8 +170,10 @@ export async function startApp(): Promise<void> {
|
||||||
const reconnectBackOff = new BackOff(FIBONACCI_TIMEOUTS);
|
const reconnectBackOff = new BackOff(FIBONACCI_TIMEOUTS);
|
||||||
|
|
||||||
window.storage.onready(() => {
|
window.storage.onready(() => {
|
||||||
|
strictAssert(server, 'WebAPI not ready');
|
||||||
|
|
||||||
senderCertificateService.initialize({
|
senderCertificateService.initialize({
|
||||||
WebAPI: window.WebAPI,
|
server,
|
||||||
navigator,
|
navigator,
|
||||||
onlineEventTarget: window,
|
onlineEventTarget: window,
|
||||||
storage: window.storage,
|
storage: window.storage,
|
||||||
|
@ -355,8 +374,7 @@ export async function startApp(): Promise<void> {
|
||||||
|
|
||||||
window.Whisper.KeyChangeListener.init(window.textsecure.storage.protocol);
|
window.Whisper.KeyChangeListener.init(window.textsecure.storage.protocol);
|
||||||
window.textsecure.storage.protocol.on('removePreKey', () => {
|
window.textsecure.storage.protocol.on('removePreKey', () => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
window.getAccountManager().refreshPreKeys();
|
||||||
window.getAccountManager()!.refreshPreKeys();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let messageReceiver: MessageReceiver | undefined;
|
let messageReceiver: MessageReceiver | undefined;
|
||||||
|
@ -372,32 +390,28 @@ export async function startApp(): Promise<void> {
|
||||||
};
|
};
|
||||||
let accountManager: typeof window.textsecure.AccountManager;
|
let accountManager: typeof window.textsecure.AccountManager;
|
||||||
window.getAccountManager = () => {
|
window.getAccountManager = () => {
|
||||||
if (!accountManager) {
|
if (accountManager) {
|
||||||
const OLD_USERNAME = window.storage.get('number_id', '');
|
return accountManager;
|
||||||
const USERNAME = window.storage.get('uuid_id', '');
|
|
||||||
const PASSWORD = window.storage.get('password', '');
|
|
||||||
accountManager = new window.textsecure.AccountManager(
|
|
||||||
USERNAME || OLD_USERNAME,
|
|
||||||
PASSWORD
|
|
||||||
);
|
|
||||||
accountManager.addEventListener('registration', () => {
|
|
||||||
const ourDeviceId = window.textsecure.storage.user.getDeviceId();
|
|
||||||
const ourNumber = window.textsecure.storage.user.getNumber();
|
|
||||||
const ourUuid = window.textsecure.storage.user.getUuid();
|
|
||||||
const user = {
|
|
||||||
ourConversationId: window.ConversationController.getOurConversationId(),
|
|
||||||
ourDeviceId,
|
|
||||||
ourNumber,
|
|
||||||
ourUuid,
|
|
||||||
regionCode: window.storage.get('regionCode'),
|
|
||||||
};
|
|
||||||
window.Whisper.events.trigger('userChanged', user);
|
|
||||||
|
|
||||||
window.Signal.Util.Registration.markDone();
|
|
||||||
window.log.info('dispatching registration event');
|
|
||||||
window.Whisper.events.trigger('registration_done');
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
accountManager = new window.textsecure.AccountManager(server);
|
||||||
|
accountManager.addEventListener('registration', () => {
|
||||||
|
const ourDeviceId = window.textsecure.storage.user.getDeviceId();
|
||||||
|
const ourNumber = window.textsecure.storage.user.getNumber();
|
||||||
|
const ourUuid = window.textsecure.storage.user.getUuid();
|
||||||
|
const user = {
|
||||||
|
ourConversationId: window.ConversationController.getOurConversationId(),
|
||||||
|
ourDeviceId,
|
||||||
|
ourNumber,
|
||||||
|
ourUuid,
|
||||||
|
regionCode: window.storage.get('regionCode'),
|
||||||
|
};
|
||||||
|
window.Whisper.events.trigger('userChanged', user);
|
||||||
|
|
||||||
|
window.Signal.Util.Registration.markDone();
|
||||||
|
window.log.info('dispatching registration event');
|
||||||
|
window.Whisper.events.trigger('registration_done');
|
||||||
|
});
|
||||||
return accountManager;
|
return accountManager;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -483,6 +497,8 @@ export async function startApp(): Promise<void> {
|
||||||
}
|
}
|
||||||
first = false;
|
first = false;
|
||||||
|
|
||||||
|
strictAssert(server !== undefined, 'WebAPI not ready');
|
||||||
|
|
||||||
cleanupSessionResets();
|
cleanupSessionResets();
|
||||||
|
|
||||||
// These make key operations available to IPC handlers created in preload.js
|
// These make key operations available to IPC handlers created in preload.js
|
||||||
|
@ -888,7 +904,7 @@ export async function startApp(): Promise<void> {
|
||||||
// We start this up before window.ConversationController.load() to
|
// We start this up before window.ConversationController.load() to
|
||||||
// ensure that our feature flags are represented in the cached props
|
// ensure that our feature flags are represented in the cached props
|
||||||
// we generate on load of each convo.
|
// we generate on load of each convo.
|
||||||
window.Signal.RemoteConfig.initRemoteConfig();
|
window.Signal.RemoteConfig.initRemoteConfig(server);
|
||||||
|
|
||||||
let retryReceiptLifespan: number | undefined;
|
let retryReceiptLifespan: number | undefined;
|
||||||
try {
|
try {
|
||||||
|
@ -1724,6 +1740,7 @@ export async function startApp(): Promise<void> {
|
||||||
window.reduxActions.network.setChallengeStatus(challengeStatus);
|
window.reduxActions.network.setChallengeStatus(challengeStatus);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
window.Whisper.events.on('challengeResponse', response => {
|
window.Whisper.events.on('challengeResponse', response => {
|
||||||
if (!challengeHandler) {
|
if (!challengeHandler) {
|
||||||
throw new Error('Expected challenge handler to be there');
|
throw new Error('Expected challenge handler to be there');
|
||||||
|
@ -1732,13 +1749,8 @@ export async function startApp(): Promise<void> {
|
||||||
challengeHandler.onResponse(response);
|
challengeHandler.onResponse(response);
|
||||||
});
|
});
|
||||||
|
|
||||||
window.storage.onready(async () => {
|
// Storage is ready because `start()` is called from `storage.onready()`
|
||||||
if (!challengeHandler) {
|
await challengeHandler.load();
|
||||||
throw new Error('Expected challenge handler to be there');
|
|
||||||
}
|
|
||||||
|
|
||||||
await challengeHandler.load();
|
|
||||||
});
|
|
||||||
|
|
||||||
window.Signal.challengeHandler = challengeHandler;
|
window.Signal.challengeHandler = challengeHandler;
|
||||||
|
|
||||||
|
@ -1828,8 +1840,10 @@ export async function startApp(): Promise<void> {
|
||||||
|
|
||||||
// Maybe refresh remote configuration when we become active
|
// Maybe refresh remote configuration when we become active
|
||||||
window.registerForActive(async () => {
|
window.registerForActive(async () => {
|
||||||
|
strictAssert(server !== undefined, 'WebAPI not ready');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await window.Signal.RemoteConfig.maybeRefreshRemoteConfig();
|
await window.Signal.RemoteConfig.maybeRefreshRemoteConfig(server);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error && window._.isNumber(error.code)) {
|
if (error && window._.isNumber(error.code)) {
|
||||||
window.log.warn(
|
window.log.warn(
|
||||||
|
@ -2010,6 +2024,9 @@ export async function startApp(): Promise<void> {
|
||||||
window.log.warn('connect already running', { connectCount });
|
window.log.warn('connect already running', { connectCount });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
strictAssert(server !== undefined, 'WebAPI not connected');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
connecting = true;
|
connecting = true;
|
||||||
|
|
||||||
|
@ -2050,20 +2067,13 @@ export async function startApp(): Promise<void> {
|
||||||
messageReceiver = undefined;
|
messageReceiver = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const OLD_USERNAME = window.storage.get('number_id', '');
|
window.textsecure.messaging = new window.textsecure.MessageSender(server);
|
||||||
const USERNAME = window.storage.get('uuid_id', '');
|
|
||||||
const PASSWORD = window.storage.get('password', '');
|
|
||||||
|
|
||||||
window.textsecure.messaging = new window.textsecure.MessageSender(
|
|
||||||
USERNAME || OLD_USERNAME,
|
|
||||||
PASSWORD
|
|
||||||
);
|
|
||||||
|
|
||||||
if (connectCount === 0) {
|
if (connectCount === 0) {
|
||||||
try {
|
try {
|
||||||
// Force a re-fetch before we process our queue. We may want to turn on
|
// Force a re-fetch before we process our queue. We may want to turn on
|
||||||
// something which changes how we process incoming messages!
|
// something which changes how we process incoming messages!
|
||||||
await window.Signal.RemoteConfig.refreshRemoteConfig();
|
await window.Signal.RemoteConfig.refreshRemoteConfig(server);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
window.log.error(
|
window.log.error(
|
||||||
'connect: Error refreshing remote config:',
|
'connect: Error refreshing remote config:',
|
||||||
|
@ -2109,9 +2119,7 @@ export async function startApp(): Promise<void> {
|
||||||
serverTrustRoot: window.getServerTrustRoot(),
|
serverTrustRoot: window.getServerTrustRoot(),
|
||||||
};
|
};
|
||||||
messageReceiver = new window.textsecure.MessageReceiver(
|
messageReceiver = new window.textsecure.MessageReceiver(
|
||||||
OLD_USERNAME,
|
server,
|
||||||
USERNAME,
|
|
||||||
PASSWORD,
|
|
||||||
messageReceiverOptions
|
messageReceiverOptions
|
||||||
);
|
);
|
||||||
window.textsecure.messageReceiver = messageReceiver;
|
window.textsecure.messageReceiver = messageReceiver;
|
||||||
|
@ -2251,8 +2259,7 @@ export async function startApp(): Promise<void> {
|
||||||
runStorageService();
|
runStorageService();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
const manager = window.getAccountManager();
|
||||||
const manager = window.getAccountManager()!;
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
manager.maybeUpdateDeviceName(),
|
manager.maybeUpdateDeviceName(),
|
||||||
window.textsecure.storage.user.removeSignalingKey(),
|
window.textsecure.storage.user.removeSignalingKey(),
|
||||||
|
@ -2267,10 +2274,6 @@ export async function startApp(): Promise<void> {
|
||||||
|
|
||||||
const udSupportKey = 'hasRegisterSupportForUnauthenticatedDelivery';
|
const udSupportKey = 'hasRegisterSupportForUnauthenticatedDelivery';
|
||||||
if (!window.storage.get(udSupportKey)) {
|
if (!window.storage.get(udSupportKey)) {
|
||||||
const server = connectToServerWithStoredCredentials(
|
|
||||||
window.WebAPI,
|
|
||||||
window.storage
|
|
||||||
);
|
|
||||||
try {
|
try {
|
||||||
await server.registerSupportForUnauthenticatedDelivery();
|
await server.registerSupportForUnauthenticatedDelivery();
|
||||||
window.storage.put(udSupportKey, true);
|
window.storage.put(udSupportKey, true);
|
||||||
|
@ -2286,10 +2289,6 @@ export async function startApp(): Promise<void> {
|
||||||
|
|
||||||
// If we didn't capture a UUID on registration, go get it from the server
|
// If we didn't capture a UUID on registration, go get it from the server
|
||||||
if (!window.textsecure.storage.user.getUuid()) {
|
if (!window.textsecure.storage.user.getUuid()) {
|
||||||
const server = window.WebAPI.connect({
|
|
||||||
username: OLD_USERNAME,
|
|
||||||
password: PASSWORD,
|
|
||||||
});
|
|
||||||
try {
|
try {
|
||||||
const { uuid } = await server.whoami();
|
const { uuid } = await server.whoami();
|
||||||
assert(deviceId, 'We should have device id');
|
assert(deviceId, 'We should have device id');
|
||||||
|
@ -2311,10 +2310,6 @@ export async function startApp(): Promise<void> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (connectCount === 1) {
|
if (connectCount === 1) {
|
||||||
const server = connectToServerWithStoredCredentials(
|
|
||||||
window.WebAPI,
|
|
||||||
window.storage
|
|
||||||
);
|
|
||||||
try {
|
try {
|
||||||
// Note: we always have to register our capabilities all at once, so we do this
|
// Note: we always have to register our capabilities all at once, so we do this
|
||||||
// after connect on every startup
|
// after connect on every startup
|
||||||
|
|
|
@ -1,13 +1,21 @@
|
||||||
// Copyright 2021 Signal Messenger, LLC
|
// Copyright 2021 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import type { WebAPIType } from '../textsecure/WebAPI';
|
||||||
|
|
||||||
import { removeStorageKeyJobQueue } from './removeStorageKeyJobQueue';
|
import { removeStorageKeyJobQueue } from './removeStorageKeyJobQueue';
|
||||||
import { reportSpamJobQueue } from './reportSpamJobQueue';
|
import { reportSpamJobQueue } from './reportSpamJobQueue';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start all of the job queues. Should be called when the database is ready.
|
* Start all of the job queues. Should be called when the database is ready.
|
||||||
*/
|
*/
|
||||||
export function initializeAllJobQueues(): void {
|
export function initializeAllJobQueues({
|
||||||
|
server,
|
||||||
|
}: {
|
||||||
|
server: WebAPIType;
|
||||||
|
}): void {
|
||||||
|
reportSpamJobQueue.initialize({ server });
|
||||||
|
|
||||||
removeStorageKeyJobQueue.streamJobs();
|
removeStorageKeyJobQueue.streamJobs();
|
||||||
reportSpamJobQueue.streamJobs();
|
reportSpamJobQueue.streamJobs();
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,16 +4,17 @@
|
||||||
|
|
||||||
import * as z from 'zod';
|
import * as z from 'zod';
|
||||||
import * as moment from 'moment';
|
import * as moment from 'moment';
|
||||||
|
import { strictAssert } from '../util/assert';
|
||||||
import { waitForOnline } from '../util/waitForOnline';
|
import { waitForOnline } from '../util/waitForOnline';
|
||||||
import { isDone as isDeviceLinked } from '../util/registration';
|
import { isDone as isDeviceLinked } from '../util/registration';
|
||||||
import * as log from '../logging/log';
|
import * as log from '../logging/log';
|
||||||
import { connectToServerWithStoredCredentials } from '../util/connectToServerWithStoredCredentials';
|
|
||||||
import { map } from '../util/iterables';
|
import { map } from '../util/iterables';
|
||||||
import { sleep } from '../util/sleep';
|
import { sleep } from '../util/sleep';
|
||||||
|
|
||||||
import { JobQueue } from './JobQueue';
|
import { JobQueue } from './JobQueue';
|
||||||
import { jobQueueDatabaseStore } from './JobQueueDatabaseStore';
|
import { jobQueueDatabaseStore } from './JobQueueDatabaseStore';
|
||||||
import { parseIntWithFallback } from '../util/parseIntWithFallback';
|
import { parseIntWithFallback } from '../util/parseIntWithFallback';
|
||||||
|
import type { WebAPIType } from '../textsecure/WebAPI';
|
||||||
|
|
||||||
const RETRY_WAIT_TIME = moment.duration(1, 'minute').asMilliseconds();
|
const RETRY_WAIT_TIME = moment.duration(1, 'minute').asMilliseconds();
|
||||||
const RETRYABLE_4XX_FAILURE_STATUSES = new Set([
|
const RETRYABLE_4XX_FAILURE_STATUSES = new Set([
|
||||||
|
@ -47,6 +48,12 @@ const reportSpamJobDataSchema = z.object({
|
||||||
export type ReportSpamJobData = z.infer<typeof reportSpamJobDataSchema>;
|
export type ReportSpamJobData = z.infer<typeof reportSpamJobDataSchema>;
|
||||||
|
|
||||||
export class ReportSpamJobQueue extends JobQueue<ReportSpamJobData> {
|
export class ReportSpamJobQueue extends JobQueue<ReportSpamJobData> {
|
||||||
|
private server?: WebAPIType;
|
||||||
|
|
||||||
|
public initialize({ server }: { server: WebAPIType }): void {
|
||||||
|
this.server = server;
|
||||||
|
}
|
||||||
|
|
||||||
protected parseData(data: unknown): ReportSpamJobData {
|
protected parseData(data: unknown): ReportSpamJobData {
|
||||||
return reportSpamJobDataSchema.parse(data);
|
return reportSpamJobDataSchema.parse(data);
|
||||||
}
|
}
|
||||||
|
@ -67,10 +74,8 @@ export class ReportSpamJobQueue extends JobQueue<ReportSpamJobData> {
|
||||||
|
|
||||||
await waitForOnline(window.navigator, window);
|
await waitForOnline(window.navigator, window);
|
||||||
|
|
||||||
const server = connectToServerWithStoredCredentials(
|
const { server } = this;
|
||||||
window.WebAPI,
|
strictAssert(server !== undefined, 'ReportSpamJobQueue not initialized');
|
||||||
window.storage
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
|
|
|
@ -1678,8 +1678,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (hadSignedPreKeyRotationError) {
|
if (hadSignedPreKeyRotationError) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
promises.push(window.getAccountManager().rotateSignedPreKey());
|
||||||
promises.push(window.getAccountManager()!.rotateSignedPreKey());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
attributesToUpdate.sendStateByConversationId = sendStateByConversationId;
|
attributesToUpdate.sendStateByConversationId = sendStateByConversationId;
|
||||||
|
|
|
@ -13,8 +13,8 @@ import { missingCaseError } from '../util/missingCaseError';
|
||||||
import { normalizeNumber } from '../util/normalizeNumber';
|
import { normalizeNumber } from '../util/normalizeNumber';
|
||||||
import { waitForOnline } from '../util/waitForOnline';
|
import { waitForOnline } from '../util/waitForOnline';
|
||||||
import * as log from '../logging/log';
|
import * as log from '../logging/log';
|
||||||
import { connectToServerWithStoredCredentials } from '../util/connectToServerWithStoredCredentials';
|
|
||||||
import { StorageInterface } from '../types/Storage.d';
|
import { StorageInterface } from '../types/Storage.d';
|
||||||
|
import type { WebAPIType } from '../textsecure/WebAPI';
|
||||||
import { SignalService as Proto } from '../protobuf';
|
import { SignalService as Proto } from '../protobuf';
|
||||||
|
|
||||||
import SenderCertificate = Proto.SenderCertificate;
|
import SenderCertificate = Proto.SenderCertificate;
|
||||||
|
@ -28,7 +28,7 @@ const CLOCK_SKEW_THRESHOLD = 15 * 60 * 1000;
|
||||||
|
|
||||||
// This is exported for testing.
|
// This is exported for testing.
|
||||||
export class SenderCertificateService {
|
export class SenderCertificateService {
|
||||||
private WebAPI?: typeof window.WebAPI;
|
private server?: WebAPIType;
|
||||||
|
|
||||||
private fetchPromises: Map<
|
private fetchPromises: Map<
|
||||||
SenderCertificateMode,
|
SenderCertificateMode,
|
||||||
|
@ -42,19 +42,19 @@ export class SenderCertificateService {
|
||||||
private storage?: StorageInterface;
|
private storage?: StorageInterface;
|
||||||
|
|
||||||
initialize({
|
initialize({
|
||||||
WebAPI,
|
server,
|
||||||
navigator,
|
navigator,
|
||||||
onlineEventTarget,
|
onlineEventTarget,
|
||||||
storage,
|
storage,
|
||||||
}: {
|
}: {
|
||||||
WebAPI: typeof window.WebAPI;
|
server: WebAPIType;
|
||||||
navigator: Readonly<{ onLine: boolean }>;
|
navigator: Readonly<{ onLine: boolean }>;
|
||||||
onlineEventTarget: EventTarget;
|
onlineEventTarget: EventTarget;
|
||||||
storage: StorageInterface;
|
storage: StorageInterface;
|
||||||
}): void {
|
}): void {
|
||||||
log.info('Sender certificate service initialized');
|
log.info('Sender certificate service initialized');
|
||||||
|
|
||||||
this.WebAPI = WebAPI;
|
this.server = server;
|
||||||
this.navigator = navigator;
|
this.navigator = navigator;
|
||||||
this.onlineEventTarget = onlineEventTarget;
|
this.onlineEventTarget = onlineEventTarget;
|
||||||
this.storage = storage;
|
this.storage = storage;
|
||||||
|
@ -188,13 +188,12 @@ export class SenderCertificateService {
|
||||||
private async requestSenderCertificate(
|
private async requestSenderCertificate(
|
||||||
mode: SenderCertificateMode
|
mode: SenderCertificateMode
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const { storage, WebAPI } = this;
|
const { server } = this;
|
||||||
assert(
|
assert(
|
||||||
storage && WebAPI,
|
server,
|
||||||
'Sender certificate service method was called before it was initialized'
|
'Sender certificate service method was called before it was initialized'
|
||||||
);
|
);
|
||||||
|
|
||||||
const server = connectToServerWithStoredCredentials(WebAPI, storage);
|
|
||||||
const omitE164 = mode === SenderCertificateMode.WithoutE164;
|
const omitE164 = mode === SenderCertificateMode.WithoutE164;
|
||||||
const { certificate } = await server.getSenderCertificate(omitE164);
|
const { certificate } = await server.getSenderCertificate(omitE164);
|
||||||
return certificate;
|
return certificate;
|
||||||
|
|
|
@ -1,90 +0,0 @@
|
||||||
// Copyright 2021 Signal Messenger, LLC
|
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
||||||
|
|
||||||
import { assert } from 'chai';
|
|
||||||
import * as sinon from 'sinon';
|
|
||||||
|
|
||||||
import { connectToServerWithStoredCredentials } from '../../util/connectToServerWithStoredCredentials';
|
|
||||||
|
|
||||||
describe('connectToServerWithStoredCredentials', () => {
|
|
||||||
let fakeWebApi: any;
|
|
||||||
let fakeStorage: { get: sinon.SinonStub };
|
|
||||||
let fakeWebApiConnect: { connect: sinon.SinonStub };
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
fakeWebApi = {};
|
|
||||||
fakeStorage = { get: sinon.stub() };
|
|
||||||
fakeWebApiConnect = { connect: sinon.stub().returns(fakeWebApi) };
|
|
||||||
});
|
|
||||||
|
|
||||||
it('throws if no ID is in storage', () => {
|
|
||||||
fakeStorage.get.withArgs('password').returns('swordfish');
|
|
||||||
|
|
||||||
assert.throws(() => {
|
|
||||||
connectToServerWithStoredCredentials(fakeWebApiConnect, fakeStorage);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('throws if the ID in storage is not a string', () => {
|
|
||||||
fakeStorage.get.withArgs('uuid_id').returns(1234);
|
|
||||||
fakeStorage.get.withArgs('password').returns('swordfish');
|
|
||||||
|
|
||||||
assert.throws(() => {
|
|
||||||
connectToServerWithStoredCredentials(fakeWebApiConnect, fakeStorage);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('throws if no password is in storage', () => {
|
|
||||||
fakeStorage.get.withArgs('uuid_id').returns('foo');
|
|
||||||
|
|
||||||
assert.throws(() => {
|
|
||||||
connectToServerWithStoredCredentials(fakeWebApiConnect, fakeStorage);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('throws if the password in storage is not a string', () => {
|
|
||||||
fakeStorage.get.withArgs('uuid_id').returns('foo');
|
|
||||||
fakeStorage.get.withArgs('password').returns(1234);
|
|
||||||
|
|
||||||
assert.throws(() => {
|
|
||||||
connectToServerWithStoredCredentials(fakeWebApiConnect, fakeStorage);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('connects with the UUID ID (if available) and password', () => {
|
|
||||||
fakeStorage.get.withArgs('uuid_id').returns('foo');
|
|
||||||
fakeStorage.get.withArgs('number_id').returns('should not be used');
|
|
||||||
fakeStorage.get.withArgs('password').returns('swordfish');
|
|
||||||
|
|
||||||
connectToServerWithStoredCredentials(fakeWebApiConnect, fakeStorage);
|
|
||||||
|
|
||||||
sinon.assert.calledWith(fakeWebApiConnect.connect, {
|
|
||||||
username: 'foo',
|
|
||||||
password: 'swordfish',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('connects with the number ID (if UUID ID not available) and password', () => {
|
|
||||||
fakeStorage.get.withArgs('number_id').returns('bar');
|
|
||||||
fakeStorage.get.withArgs('password').returns('swordfish');
|
|
||||||
|
|
||||||
connectToServerWithStoredCredentials(fakeWebApiConnect, fakeStorage);
|
|
||||||
|
|
||||||
sinon.assert.calledWith(fakeWebApiConnect.connect, {
|
|
||||||
username: 'bar',
|
|
||||||
password: 'swordfish',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns the connected WebAPI', () => {
|
|
||||||
fakeStorage.get.withArgs('uuid_id').returns('foo');
|
|
||||||
fakeStorage.get.withArgs('password').returns('swordfish');
|
|
||||||
|
|
||||||
assert.strictEqual(
|
|
||||||
connectToServerWithStoredCredentials(fakeWebApiConnect, fakeStorage),
|
|
||||||
fakeWebApi
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -11,6 +11,7 @@ import { connection as WebSocket } from 'websocket';
|
||||||
|
|
||||||
import MessageReceiver from '../textsecure/MessageReceiver';
|
import MessageReceiver from '../textsecure/MessageReceiver';
|
||||||
import { DecryptionErrorEvent } from '../textsecure/messageReceiverEvents';
|
import { DecryptionErrorEvent } from '../textsecure/messageReceiverEvents';
|
||||||
|
import { WebAPIType } from '../textsecure/WebAPI';
|
||||||
import { SignalService as Proto } from '../protobuf';
|
import { SignalService as Proto } from '../protobuf';
|
||||||
import * as Crypto from '../Crypto';
|
import * as Crypto from '../Crypto';
|
||||||
|
|
||||||
|
@ -32,15 +33,10 @@ describe('MessageReceiver', () => {
|
||||||
it('generates decryption-error event when it cannot decrypt', done => {
|
it('generates decryption-error event when it cannot decrypt', done => {
|
||||||
const socket = new FakeSocket();
|
const socket = new FakeSocket();
|
||||||
|
|
||||||
const messageReceiver = new MessageReceiver(
|
const messageReceiver = new MessageReceiver({} as WebAPIType, {
|
||||||
'oldUsername.2',
|
serverTrustRoot: 'AAAAAAAA',
|
||||||
'username.2',
|
socket: socket as WebSocket,
|
||||||
'password',
|
});
|
||||||
{
|
|
||||||
serverTrustRoot: 'AAAAAAAA',
|
|
||||||
socket: socket as WebSocket,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const body = Proto.Envelope.encode({
|
const body = Proto.Envelope.encode({
|
||||||
type: Proto.Envelope.Type.CIPHERTEXT,
|
type: Proto.Envelope.Type.CIPHERTEXT,
|
||||||
|
|
|
@ -34,12 +34,13 @@ describe('Conversations', () => {
|
||||||
version: 0,
|
version: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
window.textsecure.storage.user.setNumberAndDeviceId(
|
await window.textsecure.storage.user.setCredentials({
|
||||||
ourNumber,
|
number: ourNumber,
|
||||||
2,
|
uuid: ourUuid,
|
||||||
'my device'
|
deviceId: 2,
|
||||||
);
|
deviceName: 'my device',
|
||||||
window.textsecure.storage.user.setUuidAndDeviceId(ourUuid, 2);
|
password: 'password',
|
||||||
|
});
|
||||||
await window.ConversationController.loadPromise();
|
await window.ConversationController.loadPromise();
|
||||||
|
|
||||||
await window.Signal.Data.saveConversation(conversation.attributes);
|
await window.Signal.Data.saveConversation(conversation.attributes);
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { setup as setupI18n } from '../../../js/modules/i18n';
|
||||||
import enMessages from '../../../_locales/en/messages.json';
|
import enMessages from '../../../_locales/en/messages.json';
|
||||||
import { SendStatus } from '../../messages/MessageSendState';
|
import { SendStatus } from '../../messages/MessageSendState';
|
||||||
import MessageSender from '../../textsecure/SendMessage';
|
import MessageSender from '../../textsecure/SendMessage';
|
||||||
|
import { WebAPIType } from '../../textsecure/WebAPI';
|
||||||
import { CallbackResultType } from '../../textsecure/Types.d';
|
import { CallbackResultType } from '../../textsecure/Types.d';
|
||||||
import type { StorageAccessType } from '../../types/Storage.d';
|
import type { StorageAccessType } from '../../types/Storage.d';
|
||||||
import { SignalService as Proto } from '../../protobuf';
|
import { SignalService as Proto } from '../../protobuf';
|
||||||
|
@ -81,7 +82,7 @@ describe('Message', () => {
|
||||||
oldMessageSender = window.textsecure.messaging;
|
oldMessageSender = window.textsecure.messaging;
|
||||||
|
|
||||||
window.textsecure.messaging =
|
window.textsecure.messaging =
|
||||||
oldMessageSender ?? new MessageSender('username', 'password');
|
oldMessageSender ?? new MessageSender({} as WebAPIType);
|
||||||
this.sandbox
|
this.sandbox
|
||||||
.stub(window.textsecure.messaging, 'sendSyncMessage')
|
.stub(window.textsecure.messaging, 'sendSyncMessage')
|
||||||
.resolves({});
|
.resolves({});
|
||||||
|
|
|
@ -23,7 +23,6 @@ describe('SenderCertificateService', () => {
|
||||||
let fakeValidCertificate: SenderCertificate;
|
let fakeValidCertificate: SenderCertificate;
|
||||||
let fakeValidCertificateExpiry: number;
|
let fakeValidCertificateExpiry: number;
|
||||||
let fakeServer: any;
|
let fakeServer: any;
|
||||||
let fakeWebApi: typeof window.WebAPI;
|
|
||||||
let fakeNavigator: { onLine: boolean };
|
let fakeNavigator: { onLine: boolean };
|
||||||
let fakeWindow: EventTarget;
|
let fakeWindow: EventTarget;
|
||||||
let fakeStorage: any;
|
let fakeStorage: any;
|
||||||
|
@ -31,7 +30,7 @@ describe('SenderCertificateService', () => {
|
||||||
function initializeTestService(): SenderCertificateService {
|
function initializeTestService(): SenderCertificateService {
|
||||||
const result = new SenderCertificateService();
|
const result = new SenderCertificateService();
|
||||||
result.initialize({
|
result.initialize({
|
||||||
WebAPI: fakeWebApi,
|
server: fakeServer,
|
||||||
navigator: fakeNavigator,
|
navigator: fakeNavigator,
|
||||||
onlineEventTarget: fakeWindow,
|
onlineEventTarget: fakeWindow,
|
||||||
storage: fakeStorage,
|
storage: fakeStorage,
|
||||||
|
@ -55,7 +54,6 @@ describe('SenderCertificateService', () => {
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
fakeWebApi = { connect: sinon.stub().returns(fakeServer) };
|
|
||||||
|
|
||||||
fakeNavigator = { onLine: true };
|
fakeNavigator = { onLine: true };
|
||||||
|
|
||||||
|
|
|
@ -70,16 +70,13 @@ type GeneratedKeysType = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class AccountManager extends EventTarget {
|
export default class AccountManager extends EventTarget {
|
||||||
server: WebAPIType;
|
|
||||||
|
|
||||||
pending: Promise<void>;
|
pending: Promise<void>;
|
||||||
|
|
||||||
pendingQueue?: PQueue;
|
pendingQueue?: PQueue;
|
||||||
|
|
||||||
constructor(username: string, password: string) {
|
constructor(private readonly server: WebAPIType) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.server = window.WebAPI.connect({ username, password });
|
|
||||||
this.pending = Promise.resolve();
|
this.pending = Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -569,34 +566,27 @@ export default class AccountManager extends EventTarget {
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
window.textsecure.storage.remove('identityKey'),
|
window.textsecure.storage.remove('identityKey'),
|
||||||
window.textsecure.storage.remove('password'),
|
window.textsecure.storage.user.removeCredentials(),
|
||||||
window.textsecure.storage.remove('registrationId'),
|
window.textsecure.storage.remove('registrationId'),
|
||||||
window.textsecure.storage.remove('number_id'),
|
|
||||||
window.textsecure.storage.remove('device_name'),
|
|
||||||
window.textsecure.storage.remove('regionCode'),
|
window.textsecure.storage.remove('regionCode'),
|
||||||
window.textsecure.storage.remove('userAgent'),
|
window.textsecure.storage.remove('userAgent'),
|
||||||
window.textsecure.storage.remove('profileKey'),
|
window.textsecure.storage.remove('profileKey'),
|
||||||
window.textsecure.storage.remove('read-receipt-setting'),
|
window.textsecure.storage.remove('read-receipt-setting'),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// `setNumberAndDeviceId` and `setUuidAndDeviceId` need to be called
|
// `setCredentials` needs to be called
|
||||||
// before `saveIdentifyWithAttributes` since `saveIdentityWithAttributes`
|
// before `saveIdentifyWithAttributes` since `saveIdentityWithAttributes`
|
||||||
// indirectly calls `ConversationController.getConverationId()` which
|
// indirectly calls `ConversationController.getConverationId()` which
|
||||||
// initializes the conversation for the given number (our number) which
|
// initializes the conversation for the given number (our number) which
|
||||||
// calls out to the user storage API to get the stored UUID and number
|
// calls out to the user storage API to get the stored UUID and number
|
||||||
// information.
|
// information.
|
||||||
await window.textsecure.storage.user.setNumberAndDeviceId(
|
await window.textsecure.storage.user.setCredentials({
|
||||||
|
uuid,
|
||||||
number,
|
number,
|
||||||
response.deviceId || 1,
|
deviceId: response.deviceId ?? 1,
|
||||||
deviceName || undefined
|
deviceName: deviceName ?? undefined,
|
||||||
);
|
password,
|
||||||
|
});
|
||||||
if (uuid) {
|
|
||||||
await window.textsecure.storage.user.setUuidAndDeviceId(
|
|
||||||
uuid,
|
|
||||||
response.deviceId || 1
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// This needs to be done very early, because it changes how things are saved in the
|
// This needs to be done very early, because it changes how things are saved in the
|
||||||
// database. Your identity, for example, in the saveIdentityWithAttributes call
|
// database. Your identity, for example, in the saveIdentityWithAttributes call
|
||||||
|
@ -625,7 +615,6 @@ export default class AccountManager extends EventTarget {
|
||||||
);
|
);
|
||||||
|
|
||||||
await window.textsecure.storage.put('identityKey', identityKeyPair);
|
await window.textsecure.storage.put('identityKey', identityKeyPair);
|
||||||
await window.textsecure.storage.put('password', password);
|
|
||||||
await window.textsecure.storage.put('registrationId', registrationId);
|
await window.textsecure.storage.put('registrationId', registrationId);
|
||||||
if (profileKey) {
|
if (profileKey) {
|
||||||
await ourProfileKeyService.set(profileKey);
|
await ourProfileKeyService.set(profileKey);
|
||||||
|
|
|
@ -53,7 +53,6 @@ import { processAttachment, processDataMessage } from './processDataMessage';
|
||||||
import { processSyncMessage } from './processSyncMessage';
|
import { processSyncMessage } from './processSyncMessage';
|
||||||
import EventTarget, { EventHandler } from './EventTarget';
|
import EventTarget, { EventHandler } from './EventTarget';
|
||||||
import { WebAPIType } from './WebAPI';
|
import { WebAPIType } from './WebAPI';
|
||||||
import utils from './Helpers';
|
|
||||||
import WebSocketResource, {
|
import WebSocketResource, {
|
||||||
IncomingWebSocketRequest,
|
IncomingWebSocketRequest,
|
||||||
CloseEvent,
|
CloseEvent,
|
||||||
|
@ -186,16 +185,12 @@ class MessageReceiverInner extends EventTarget {
|
||||||
|
|
||||||
number_id?: string;
|
number_id?: string;
|
||||||
|
|
||||||
password: string;
|
|
||||||
|
|
||||||
encryptedQueue: PQueue;
|
encryptedQueue: PQueue;
|
||||||
|
|
||||||
decryptedQueue: PQueue;
|
decryptedQueue: PQueue;
|
||||||
|
|
||||||
retryCachedTimeout: any;
|
retryCachedTimeout: any;
|
||||||
|
|
||||||
server: WebAPIType;
|
|
||||||
|
|
||||||
serverTrustRoot: Uint8Array;
|
serverTrustRoot: Uint8Array;
|
||||||
|
|
||||||
socket?: WebSocket;
|
socket?: WebSocket;
|
||||||
|
@ -204,10 +199,6 @@ class MessageReceiverInner extends EventTarget {
|
||||||
|
|
||||||
stoppingProcessing?: boolean;
|
stoppingProcessing?: boolean;
|
||||||
|
|
||||||
username: string;
|
|
||||||
|
|
||||||
uuid: string;
|
|
||||||
|
|
||||||
uuid_id?: string;
|
uuid_id?: string;
|
||||||
|
|
||||||
wsr?: WebSocketResource;
|
wsr?: WebSocketResource;
|
||||||
|
@ -215,9 +206,7 @@ class MessageReceiverInner extends EventTarget {
|
||||||
private readonly reconnectBackOff = new BackOff(FIBONACCI_TIMEOUTS);
|
private readonly reconnectBackOff = new BackOff(FIBONACCI_TIMEOUTS);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
oldUsername: string,
|
public readonly server: WebAPIType,
|
||||||
username: string,
|
|
||||||
password: string,
|
|
||||||
options: {
|
options: {
|
||||||
serverTrustRoot: string;
|
serverTrustRoot: string;
|
||||||
}
|
}
|
||||||
|
@ -227,30 +216,14 @@ class MessageReceiverInner extends EventTarget {
|
||||||
this.count = 0;
|
this.count = 0;
|
||||||
this.processedCount = 0;
|
this.processedCount = 0;
|
||||||
|
|
||||||
this.username = oldUsername;
|
|
||||||
this.uuid = username;
|
|
||||||
this.password = password;
|
|
||||||
this.server = window.WebAPI.connect({
|
|
||||||
username: username || oldUsername,
|
|
||||||
password,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!options.serverTrustRoot) {
|
if (!options.serverTrustRoot) {
|
||||||
throw new Error('Server trust root is required!');
|
throw new Error('Server trust root is required!');
|
||||||
}
|
}
|
||||||
this.serverTrustRoot = Bytes.fromBase64(options.serverTrustRoot);
|
this.serverTrustRoot = Bytes.fromBase64(options.serverTrustRoot);
|
||||||
|
|
||||||
this.number_id = oldUsername
|
this.number_id = window.textsecure.storage.user.getNumber();
|
||||||
? utils.unencodeNumber(oldUsername)[0]
|
this.uuid_id = window.textsecure.storage.user.getUuid();
|
||||||
: undefined;
|
this.deviceId = window.textsecure.storage.user.getDeviceId();
|
||||||
this.uuid_id = username ? utils.unencodeNumber(username)[0] : undefined;
|
|
||||||
this.deviceId =
|
|
||||||
username || oldUsername
|
|
||||||
? parseIntOrThrow(
|
|
||||||
utils.unencodeNumber(username || oldUsername)[1],
|
|
||||||
'MessageReceiver.constructor: username || oldUsername'
|
|
||||||
)
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
this.incomingQueue = new PQueue({ concurrency: 1, timeout: 1000 * 60 * 2 });
|
this.incomingQueue = new PQueue({ concurrency: 1, timeout: 1000 * 60 * 2 });
|
||||||
this.appQueue = new PQueue({ concurrency: 1, timeout: 1000 * 60 * 2 });
|
this.appQueue = new PQueue({ concurrency: 1, timeout: 1000 * 60 * 2 });
|
||||||
|
@ -2666,21 +2639,14 @@ export default class MessageReceiver {
|
||||||
private readonly inner: MessageReceiverInner;
|
private readonly inner: MessageReceiverInner;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
oldUsername: string,
|
server: WebAPIType,
|
||||||
username: string,
|
|
||||||
password: string,
|
|
||||||
options: {
|
options: {
|
||||||
serverTrustRoot: string;
|
serverTrustRoot: string;
|
||||||
retryCached?: string;
|
retryCached?: string;
|
||||||
socket?: WebSocket;
|
socket?: WebSocket;
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
const inner = new MessageReceiverInner(
|
const inner = new MessageReceiverInner(server, options);
|
||||||
oldUsername,
|
|
||||||
username,
|
|
||||||
password,
|
|
||||||
options
|
|
||||||
);
|
|
||||||
this.inner = inner;
|
this.inner = inner;
|
||||||
|
|
||||||
this.close = inner.close.bind(inner);
|
this.close = inner.close.bind(inner);
|
||||||
|
|
|
@ -439,14 +439,11 @@ class Message {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class MessageSender {
|
export default class MessageSender {
|
||||||
server: WebAPIType;
|
|
||||||
|
|
||||||
pendingMessages: {
|
pendingMessages: {
|
||||||
[id: string]: PQueue;
|
[id: string]: PQueue;
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(username: string, password: string) {
|
constructor(public readonly server: WebAPIType) {
|
||||||
this.server = window.WebAPI.connect({ username, password });
|
|
||||||
this.pendingMessages = {};
|
this.pendingMessages = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
5
ts/textsecure/Types.d.ts
vendored
5
ts/textsecure/Types.d.ts
vendored
|
@ -23,6 +23,11 @@ export type StorageServiceCredentials = {
|
||||||
password: string;
|
password: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type WebAPICredentials = {
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type DeviceType = {
|
export type DeviceType = {
|
||||||
id: number;
|
id: number;
|
||||||
identifier: string;
|
identifier: string;
|
||||||
|
|
|
@ -59,6 +59,7 @@ import { SignalService as Proto } from '../protobuf';
|
||||||
|
|
||||||
import { ConnectTimeoutError } from './Errors';
|
import { ConnectTimeoutError } from './Errors';
|
||||||
import MessageSender from './SendMessage';
|
import MessageSender from './SendMessage';
|
||||||
|
import { WebAPICredentials } from './Types.d';
|
||||||
|
|
||||||
// TODO: remove once we move away from ArrayBuffers
|
// TODO: remove once we move away from ArrayBuffers
|
||||||
const FIXMEU8 = Uint8Array;
|
const FIXMEU8 = Uint8Array;
|
||||||
|
@ -859,11 +860,6 @@ type InitializeOptionsType = {
|
||||||
version: string;
|
version: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ConnectParametersType = {
|
|
||||||
username: string;
|
|
||||||
password: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type MessageType = any;
|
type MessageType = any;
|
||||||
|
|
||||||
type AjaxOptionsType = {
|
type AjaxOptionsType = {
|
||||||
|
@ -888,7 +884,7 @@ type AjaxOptionsType = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type WebAPIConnectType = {
|
export type WebAPIConnectType = {
|
||||||
connect: (options: ConnectParametersType) => WebAPIType;
|
connect: (options: WebAPICredentials) => WebAPIType;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CapabilitiesType = {
|
export type CapabilitiesType = {
|
||||||
|
@ -1089,6 +1085,7 @@ export type WebAPIType = {
|
||||||
getConfig: () => Promise<
|
getConfig: () => Promise<
|
||||||
Array<{ name: string; enabled: boolean; value: string | null }>
|
Array<{ name: string; enabled: boolean; value: string | null }>
|
||||||
>;
|
>;
|
||||||
|
authenticate: (credentials: WebAPICredentials) => Promise<void>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SignedPreKeyType = {
|
export type SignedPreKeyType = {
|
||||||
|
@ -1197,7 +1194,7 @@ export function initialize({
|
||||||
function connect({
|
function connect({
|
||||||
username: initialUsername,
|
username: initialUsername,
|
||||||
password: initialPassword,
|
password: initialPassword,
|
||||||
}: ConnectParametersType) {
|
}: WebAPICredentials) {
|
||||||
let username = initialUsername;
|
let username = initialUsername;
|
||||||
let password = initialPassword;
|
let password = initialPassword;
|
||||||
const PARSE_RANGE_HEADER = /\/(\d+)$/;
|
const PARSE_RANGE_HEADER = /\/(\d+)$/;
|
||||||
|
@ -1205,6 +1202,7 @@ export function initialize({
|
||||||
|
|
||||||
// Thanks, function hoisting!
|
// Thanks, function hoisting!
|
||||||
return {
|
return {
|
||||||
|
authenticate,
|
||||||
confirmCode,
|
confirmCode,
|
||||||
createGroup,
|
createGroup,
|
||||||
fetchLinkPreviewImage,
|
fetchLinkPreviewImage,
|
||||||
|
@ -1307,6 +1305,14 @@ export function initialize({
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function authenticate({
|
||||||
|
username: newUsername,
|
||||||
|
password: newPassword,
|
||||||
|
}: WebAPICredentials) {
|
||||||
|
username = newUsername;
|
||||||
|
password = newPassword;
|
||||||
|
}
|
||||||
|
|
||||||
async function getConfig() {
|
async function getConfig() {
|
||||||
type ResType = {
|
type ResType = {
|
||||||
config: Array<{ name: string; enabled: boolean; value: string | null }>;
|
config: Array<{ name: string; enabled: boolean; value: string | null }>;
|
||||||
|
|
|
@ -1,29 +1,35 @@
|
||||||
// Copyright 2021 Signal Messenger, LLC
|
// Copyright 2021 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import { EventEmitter } from 'events';
|
||||||
|
|
||||||
|
import { WebAPICredentials } from '../Types.d';
|
||||||
|
|
||||||
import { StorageInterface } from '../../types/Storage.d';
|
import { StorageInterface } from '../../types/Storage.d';
|
||||||
|
|
||||||
import Helpers from '../Helpers';
|
import Helpers from '../Helpers';
|
||||||
|
|
||||||
export class User {
|
export type SetCredentialsOptions = {
|
||||||
constructor(private readonly storage: StorageInterface) {}
|
uuid?: string;
|
||||||
|
number: string;
|
||||||
|
deviceId: number;
|
||||||
|
deviceName?: string;
|
||||||
|
password: string;
|
||||||
|
};
|
||||||
|
|
||||||
public async setNumberAndDeviceId(
|
export class User extends EventEmitter {
|
||||||
number: string,
|
constructor(private readonly storage: StorageInterface) {
|
||||||
deviceId: number,
|
super();
|
||||||
deviceName?: string
|
|
||||||
): Promise<void> {
|
|
||||||
await this.storage.put('number_id', `${number}.${deviceId}`);
|
|
||||||
if (deviceName) {
|
|
||||||
await this.storage.put('device_name', deviceName);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async setUuidAndDeviceId(
|
public async setUuidAndDeviceId(
|
||||||
uuid: string,
|
uuid: string,
|
||||||
deviceId: number
|
deviceId: number
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
return this.storage.put('uuid_id', `${uuid}.${deviceId}`);
|
await this.storage.put('uuid_id', `${uuid}.${deviceId}`);
|
||||||
|
|
||||||
|
window.log.info('storage.user: uuid and device id changed');
|
||||||
|
this.emit('credentialsChange');
|
||||||
}
|
}
|
||||||
|
|
||||||
public getNumber(): string | undefined {
|
public getNumber(): string | undefined {
|
||||||
|
@ -62,6 +68,41 @@ export class User {
|
||||||
return this.storage.remove('signaling_key');
|
return this.storage.remove('signaling_key');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async setCredentials(
|
||||||
|
credentials: SetCredentialsOptions
|
||||||
|
): Promise<void> {
|
||||||
|
const { uuid, number, deviceId, deviceName, password } = credentials;
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
this.storage.put('number_id', `${number}.${deviceId}`),
|
||||||
|
this.storage.put('uuid_id', `${uuid}.${deviceId}`),
|
||||||
|
this.storage.put('password', password),
|
||||||
|
deviceName
|
||||||
|
? this.storage.put('device_name', deviceName)
|
||||||
|
: Promise.resolve(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
window.log.info('storage.user: credentials changed');
|
||||||
|
this.emit('credentialsChange');
|
||||||
|
}
|
||||||
|
|
||||||
|
public async removeCredentials(): Promise<void> {
|
||||||
|
await Promise.all([
|
||||||
|
this.storage.remove('number_id'),
|
||||||
|
this.storage.remove('uuid_id'),
|
||||||
|
this.storage.remove('password'),
|
||||||
|
this.storage.remove('device_name'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getWebAPICredentials(): WebAPICredentials {
|
||||||
|
return {
|
||||||
|
username:
|
||||||
|
this.storage.get('uuid_id') || this.storage.get('number_id') || '',
|
||||||
|
password: this.storage.get('password', ''),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private _getDeviceIdFromUuid(): string | undefined {
|
private _getDeviceIdFromUuid(): string | undefined {
|
||||||
const uuid = this.storage.get('uuid_id');
|
const uuid = this.storage.get('uuid_id');
|
||||||
if (uuid === undefined) return undefined;
|
if (uuid === undefined) return undefined;
|
||||||
|
@ -73,4 +114,25 @@ export class User {
|
||||||
if (numberId === undefined) return undefined;
|
if (numberId === undefined) return undefined;
|
||||||
return Helpers.unencodeNumber(numberId)[1];
|
return Helpers.unencodeNumber(numberId)[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// EventEmitter typing
|
||||||
|
//
|
||||||
|
|
||||||
|
public on(type: 'credentialsChange', callback: () => void): this;
|
||||||
|
|
||||||
|
public on(
|
||||||
|
type: string | symbol,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
listener: (...args: Array<any>) => void
|
||||||
|
): this {
|
||||||
|
return super.on(type, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public emit(type: 'credentialsChange'): boolean;
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
public emit(type: string | symbol, ...args: Array<any>): boolean {
|
||||||
|
return super.emit(type, ...args);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
// Copyright 2021 Signal Messenger, LLC
|
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
import type { WebAPIConnectType, WebAPIType } from '../textsecure/WebAPI';
|
|
||||||
import { StorageInterface } from '../types/Storage.d';
|
|
||||||
|
|
||||||
export function connectToServerWithStoredCredentials(
|
|
||||||
WebAPI: WebAPIConnectType,
|
|
||||||
storage: Pick<StorageInterface, 'get'>
|
|
||||||
): WebAPIType {
|
|
||||||
const username = storage.get('uuid_id') || storage.get('number_id');
|
|
||||||
if (typeof username !== 'string') {
|
|
||||||
throw new Error(
|
|
||||||
'Username in storage was not a string. Cannot connect to WebAPI'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const password = storage.get('password');
|
|
||||||
if (typeof password !== 'string') {
|
|
||||||
throw new Error(
|
|
||||||
'Password in storage was not a string. Cannot connect to WebAPI'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return WebAPI.connect({ username, password });
|
|
||||||
}
|
|
2
ts/window.d.ts
vendored
2
ts/window.d.ts
vendored
|
@ -175,7 +175,7 @@ declare global {
|
||||||
receivedAtCounter: number;
|
receivedAtCounter: number;
|
||||||
enterKeyboardMode: () => void;
|
enterKeyboardMode: () => void;
|
||||||
enterMouseMode: () => void;
|
enterMouseMode: () => void;
|
||||||
getAccountManager: () => AccountManager | undefined;
|
getAccountManager: () => AccountManager;
|
||||||
getAlwaysRelayCalls: () => Promise<boolean>;
|
getAlwaysRelayCalls: () => Promise<boolean>;
|
||||||
getBuiltInImages: () => Promise<Array<string>>;
|
getBuiltInImages: () => Promise<Array<string>>;
|
||||||
getCallRingtoneNotification: () => Promise<boolean>;
|
getCallRingtoneNotification: () => Promise<boolean>;
|
||||||
|
|
Loading…
Reference in a new issue