|  |  |  | /* global window, setTimeout, clearTimeout, textsecure, WebAPI, ConversationController */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | module.exports = { | 
					
						
							|  |  |  |   initialize, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const ONE_DAY = 24 * 60 * 60 * 1000; // one day
 | 
					
						
							|  |  |  | const MINIMUM_TIME_LEFT = 2 * 60 * 60 * 1000; // two hours
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | let timeout = null; | 
					
						
							|  |  |  | let scheduledTime = null; | 
					
						
							|  |  |  | let scheduleNext = null; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // We need to refresh our own profile regularly to account for newly-added devices which
 | 
					
						
							|  |  |  | //   do not support unidentified delivery.
 | 
					
						
							|  |  |  | function refreshOurProfile() { | 
					
						
							|  |  |  |   const ourNumber = textsecure.storage.user.getNumber(); | 
					
						
							|  |  |  |   const conversation = ConversationController.getOrCreate(ourNumber, 'private'); | 
					
						
							|  |  |  |   conversation.getProfiles(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function initialize({ events, storage, navigator, logger }) { | 
					
						
							|  |  |  |   // We don't want to set up all of the below functions, but we do want to ensure that our
 | 
					
						
							|  |  |  |   //   refresh timer is up-to-date.
 | 
					
						
							|  |  |  |   if (scheduleNext) { | 
					
						
							|  |  |  |     scheduleNext(); | 
					
						
							|  |  |  |     return; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   runWhenOnline(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   events.on('timetravel', scheduleNextRotation); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   function scheduleNextRotation() { | 
					
						
							|  |  |  |     const now = Date.now(); | 
					
						
							|  |  |  |     const certificate = storage.get('senderCertificate'); | 
					
						
							|  |  |  |     if (!certificate) { | 
					
						
							|  |  |  |       setTimeoutForNextRun(now); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // The useful information in a SenderCertificate is all serialized, so we
 | 
					
						
							|  |  |  |     //   need to do another layer of decoding.
 | 
					
						
							|  |  |  |     const decoded = textsecure.protobuf.SenderCertificate.Certificate.decode( | 
					
						
							|  |  |  |       certificate.certificate | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     const expires = decoded.expires.toNumber(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const time = Math.min(now + ONE_DAY, expires - MINIMUM_TIME_LEFT); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     setTimeoutForNextRun(time); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Keeping this entrypoint around so more inialize() calls just kick the timing
 | 
					
						
							|  |  |  |   scheduleNext = scheduleNextRotation; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async function run() { | 
					
						
							|  |  |  |     logger.info('refreshSenderCertificate: Getting new certificate...'); | 
					
						
							|  |  |  |     try { | 
					
						
							|  |  |  |       const username = storage.get('number_id'); | 
					
						
							|  |  |  |       const password = storage.get('password'); | 
					
						
							|  |  |  |       const server = WebAPI.connect({ username, password }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       const { certificate } = await server.getSenderCertificate(); | 
					
						
							|  |  |  |       const arrayBuffer = window.Signal.Crypto.base64ToArrayBuffer(certificate); | 
					
						
							|  |  |  |       const decoded = textsecure.protobuf.SenderCertificate.decode(arrayBuffer); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       decoded.certificate = decoded.certificate.toArrayBuffer(); | 
					
						
							|  |  |  |       decoded.signature = decoded.signature.toArrayBuffer(); | 
					
						
							|  |  |  |       decoded.serialized = arrayBuffer; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       storage.put('senderCertificate', decoded); | 
					
						
							|  |  |  |       scheduleNextRotation(); | 
					
						
							|  |  |  |     } catch (error) { | 
					
						
							|  |  |  |       logger.error( | 
					
						
							|  |  |  |         'refreshSenderCertificate: Get failed. Trying again in two minutes...', | 
					
						
							|  |  |  |         error && error.stack ? error.stack : error | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |       setTimeout(runWhenOnline, 2 * 60 * 1000); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     refreshOurProfile(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   function runWhenOnline() { | 
					
						
							|  |  |  |     if (navigator.onLine) { | 
					
						
							|  |  |  |       run(); | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       logger.info( | 
					
						
							|  |  |  |         'refreshSenderCertificate: Offline. Will update certificate when online...' | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |       const listener = () => { | 
					
						
							|  |  |  |         logger.info( | 
					
						
							|  |  |  |           'refreshSenderCertificate: Online. Now updating certificate...' | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |         window.removeEventListener('online', listener); | 
					
						
							|  |  |  |         run(); | 
					
						
							|  |  |  |       }; | 
					
						
							|  |  |  |       window.addEventListener('online', listener); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   function setTimeoutForNextRun(time = Date.now()) { | 
					
						
							|  |  |  |     const now = Date.now(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (scheduledTime !== time || !timeout) { | 
					
						
							|  |  |  |       logger.info( | 
					
						
							|  |  |  |         'Next sender certificate refresh scheduled for', | 
					
						
							|  |  |  |         new Date(time).toISOString() | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     scheduledTime = time; | 
					
						
							|  |  |  |     const waitTime = Math.max(0, time - now); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     clearTimeout(timeout); | 
					
						
							|  |  |  |     timeout = setTimeout(runWhenOnline, waitTime); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } |