Use single WebAPI instance across the app

This commit is contained in:
Fedor Indutny 2021-07-23 10:23:50 -07:00 committed by GitHub
parent 79633a9e7b
commit fdec47d637
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 218 additions and 308 deletions

View file

@ -70,16 +70,13 @@ type GeneratedKeysType = {
};
export default class AccountManager extends EventTarget {
server: WebAPIType;
pending: Promise<void>;
pendingQueue?: PQueue;
constructor(username: string, password: string) {
constructor(private readonly server: WebAPIType) {
super();
this.server = window.WebAPI.connect({ username, password });
this.pending = Promise.resolve();
}
@ -569,34 +566,27 @@ export default class AccountManager extends EventTarget {
await Promise.all([
window.textsecure.storage.remove('identityKey'),
window.textsecure.storage.remove('password'),
window.textsecure.storage.user.removeCredentials(),
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('userAgent'),
window.textsecure.storage.remove('profileKey'),
window.textsecure.storage.remove('read-receipt-setting'),
]);
// `setNumberAndDeviceId` and `setUuidAndDeviceId` need to be called
// `setCredentials` needs to be called
// before `saveIdentifyWithAttributes` since `saveIdentityWithAttributes`
// indirectly calls `ConversationController.getConverationId()` 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
// information.
await window.textsecure.storage.user.setNumberAndDeviceId(
await window.textsecure.storage.user.setCredentials({
uuid,
number,
response.deviceId || 1,
deviceName || undefined
);
if (uuid) {
await window.textsecure.storage.user.setUuidAndDeviceId(
uuid,
response.deviceId || 1
);
}
deviceId: response.deviceId ?? 1,
deviceName: deviceName ?? undefined,
password,
});
// 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
@ -625,7 +615,6 @@ export default class AccountManager extends EventTarget {
);
await window.textsecure.storage.put('identityKey', identityKeyPair);
await window.textsecure.storage.put('password', password);
await window.textsecure.storage.put('registrationId', registrationId);
if (profileKey) {
await ourProfileKeyService.set(profileKey);

View file

@ -53,7 +53,6 @@ import { processAttachment, processDataMessage } from './processDataMessage';
import { processSyncMessage } from './processSyncMessage';
import EventTarget, { EventHandler } from './EventTarget';
import { WebAPIType } from './WebAPI';
import utils from './Helpers';
import WebSocketResource, {
IncomingWebSocketRequest,
CloseEvent,
@ -186,16 +185,12 @@ class MessageReceiverInner extends EventTarget {
number_id?: string;
password: string;
encryptedQueue: PQueue;
decryptedQueue: PQueue;
retryCachedTimeout: any;
server: WebAPIType;
serverTrustRoot: Uint8Array;
socket?: WebSocket;
@ -204,10 +199,6 @@ class MessageReceiverInner extends EventTarget {
stoppingProcessing?: boolean;
username: string;
uuid: string;
uuid_id?: string;
wsr?: WebSocketResource;
@ -215,9 +206,7 @@ class MessageReceiverInner extends EventTarget {
private readonly reconnectBackOff = new BackOff(FIBONACCI_TIMEOUTS);
constructor(
oldUsername: string,
username: string,
password: string,
public readonly server: WebAPIType,
options: {
serverTrustRoot: string;
}
@ -227,30 +216,14 @@ class MessageReceiverInner extends EventTarget {
this.count = 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) {
throw new Error('Server trust root is required!');
}
this.serverTrustRoot = Bytes.fromBase64(options.serverTrustRoot);
this.number_id = oldUsername
? utils.unencodeNumber(oldUsername)[0]
: undefined;
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.number_id = window.textsecure.storage.user.getNumber();
this.uuid_id = window.textsecure.storage.user.getUuid();
this.deviceId = window.textsecure.storage.user.getDeviceId();
this.incomingQueue = 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;
constructor(
oldUsername: string,
username: string,
password: string,
server: WebAPIType,
options: {
serverTrustRoot: string;
retryCached?: string;
socket?: WebSocket;
}
) {
const inner = new MessageReceiverInner(
oldUsername,
username,
password,
options
);
const inner = new MessageReceiverInner(server, options);
this.inner = inner;
this.close = inner.close.bind(inner);

View file

@ -439,14 +439,11 @@ class Message {
}
export default class MessageSender {
server: WebAPIType;
pendingMessages: {
[id: string]: PQueue;
};
constructor(username: string, password: string) {
this.server = window.WebAPI.connect({ username, password });
constructor(public readonly server: WebAPIType) {
this.pendingMessages = {};
}

View file

@ -23,6 +23,11 @@ export type StorageServiceCredentials = {
password: string;
};
export type WebAPICredentials = {
username: string;
password: string;
};
export type DeviceType = {
id: number;
identifier: string;

View file

@ -59,6 +59,7 @@ import { SignalService as Proto } from '../protobuf';
import { ConnectTimeoutError } from './Errors';
import MessageSender from './SendMessage';
import { WebAPICredentials } from './Types.d';
// TODO: remove once we move away from ArrayBuffers
const FIXMEU8 = Uint8Array;
@ -859,11 +860,6 @@ type InitializeOptionsType = {
version: string;
};
type ConnectParametersType = {
username: string;
password: string;
};
type MessageType = any;
type AjaxOptionsType = {
@ -888,7 +884,7 @@ type AjaxOptionsType = {
};
export type WebAPIConnectType = {
connect: (options: ConnectParametersType) => WebAPIType;
connect: (options: WebAPICredentials) => WebAPIType;
};
export type CapabilitiesType = {
@ -1089,6 +1085,7 @@ export type WebAPIType = {
getConfig: () => Promise<
Array<{ name: string; enabled: boolean; value: string | null }>
>;
authenticate: (credentials: WebAPICredentials) => Promise<void>;
};
export type SignedPreKeyType = {
@ -1197,7 +1194,7 @@ export function initialize({
function connect({
username: initialUsername,
password: initialPassword,
}: ConnectParametersType) {
}: WebAPICredentials) {
let username = initialUsername;
let password = initialPassword;
const PARSE_RANGE_HEADER = /\/(\d+)$/;
@ -1205,6 +1202,7 @@ export function initialize({
// Thanks, function hoisting!
return {
authenticate,
confirmCode,
createGroup,
fetchLinkPreviewImage,
@ -1307,6 +1305,14 @@ export function initialize({
});
}
async function authenticate({
username: newUsername,
password: newPassword,
}: WebAPICredentials) {
username = newUsername;
password = newPassword;
}
async function getConfig() {
type ResType = {
config: Array<{ name: string; enabled: boolean; value: string | null }>;

View file

@ -1,29 +1,35 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { EventEmitter } from 'events';
import { WebAPICredentials } from '../Types.d';
import { StorageInterface } from '../../types/Storage.d';
import Helpers from '../Helpers';
export class User {
constructor(private readonly storage: StorageInterface) {}
export type SetCredentialsOptions = {
uuid?: string;
number: string;
deviceId: number;
deviceName?: string;
password: string;
};
public async setNumberAndDeviceId(
number: string,
deviceId: number,
deviceName?: string
): Promise<void> {
await this.storage.put('number_id', `${number}.${deviceId}`);
if (deviceName) {
await this.storage.put('device_name', deviceName);
}
export class User extends EventEmitter {
constructor(private readonly storage: StorageInterface) {
super();
}
public async setUuidAndDeviceId(
uuid: string,
deviceId: number
): 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 {
@ -62,6 +68,41 @@ export class User {
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 {
const uuid = this.storage.get('uuid_id');
if (uuid === undefined) return undefined;
@ -73,4 +114,25 @@ export class User {
if (numberId === undefined) return undefined;
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);
}
}