| 
									
										
										
										
											2018-07-26 18:13:56 -07:00
										 |  |  | /* global window, IDBKeyRange */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-16 09:50:56 -07:00
										 |  |  | const { includes, isFunction, isString, last, map } = require('lodash'); | 
					
						
							| 
									
										
										
										
											2018-07-31 19:51:17 -07:00
										 |  |  | const { | 
					
						
							|  |  |  |   saveMessages, | 
					
						
							|  |  |  |   _removeMessages, | 
					
						
							|  |  |  |   saveUnprocesseds, | 
					
						
							|  |  |  |   removeUnprocessed, | 
					
						
							|  |  |  | } = require('./data'); | 
					
						
							| 
									
										
										
										
											2018-07-26 18:13:56 -07:00
										 |  |  | const { | 
					
						
							|  |  |  |   getMessageExportLastIndex, | 
					
						
							|  |  |  |   setMessageExportLastIndex, | 
					
						
							| 
									
										
										
										
											2018-08-01 18:34:50 -07:00
										 |  |  |   getMessageExportCount, | 
					
						
							|  |  |  |   setMessageExportCount, | 
					
						
							| 
									
										
										
										
											2018-07-26 18:13:56 -07:00
										 |  |  |   getUnprocessedExportLastIndex, | 
					
						
							|  |  |  |   setUnprocessedExportLastIndex, | 
					
						
							|  |  |  | } = require('./settings'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | module.exports = { | 
					
						
							|  |  |  |   migrateToSQL, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-01 18:34:50 -07:00
										 |  |  | async function migrateToSQL({ | 
					
						
							|  |  |  |   db, | 
					
						
							|  |  |  |   clearStores, | 
					
						
							|  |  |  |   handleDOMException, | 
					
						
							|  |  |  |   countCallback, | 
					
						
							| 
									
										
										
										
											2018-08-06 12:11:02 -07:00
										 |  |  |   arrayBufferToString, | 
					
						
							| 
									
										
										
										
											2018-08-01 18:34:50 -07:00
										 |  |  | }) { | 
					
						
							| 
									
										
										
										
											2018-07-26 18:13:56 -07:00
										 |  |  |   if (!db) { | 
					
						
							|  |  |  |     throw new Error('Need db for IndexedDB connection!'); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   if (!isFunction(clearStores)) { | 
					
						
							|  |  |  |     throw new Error('Need clearStores function!'); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2018-08-06 12:11:02 -07:00
										 |  |  |   if (!isFunction(arrayBufferToString)) { | 
					
						
							|  |  |  |     throw new Error('Need arrayBufferToString function!'); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2018-07-26 18:13:56 -07:00
										 |  |  |   if (!isFunction(handleDOMException)) { | 
					
						
							|  |  |  |     throw new Error('Need handleDOMException function!'); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   window.log.info('migrateToSQL: start'); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-01 18:34:50 -07:00
										 |  |  |   let [lastIndex, doneSoFar] = await Promise.all([ | 
					
						
							|  |  |  |     getMessageExportLastIndex(db), | 
					
						
							|  |  |  |     getMessageExportCount(db), | 
					
						
							|  |  |  |   ]); | 
					
						
							| 
									
										
										
										
											2018-07-26 18:13:56 -07:00
										 |  |  |   let complete = false; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   while (!complete) { | 
					
						
							|  |  |  |     // eslint-disable-next-line no-await-in-loop
 | 
					
						
							|  |  |  |     const status = await migrateStoreToSQLite({ | 
					
						
							|  |  |  |       db, | 
					
						
							|  |  |  |       save: saveMessages, | 
					
						
							| 
									
										
										
										
											2018-07-31 19:51:17 -07:00
										 |  |  |       remove: _removeMessages, | 
					
						
							| 
									
										
										
										
											2018-07-26 18:13:56 -07:00
										 |  |  |       storeName: 'messages', | 
					
						
							|  |  |  |       handleDOMException, | 
					
						
							|  |  |  |       lastIndex, | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     ({ complete, lastIndex } = status); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // eslint-disable-next-line no-await-in-loop
 | 
					
						
							| 
									
										
										
										
											2018-08-01 18:34:50 -07:00
										 |  |  |     await Promise.all([ | 
					
						
							|  |  |  |       setMessageExportCount(db, doneSoFar), | 
					
						
							|  |  |  |       setMessageExportLastIndex(db, lastIndex), | 
					
						
							|  |  |  |     ]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const { count } = status; | 
					
						
							|  |  |  |     doneSoFar += count; | 
					
						
							|  |  |  |     if (countCallback) { | 
					
						
							|  |  |  |       countCallback(doneSoFar); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2018-07-26 18:13:56 -07:00
										 |  |  |   } | 
					
						
							|  |  |  |   window.log.info('migrateToSQL: migrate of messages complete'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   lastIndex = await getUnprocessedExportLastIndex(db); | 
					
						
							|  |  |  |   complete = false; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   while (!complete) { | 
					
						
							|  |  |  |     // eslint-disable-next-line no-await-in-loop
 | 
					
						
							|  |  |  |     const status = await migrateStoreToSQLite({ | 
					
						
							|  |  |  |       db, | 
					
						
							| 
									
										
										
										
											2018-08-06 12:11:02 -07:00
										 |  |  |       save: async array => { | 
					
						
							| 
									
										
										
										
											2018-08-16 09:50:56 -07:00
										 |  |  |         await Promise.all( | 
					
						
							|  |  |  |           map(array, async item => { | 
					
						
							|  |  |  |             // In the new database, we can't store ArrayBuffers, so we turn these two
 | 
					
						
							|  |  |  |             //   fields into strings like MessageReceiver now does before save.
 | 
					
						
							| 
									
										
										
										
											2018-08-09 17:28:51 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-16 09:50:56 -07:00
										 |  |  |             // Need to set it to version two, since we're using Base64 strings now
 | 
					
						
							| 
									
										
										
										
											2018-08-06 12:11:02 -07:00
										 |  |  |             // eslint-disable-next-line no-param-reassign
 | 
					
						
							| 
									
										
										
										
											2018-08-16 09:50:56 -07:00
										 |  |  |             item.version = 2; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if (item.envelope) { | 
					
						
							|  |  |  |               // eslint-disable-next-line no-param-reassign
 | 
					
						
							|  |  |  |               item.envelope = await arrayBufferToString(item.envelope); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             if (item.decrypted) { | 
					
						
							|  |  |  |               // eslint-disable-next-line no-param-reassign
 | 
					
						
							|  |  |  |               item.decrypted = await arrayBufferToString(item.decrypted); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |           }) | 
					
						
							|  |  |  |         ); | 
					
						
							| 
									
										
										
										
											2018-08-06 12:11:02 -07:00
										 |  |  |         await saveUnprocesseds(array); | 
					
						
							|  |  |  |       }, | 
					
						
							| 
									
										
										
										
											2018-07-31 19:51:17 -07:00
										 |  |  |       remove: removeUnprocessed, | 
					
						
							| 
									
										
										
										
											2018-07-26 18:13:56 -07:00
										 |  |  |       storeName: 'unprocessed', | 
					
						
							|  |  |  |       handleDOMException, | 
					
						
							|  |  |  |       lastIndex, | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     ({ complete, lastIndex } = status); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // eslint-disable-next-line no-await-in-loop
 | 
					
						
							|  |  |  |     await setUnprocessedExportLastIndex(db, lastIndex); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   window.log.info('migrateToSQL: migrate of unprocessed complete'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   await clearStores(['messages', 'unprocessed']); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   window.log.info('migrateToSQL: complete'); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | async function migrateStoreToSQLite({ | 
					
						
							|  |  |  |   db, | 
					
						
							|  |  |  |   save, | 
					
						
							| 
									
										
										
										
											2018-07-31 19:51:17 -07:00
										 |  |  |   remove, | 
					
						
							| 
									
										
										
										
											2018-07-26 18:13:56 -07:00
										 |  |  |   storeName, | 
					
						
							|  |  |  |   handleDOMException, | 
					
						
							|  |  |  |   lastIndex = null, | 
					
						
							| 
									
										
										
										
											2018-08-01 18:34:50 -07:00
										 |  |  |   batchSize = 50, | 
					
						
							| 
									
										
										
										
											2018-07-26 18:13:56 -07:00
										 |  |  | }) { | 
					
						
							|  |  |  |   if (!db) { | 
					
						
							|  |  |  |     throw new Error('Need db for IndexedDB connection!'); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   if (!isFunction(save)) { | 
					
						
							|  |  |  |     throw new Error('Need save function!'); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2018-07-31 19:51:17 -07:00
										 |  |  |   if (!isFunction(remove)) { | 
					
						
							|  |  |  |     throw new Error('Need remove function!'); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2018-07-26 18:13:56 -07:00
										 |  |  |   if (!isString(storeName)) { | 
					
						
							|  |  |  |     throw new Error('Need storeName!'); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   if (!isFunction(handleDOMException)) { | 
					
						
							|  |  |  |     throw new Error('Need handleDOMException for error handling!'); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (!includes(db.objectStoreNames, storeName)) { | 
					
						
							|  |  |  |     return { | 
					
						
							|  |  |  |       complete: true, | 
					
						
							|  |  |  |       count: 0, | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const queryPromise = new Promise((resolve, reject) => { | 
					
						
							|  |  |  |     const items = []; | 
					
						
							|  |  |  |     const transaction = db.transaction(storeName, 'readonly'); | 
					
						
							|  |  |  |     transaction.onerror = () => { | 
					
						
							|  |  |  |       handleDOMException( | 
					
						
							|  |  |  |         'migrateToSQLite transaction error', | 
					
						
							|  |  |  |         transaction.error, | 
					
						
							|  |  |  |         reject | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |     transaction.oncomplete = () => {}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const store = transaction.objectStore(storeName); | 
					
						
							|  |  |  |     const excludeLowerBound = true; | 
					
						
							|  |  |  |     const range = lastIndex | 
					
						
							|  |  |  |       ? IDBKeyRange.lowerBound(lastIndex, excludeLowerBound) | 
					
						
							|  |  |  |       : undefined; | 
					
						
							|  |  |  |     const request = store.openCursor(range); | 
					
						
							|  |  |  |     request.onerror = () => { | 
					
						
							|  |  |  |       handleDOMException( | 
					
						
							|  |  |  |         'migrateToSQLite: request error', | 
					
						
							|  |  |  |         request.error, | 
					
						
							|  |  |  |         reject | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |     request.onsuccess = event => { | 
					
						
							|  |  |  |       const cursor = event.target.result; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       if (!cursor || !cursor.value) { | 
					
						
							|  |  |  |         return resolve({ | 
					
						
							|  |  |  |           complete: true, | 
					
						
							|  |  |  |           items, | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       const item = cursor.value; | 
					
						
							|  |  |  |       items.push(item); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       if (items.length >= batchSize) { | 
					
						
							|  |  |  |         return resolve({ | 
					
						
							|  |  |  |           complete: false, | 
					
						
							|  |  |  |           items, | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       return cursor.continue(); | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const { items, complete } = await queryPromise; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (items.length) { | 
					
						
							| 
									
										
										
										
											2018-07-31 19:51:17 -07:00
										 |  |  |     // Because of the force save and some failed imports, we're going to delete before
 | 
					
						
							|  |  |  |     //   we attempt to insert.
 | 
					
						
							|  |  |  |     const ids = items.map(item => item.id); | 
					
						
							|  |  |  |     await remove(ids); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-26 18:13:56 -07:00
										 |  |  |     // We need to pass forceSave parameter, because these items already have an
 | 
					
						
							|  |  |  |     //   id key. Normally, this call would be interpreted as an update request.
 | 
					
						
							|  |  |  |     await save(items, { forceSave: true }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const lastItem = last(items); | 
					
						
							|  |  |  |   const id = lastItem ? lastItem.id : null; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return { | 
					
						
							|  |  |  |     complete, | 
					
						
							|  |  |  |     count: items.length, | 
					
						
							|  |  |  |     lastIndex: id, | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | } |