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…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Fedor Indutny
				Fedor Indutny