background/connect: Prevent concurrent calls to connect

This commit is contained in:
Scott Nonnenberg 2020-11-11 08:24:29 -08:00 committed by GitHub
parent 1d1fccd96f
commit cd599f92c8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -1716,333 +1716,348 @@ type WhatIsThis = typeof window.WhatIsThis;
}
let connectCount = 0;
let connecting = false;
async function connect(firstRun?: boolean) {
window.log.info('connect', { firstRun, connectCount });
if (reconnectTimer) {
clearTimeout(reconnectTimer);
reconnectTimer = null;
}
// Bootstrap our online/offline detection, only the first time we connect
if (connectCount === 0 && navigator.onLine) {
window.addEventListener('offline', onOffline);
}
if (connectCount === 0 && !navigator.onLine) {
window.log.warn(
'Starting up offline; will connect when we have network access'
);
window.addEventListener('online', onOnline);
onEmpty(); // this ensures that the loading screen is dismissed
if (connecting) {
window.log.warn('connect already running', { connectCount });
return;
}
try {
connecting = true;
if (!window.Signal.Util.Registration.everDone()) {
return;
}
window.log.info('connect', { firstRun, connectCount });
preMessageReceiverStatus = WebSocket.CONNECTING;
if (messageReceiver) {
await messageReceiver.stopProcessing();
await window.waitForAllBatchers();
messageReceiver.unregisterBatchers();
messageReceiver = null;
}
const OLD_USERNAME = window.storage.get('number_id');
const USERNAME = window.storage.get('uuid_id');
const PASSWORD = window.storage.get('password');
const mySignalingKey = window.storage.get('signaling_key');
window.textsecure.messaging = new window.textsecure.MessageSender(
USERNAME || OLD_USERNAME,
PASSWORD
);
if (connectCount === 0) {
try {
// Force a re-fetch before we process our queue. We may want to turn on something
// which changes how we process incoming messages!
await window.Signal.RemoteConfig.refreshRemoteConfig();
} catch (error) {
window.log.error(
'connect: Error refreshing remote config:',
error && error.stack ? error.stack : error
);
if (reconnectTimer) {
clearTimeout(reconnectTimer);
reconnectTimer = null;
}
try {
if (window.Signal.RemoteConfig.isEnabled('desktop.cds')) {
const lonelyE164s = window
.getConversations()
.filter(c =>
Boolean(
c.isPrivate() &&
c.get('e164') &&
!c.get('uuid') &&
!c.isEverUnregistered()
)
)
.map(c => c.get('e164'));
if (lonelyE164s.length > 0) {
const lookup = await window.textsecure.messaging.getUuidsForE164s(
lonelyE164s as WhatIsThis
);
const e164s = Object.keys(lookup);
e164s.forEach(e164 => {
const uuid = lookup[e164];
if (!uuid) {
const byE164 = window.ConversationController.get(e164);
if (byE164) {
byE164.setUnregistered();
}
}
window.ConversationController.ensureContactIds({
e164,
uuid,
highTrust: true,
});
});
}
}
} catch (error) {
window.log.error(
'connect: Error fetching UUIDs for lonely e164s:',
error && error.stack ? error.stack : error
);
// Bootstrap our online/offline detection, only the first time we connect
if (connectCount === 0 && navigator.onLine) {
window.addEventListener('offline', onOffline);
}
if (connectCount === 0 && !navigator.onLine) {
window.log.warn(
'Starting up offline; will connect when we have network access'
);
window.addEventListener('online', onOnline);
onEmpty(); // this ensures that the loading screen is dismissed
return;
}
}
connectCount += 1;
if (!window.Signal.Util.Registration.everDone()) {
return;
}
window.Whisper.deliveryReceiptQueue.pause(); // avoid flood of delivery receipts until we catch up
window.Whisper.Notifications.disable(); // avoid notification flood until empty
preMessageReceiverStatus = WebSocket.CONNECTING;
// initialize the socket and start listening for messages
window.log.info('Initializing socket and listening for messages');
const messageReceiverOptions = {
serverTrustRoot: window.getServerTrustRoot(),
};
messageReceiver = new window.textsecure.MessageReceiver(
OLD_USERNAME,
USERNAME,
PASSWORD,
mySignalingKey,
messageReceiverOptions as WhatIsThis
);
window.textsecure.messageReceiver = messageReceiver;
if (messageReceiver) {
await messageReceiver.stopProcessing();
window.Signal.Services.initializeGroupCredentialFetcher();
await window.waitForAllBatchers();
messageReceiver.unregisterBatchers();
preMessageReceiverStatus = null;
messageReceiver = null;
}
function addQueuedEventListener(name: WhatIsThis, handler: WhatIsThis) {
messageReceiver.addEventListener(name, (...args: Array<WhatIsThis>) =>
eventHandlerQueue.add(async () => {
try {
await handler(...args);
} finally {
// message/sent: Message.handleDataMessage has its own queue and will trigger
// this event itself when complete.
// error: Error processing (below) also has its own queue and self-trigger.
if (name !== 'message' && name !== 'sent' && name !== 'error') {
window.Whisper.events.trigger('incrementProgress');
}
}
})
const OLD_USERNAME = window.storage.get('number_id');
const USERNAME = window.storage.get('uuid_id');
const PASSWORD = window.storage.get('password');
const mySignalingKey = window.storage.get('signaling_key');
window.textsecure.messaging = new window.textsecure.MessageSender(
USERNAME || OLD_USERNAME,
PASSWORD
);
}
addQueuedEventListener('message', onMessageReceived);
addQueuedEventListener('delivery', onDeliveryReceipt);
addQueuedEventListener('contact', onContactReceived);
addQueuedEventListener('group', onGroupReceived);
addQueuedEventListener('sent', onSentMessage);
addQueuedEventListener('readSync', onReadSync);
addQueuedEventListener('read', onReadReceipt);
addQueuedEventListener('verified', onVerified);
addQueuedEventListener('error', onError);
addQueuedEventListener('empty', onEmpty);
addQueuedEventListener('reconnect', onReconnect);
addQueuedEventListener('configuration', onConfiguration);
addQueuedEventListener('typing', onTyping);
addQueuedEventListener('sticker-pack', onStickerPack);
addQueuedEventListener('viewSync', onViewSync);
addQueuedEventListener('messageRequestResponse', onMessageRequestResponse);
addQueuedEventListener('profileKeyUpdate', onProfileKeyUpdate);
addQueuedEventListener('fetchLatest', onFetchLatestSync);
addQueuedEventListener('keys', onKeysSync);
window.Signal.AttachmentDownloads.start({
getMessageReceiver: () => messageReceiver,
logger: window.log,
});
if (connectCount === 1) {
window.Signal.Stickers.downloadQueuedPacks();
if (!newVersion) {
runStorageService();
}
}
// On startup after upgrading to a new version, request a contact sync
// (but only if we're not the primary device)
if (
!firstRun &&
connectCount === 1 &&
newVersion &&
// eslint-disable-next-line eqeqeq
window.textsecure.storage.user.getDeviceId() != '1'
) {
window.log.info('Boot after upgrading. Requesting contact sync');
window.getSyncRequest();
runStorageService();
try {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const manager = window.getAccountManager()!;
await Promise.all([
manager.maybeUpdateDeviceName(),
manager.maybeDeleteSignalingKey(),
]);
} catch (e) {
window.log.error(
'Problem with account manager updates after starting new version: ',
e && e.stack ? e.stack : e
);
}
}
const udSupportKey = 'hasRegisterSupportForUnauthenticatedDelivery';
if (!window.storage.get(udSupportKey)) {
const server = window.WebAPI.connect({
username: USERNAME || OLD_USERNAME,
password: PASSWORD,
});
try {
await server.registerSupportForUnauthenticatedDelivery();
window.storage.put(udSupportKey, true);
} catch (error) {
window.log.error(
'Error: Unable to register for unauthenticated delivery support.',
error && error.stack ? error.stack : error
);
}
}
const deviceId = window.textsecure.storage.user.getDeviceId();
// If we didn't capture a UUID on registration, go get it from the server
if (!window.textsecure.storage.user.getUuid()) {
const server = window.WebAPI.connect({
username: OLD_USERNAME,
password: PASSWORD,
});
try {
const { uuid } = await server.whoami();
window.textsecure.storage.user.setUuidAndDeviceId(
uuid,
deviceId as WhatIsThis
);
const ourNumber = window.textsecure.storage.user.getNumber();
const me = await window.ConversationController.getOrCreateAndWait(
ourNumber,
'private'
);
me.updateUuid(uuid);
} catch (error) {
window.log.error(
'Error: Unable to retrieve UUID from service.',
error && error.stack ? error.stack : error
);
}
}
// We need to do this after fetching our UUID
const hasRegisteredGV23Support = 'hasRegisteredGV23Support';
if (
!window.storage.get(hasRegisteredGV23Support) &&
window.textsecure.storage.user.getUuid()
) {
const server = window.WebAPI.connect({
username: USERNAME || OLD_USERNAME,
password: PASSWORD,
});
try {
await server.registerCapabilities({ 'gv2-3': true });
window.storage.put(hasRegisteredGV23Support, true);
} catch (error) {
window.log.error(
'Error: Unable to register support for GV2.',
error && error.stack ? error.stack : error
);
}
}
if (firstRun === true && deviceId !== '1') {
const hasThemeSetting = Boolean(window.storage.get('theme-setting'));
if (
!hasThemeSetting &&
window.textsecure.storage.get('userAgent') === 'OWI'
) {
window.storage.put('theme-setting', 'ios');
onChangeTheme();
}
const syncRequest = new window.textsecure.SyncRequest(
window.textsecure.messaging,
messageReceiver
);
window.Whisper.events.trigger('contactsync:begin');
syncRequest.addEventListener('success', () => {
window.log.info('sync successful');
window.storage.put('synced_at', Date.now());
window.Whisper.events.trigger('contactsync');
runStorageService();
});
syncRequest.addEventListener('timeout', () => {
window.log.error('sync timed out');
window.Whisper.events.trigger('contactsync');
runStorageService();
});
const ourId = window.ConversationController.getOurConversationId();
const {
wrap,
sendOptions,
} = window.ConversationController.prepareForSend(ourId, {
syncMessage: true,
});
const installedStickerPacks = window.Signal.Stickers.getInstalledStickerPacks();
if (installedStickerPacks.length) {
const operations = installedStickerPacks.map((pack: WhatIsThis) => ({
packId: pack.id,
packKey: pack.key,
installed: true,
}));
wrap(
window.textsecure.messaging.sendStickerPackSync(
operations,
sendOptions
)
).catch(error => {
if (connectCount === 0) {
try {
// Force a re-fetch before we process our queue. We may want to turn on
// something which changes how we process incoming messages!
await window.Signal.RemoteConfig.refreshRemoteConfig();
} catch (error) {
window.log.error(
'Failed to send installed sticker packs via sync message',
'connect: Error refreshing remote config:',
error && error.stack ? error.stack : error
);
});
}
}
}
window.storage.onready(async () => {
idleDetector.start();
});
try {
if (window.Signal.RemoteConfig.isEnabled('desktop.cds')) {
const lonelyE164s = window
.getConversations()
.filter(c =>
Boolean(
c.isPrivate() &&
c.get('e164') &&
!c.get('uuid') &&
!c.isEverUnregistered()
)
)
.map(c => c.get('e164'));
if (lonelyE164s.length > 0) {
const lookup = await window.textsecure.messaging.getUuidsForE164s(
lonelyE164s as WhatIsThis
);
const e164s = Object.keys(lookup);
e164s.forEach(e164 => {
const uuid = lookup[e164];
if (!uuid) {
const byE164 = window.ConversationController.get(e164);
if (byE164) {
byE164.setUnregistered();
}
}
window.ConversationController.ensureContactIds({
e164,
uuid,
highTrust: true,
});
});
}
}
} catch (error) {
window.log.error(
'connect: Error fetching UUIDs for lonely e164s:',
error && error.stack ? error.stack : error
);
}
}
connectCount += 1;
window.Whisper.deliveryReceiptQueue.pause(); // avoid flood of delivery receipts until we catch up
window.Whisper.Notifications.disable(); // avoid notification flood until empty
// initialize the socket and start listening for messages
window.log.info('Initializing socket and listening for messages');
const messageReceiverOptions = {
serverTrustRoot: window.getServerTrustRoot(),
};
messageReceiver = new window.textsecure.MessageReceiver(
OLD_USERNAME,
USERNAME,
PASSWORD,
mySignalingKey,
messageReceiverOptions as WhatIsThis
);
window.textsecure.messageReceiver = messageReceiver;
window.Signal.Services.initializeGroupCredentialFetcher();
preMessageReceiverStatus = null;
// eslint-disable-next-line no-inner-declarations
function addQueuedEventListener(name: WhatIsThis, handler: WhatIsThis) {
messageReceiver.addEventListener(name, (...args: Array<WhatIsThis>) =>
eventHandlerQueue.add(async () => {
try {
await handler(...args);
} finally {
// message/sent: Message.handleDataMessage has its own queue and will
// trigger this event itself when complete.
// error: Error processing (below) also has its own queue and self-trigger.
if (name !== 'message' && name !== 'sent' && name !== 'error') {
window.Whisper.events.trigger('incrementProgress');
}
}
})
);
}
addQueuedEventListener('message', onMessageReceived);
addQueuedEventListener('delivery', onDeliveryReceipt);
addQueuedEventListener('contact', onContactReceived);
addQueuedEventListener('group', onGroupReceived);
addQueuedEventListener('sent', onSentMessage);
addQueuedEventListener('readSync', onReadSync);
addQueuedEventListener('read', onReadReceipt);
addQueuedEventListener('verified', onVerified);
addQueuedEventListener('error', onError);
addQueuedEventListener('empty', onEmpty);
addQueuedEventListener('reconnect', onReconnect);
addQueuedEventListener('configuration', onConfiguration);
addQueuedEventListener('typing', onTyping);
addQueuedEventListener('sticker-pack', onStickerPack);
addQueuedEventListener('viewSync', onViewSync);
addQueuedEventListener(
'messageRequestResponse',
onMessageRequestResponse
);
addQueuedEventListener('profileKeyUpdate', onProfileKeyUpdate);
addQueuedEventListener('fetchLatest', onFetchLatestSync);
addQueuedEventListener('keys', onKeysSync);
window.Signal.AttachmentDownloads.start({
getMessageReceiver: () => messageReceiver,
logger: window.log,
});
if (connectCount === 1) {
window.Signal.Stickers.downloadQueuedPacks();
if (!newVersion) {
runStorageService();
}
}
// On startup after upgrading to a new version, request a contact sync
// (but only if we're not the primary device)
if (
!firstRun &&
connectCount === 1 &&
newVersion &&
// eslint-disable-next-line eqeqeq
window.textsecure.storage.user.getDeviceId() != '1'
) {
window.log.info('Boot after upgrading. Requesting contact sync');
window.getSyncRequest();
runStorageService();
try {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const manager = window.getAccountManager()!;
await Promise.all([
manager.maybeUpdateDeviceName(),
manager.maybeDeleteSignalingKey(),
]);
} catch (e) {
window.log.error(
'Problem with account manager updates after starting new version: ',
e && e.stack ? e.stack : e
);
}
}
const udSupportKey = 'hasRegisterSupportForUnauthenticatedDelivery';
if (!window.storage.get(udSupportKey)) {
const server = window.WebAPI.connect({
username: USERNAME || OLD_USERNAME,
password: PASSWORD,
});
try {
await server.registerSupportForUnauthenticatedDelivery();
window.storage.put(udSupportKey, true);
} catch (error) {
window.log.error(
'Error: Unable to register for unauthenticated delivery support.',
error && error.stack ? error.stack : error
);
}
}
const deviceId = window.textsecure.storage.user.getDeviceId();
// If we didn't capture a UUID on registration, go get it from the server
if (!window.textsecure.storage.user.getUuid()) {
const server = window.WebAPI.connect({
username: OLD_USERNAME,
password: PASSWORD,
});
try {
const { uuid } = await server.whoami();
window.textsecure.storage.user.setUuidAndDeviceId(
uuid,
deviceId as WhatIsThis
);
const ourNumber = window.textsecure.storage.user.getNumber();
const me = await window.ConversationController.getOrCreateAndWait(
ourNumber,
'private'
);
me.updateUuid(uuid);
} catch (error) {
window.log.error(
'Error: Unable to retrieve UUID from service.',
error && error.stack ? error.stack : error
);
}
}
// We need to do this after fetching our UUID
const hasRegisteredGV23Support = 'hasRegisteredGV23Support';
if (
!window.storage.get(hasRegisteredGV23Support) &&
window.textsecure.storage.user.getUuid()
) {
const server = window.WebAPI.connect({
username: USERNAME || OLD_USERNAME,
password: PASSWORD,
});
try {
await server.registerCapabilities({ 'gv2-3': true });
window.storage.put(hasRegisteredGV23Support, true);
} catch (error) {
window.log.error(
'Error: Unable to register support for GV2.',
error && error.stack ? error.stack : error
);
}
}
if (firstRun === true && deviceId !== '1') {
const hasThemeSetting = Boolean(window.storage.get('theme-setting'));
if (
!hasThemeSetting &&
window.textsecure.storage.get('userAgent') === 'OWI'
) {
window.storage.put('theme-setting', 'ios');
onChangeTheme();
}
const syncRequest = new window.textsecure.SyncRequest(
window.textsecure.messaging,
messageReceiver
);
window.Whisper.events.trigger('contactsync:begin');
syncRequest.addEventListener('success', () => {
window.log.info('sync successful');
window.storage.put('synced_at', Date.now());
window.Whisper.events.trigger('contactsync');
runStorageService();
});
syncRequest.addEventListener('timeout', () => {
window.log.error('sync timed out');
window.Whisper.events.trigger('contactsync');
runStorageService();
});
const ourId = window.ConversationController.getOurConversationId();
const {
wrap,
sendOptions,
} = window.ConversationController.prepareForSend(ourId, {
syncMessage: true,
});
const installedStickerPacks = window.Signal.Stickers.getInstalledStickerPacks();
if (installedStickerPacks.length) {
const operations = installedStickerPacks.map((pack: WhatIsThis) => ({
packId: pack.id,
packKey: pack.key,
installed: true,
}));
wrap(
window.textsecure.messaging.sendStickerPackSync(
operations,
sendOptions
)
).catch(error => {
window.log.error(
'Failed to send installed sticker packs via sync message',
error && error.stack ? error.stack : error
);
});
}
}
window.storage.onready(async () => {
idleDetector.start();
});
} finally {
connecting = false;
}
}
function onChangeTheme() {