|  |  | // Module to upgrade the schema of messages, e.g. migrate attachments to disk.
 | 
						
						
						
							|  |  | // `dangerouslyProcessAllWithoutIndex` purposely doesn’t rely on our Backbone
 | 
						
						
						
							|  |  | // IndexedDB adapter to prevent automatic migrations. Rather, it uses direct
 | 
						
						
						
							|  |  | // IndexedDB access. This includes avoiding usage of `storage` module which uses
 | 
						
						
						
							|  |  | // Backbone under the hood.
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | /* global IDBKeyRange, window */
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | const { isFunction, isNumber, isObject, isString, last } = require('lodash');
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | const database = require('./database');
 | 
						
						
						
							|  |  | const Message = require('./types/message');
 | 
						
						
						
							|  |  | const settings = require('./settings');
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | const MESSAGES_STORE_NAME = 'messages';
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | exports.processNext = async ({
 | 
						
						
						
							|  |  |   BackboneMessage,
 | 
						
						
						
							|  |  |   BackboneMessageCollection,
 | 
						
						
						
							|  |  |   numMessagesPerBatch,
 | 
						
						
						
							|  |  |   upgradeMessageSchema,
 | 
						
						
						
							|  |  |   getMessagesNeedingUpgrade,
 | 
						
						
						
							|  |  |   saveMessage,
 | 
						
						
						
							|  |  |   maxVersion = Message.CURRENT_SCHEMA_VERSION,
 | 
						
						
						
							|  |  | } = {}) => {
 | 
						
						
						
							|  |  |   if (!isFunction(BackboneMessage)) {
 | 
						
						
						
							|  |  |     throw new TypeError(
 | 
						
						
						
							|  |  |       "'BackboneMessage' (Whisper.Message) constructor is required"
 | 
						
						
						
							|  |  |     );
 | 
						
						
						
							|  |  |   }
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |   if (!isFunction(BackboneMessageCollection)) {
 | 
						
						
						
							|  |  |     throw new TypeError(
 | 
						
						
						
							|  |  |       "'BackboneMessageCollection' (Whisper.MessageCollection)" +
 | 
						
						
						
							|  |  |         ' constructor is required'
 | 
						
						
						
							|  |  |     );
 | 
						
						
						
							|  |  |   }
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |   if (!isNumber(numMessagesPerBatch)) {
 | 
						
						
						
							|  |  |     throw new TypeError("'numMessagesPerBatch' is required");
 | 
						
						
						
							|  |  |   }
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |   if (!isFunction(upgradeMessageSchema)) {
 | 
						
						
						
							|  |  |     throw new TypeError("'upgradeMessageSchema' is required");
 | 
						
						
						
							|  |  |   }
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |   const startTime = Date.now();
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |   const fetchStartTime = Date.now();
 | 
						
						
						
							|  |  |   let messagesRequiringSchemaUpgrade;
 | 
						
						
						
							|  |  |   try {
 | 
						
						
						
							|  |  |     messagesRequiringSchemaUpgrade = await getMessagesNeedingUpgrade(
 | 
						
						
						
							|  |  |       numMessagesPerBatch,
 | 
						
						
						
							|  |  |       {
 | 
						
						
						
							|  |  |         maxVersion,
 | 
						
						
						
							|  |  |         MessageCollection: BackboneMessageCollection,
 | 
						
						
						
							|  |  |       }
 | 
						
						
						
							|  |  |     );
 | 
						
						
						
							|  |  |   } catch (error) {
 | 
						
						
						
							|  |  |     window.log.error(
 | 
						
						
						
							|  |  |       'processNext error:',
 | 
						
						
						
							|  |  |       error && error.stack ? error.stack : error
 | 
						
						
						
							|  |  |     );
 | 
						
						
						
							|  |  |     return {
 | 
						
						
						
							|  |  |       done: true,
 | 
						
						
						
							|  |  |       numProcessed: 0,
 | 
						
						
						
							|  |  |     };
 | 
						
						
						
							|  |  |   }
 | 
						
						
						
							|  |  |   const fetchDuration = Date.now() - fetchStartTime;
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |   const upgradeStartTime = Date.now();
 | 
						
						
						
							|  |  |   const upgradedMessages = await Promise.all(
 | 
						
						
						
							|  |  |     messagesRequiringSchemaUpgrade.map(message =>
 | 
						
						
						
							|  |  |       upgradeMessageSchema(message, { maxVersion })
 | 
						
						
						
							|  |  |     )
 | 
						
						
						
							|  |  |   );
 | 
						
						
						
							|  |  |   const upgradeDuration = Date.now() - upgradeStartTime;
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |   const saveStartTime = Date.now();
 | 
						
						
						
							|  |  |   await Promise.all(
 | 
						
						
						
							|  |  |     upgradedMessages.map(message =>
 | 
						
						
						
							|  |  |       saveMessage(message, { Message: BackboneMessage })
 | 
						
						
						
							|  |  |     )
 | 
						
						
						
							|  |  |   );
 | 
						
						
						
							|  |  |   const saveDuration = Date.now() - saveStartTime;
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |   const totalDuration = Date.now() - startTime;
 | 
						
						
						
							|  |  |   const numProcessed = messagesRequiringSchemaUpgrade.length;
 | 
						
						
						
							|  |  |   const done = numProcessed < numMessagesPerBatch;
 | 
						
						
						
							|  |  |   return {
 | 
						
						
						
							|  |  |     done,
 | 
						
						
						
							|  |  |     numProcessed,
 | 
						
						
						
							|  |  |     fetchDuration,
 | 
						
						
						
							|  |  |     upgradeDuration,
 | 
						
						
						
							|  |  |     saveDuration,
 | 
						
						
						
							|  |  |     totalDuration,
 | 
						
						
						
							|  |  |   };
 | 
						
						
						
							|  |  | };
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | exports.dangerouslyProcessAllWithoutIndex = async ({
 | 
						
						
						
							|  |  |   databaseName,
 | 
						
						
						
							|  |  |   minDatabaseVersion,
 | 
						
						
						
							|  |  |   numMessagesPerBatch,
 | 
						
						
						
							|  |  |   upgradeMessageSchema,
 | 
						
						
						
							|  |  |   logger,
 | 
						
						
						
							|  |  |   maxVersion = Message.CURRENT_SCHEMA_VERSION,
 | 
						
						
						
							|  |  |   saveMessage,
 | 
						
						
						
							|  |  |   BackboneMessage,
 | 
						
						
						
							|  |  | } = {}) => {
 | 
						
						
						
							|  |  |   if (!isString(databaseName)) {
 | 
						
						
						
							|  |  |     throw new TypeError("'databaseName' must be a string");
 | 
						
						
						
							|  |  |   }
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |   if (!isNumber(minDatabaseVersion)) {
 | 
						
						
						
							|  |  |     throw new TypeError("'minDatabaseVersion' must be a number");
 | 
						
						
						
							|  |  |   }
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |   if (!isNumber(numMessagesPerBatch)) {
 | 
						
						
						
							|  |  |     throw new TypeError("'numMessagesPerBatch' must be a number");
 | 
						
						
						
							|  |  |   }
 | 
						
						
						
							|  |  |   if (!isFunction(upgradeMessageSchema)) {
 | 
						
						
						
							|  |  |     throw new TypeError("'upgradeMessageSchema' is required");
 | 
						
						
						
							|  |  |   }
 | 
						
						
						
							|  |  |   if (!isFunction(BackboneMessage)) {
 | 
						
						
						
							|  |  |     throw new TypeError("'upgradeMessageSchema' is required");
 | 
						
						
						
							|  |  |   }
 | 
						
						
						
							|  |  |   if (!isFunction(saveMessage)) {
 | 
						
						
						
							|  |  |     throw new TypeError("'upgradeMessageSchema' is required");
 | 
						
						
						
							|  |  |   }
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |   const connection = await database.open(databaseName);
 | 
						
						
						
							|  |  |   const databaseVersion = connection.version;
 | 
						
						
						
							|  |  |   const isValidDatabaseVersion = databaseVersion >= minDatabaseVersion;
 | 
						
						
						
							|  |  |   logger.info('Database status', {
 | 
						
						
						
							|  |  |     databaseVersion,
 | 
						
						
						
							|  |  |     isValidDatabaseVersion,
 | 
						
						
						
							|  |  |     minDatabaseVersion,
 | 
						
						
						
							|  |  |   });
 | 
						
						
						
							|  |  |   if (!isValidDatabaseVersion) {
 | 
						
						
						
							|  |  |     throw new Error(
 | 
						
						
						
							|  |  |       `Expected database version (${databaseVersion})` +
 | 
						
						
						
							|  |  |         ` to be at least ${minDatabaseVersion}`
 | 
						
						
						
							|  |  |     );
 | 
						
						
						
							|  |  |   }
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |   // NOTE: Even if we make this async using `then`, requesting `count` on an
 | 
						
						
						
							|  |  |   // IndexedDB store blocks all subsequent transactions, so we might as well
 | 
						
						
						
							|  |  |   // explicitly wait for it here:
 | 
						
						
						
							|  |  |   const numTotalMessages = await exports.getNumMessages({ connection });
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |   const migrationStartTime = Date.now();
 | 
						
						
						
							|  |  |   let numCumulativeMessagesProcessed = 0;
 | 
						
						
						
							|  |  |   // eslint-disable-next-line no-constant-condition
 | 
						
						
						
							|  |  |   while (true) {
 | 
						
						
						
							|  |  |     // eslint-disable-next-line no-await-in-loop
 | 
						
						
						
							|  |  |     const status = await _processBatch({
 | 
						
						
						
							|  |  |       connection,
 | 
						
						
						
							|  |  |       numMessagesPerBatch,
 | 
						
						
						
							|  |  |       upgradeMessageSchema,
 | 
						
						
						
							|  |  |       maxVersion,
 | 
						
						
						
							|  |  |       saveMessage,
 | 
						
						
						
							|  |  |       BackboneMessage,
 | 
						
						
						
							|  |  |     });
 | 
						
						
						
							|  |  |     if (status.done) {
 | 
						
						
						
							|  |  |       break;
 | 
						
						
						
							|  |  |     }
 | 
						
						
						
							|  |  |     numCumulativeMessagesProcessed += status.numMessagesProcessed;
 | 
						
						
						
							|  |  |     logger.info(
 | 
						
						
						
							|  |  |       'Upgrade message schema:',
 | 
						
						
						
							|  |  |       Object.assign({}, status, {
 | 
						
						
						
							|  |  |         numTotalMessages,
 | 
						
						
						
							|  |  |         numCumulativeMessagesProcessed,
 | 
						
						
						
							|  |  |       })
 | 
						
						
						
							|  |  |     );
 | 
						
						
						
							|  |  |   }
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |   logger.info('Close database connection');
 | 
						
						
						
							|  |  |   connection.close();
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |   const totalDuration = Date.now() - migrationStartTime;
 | 
						
						
						
							|  |  |   logger.info('Attachment migration complete:', {
 | 
						
						
						
							|  |  |     totalDuration,
 | 
						
						
						
							|  |  |     totalMessagesProcessed: numCumulativeMessagesProcessed,
 | 
						
						
						
							|  |  |   });
 | 
						
						
						
							|  |  | };
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | exports.processNextBatchWithoutIndex = async ({
 | 
						
						
						
							|  |  |   databaseName,
 | 
						
						
						
							|  |  |   minDatabaseVersion,
 | 
						
						
						
							|  |  |   numMessagesPerBatch,
 | 
						
						
						
							|  |  |   upgradeMessageSchema,
 | 
						
						
						
							|  |  |   maxVersion,
 | 
						
						
						
							|  |  |   BackboneMessage,
 | 
						
						
						
							|  |  |   saveMessage,
 | 
						
						
						
							|  |  | } = {}) => {
 | 
						
						
						
							|  |  |   if (!isFunction(upgradeMessageSchema)) {
 | 
						
						
						
							|  |  |     throw new TypeError("'upgradeMessageSchema' is required");
 | 
						
						
						
							|  |  |   }
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |   const connection = await _getConnection({ databaseName, minDatabaseVersion });
 | 
						
						
						
							|  |  |   const batch = await _processBatch({
 | 
						
						
						
							|  |  |     connection,
 | 
						
						
						
							|  |  |     numMessagesPerBatch,
 | 
						
						
						
							|  |  |     upgradeMessageSchema,
 | 
						
						
						
							|  |  |     maxVersion,
 | 
						
						
						
							|  |  |     BackboneMessage,
 | 
						
						
						
							|  |  |     saveMessage,
 | 
						
						
						
							|  |  |   });
 | 
						
						
						
							|  |  |   return batch;
 | 
						
						
						
							|  |  | };
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | // Private API
 | 
						
						
						
							|  |  | const _getConnection = async ({ databaseName, minDatabaseVersion }) => {
 | 
						
						
						
							|  |  |   if (!isString(databaseName)) {
 | 
						
						
						
							|  |  |     throw new TypeError("'databaseName' must be a string");
 | 
						
						
						
							|  |  |   }
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |   if (!isNumber(minDatabaseVersion)) {
 | 
						
						
						
							|  |  |     throw new TypeError("'minDatabaseVersion' must be a number");
 | 
						
						
						
							|  |  |   }
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |   const connection = await database.open(databaseName);
 | 
						
						
						
							|  |  |   const databaseVersion = connection.version;
 | 
						
						
						
							|  |  |   const isValidDatabaseVersion = databaseVersion >= minDatabaseVersion;
 | 
						
						
						
							|  |  |   if (!isValidDatabaseVersion) {
 | 
						
						
						
							|  |  |     throw new Error(
 | 
						
						
						
							|  |  |       `Expected database version (${databaseVersion})` +
 | 
						
						
						
							|  |  |         ` to be at least ${minDatabaseVersion}`
 | 
						
						
						
							|  |  |     );
 | 
						
						
						
							|  |  |   }
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |   return connection;
 | 
						
						
						
							|  |  | };
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | const _processBatch = async ({
 | 
						
						
						
							|  |  |   connection,
 | 
						
						
						
							|  |  |   numMessagesPerBatch,
 | 
						
						
						
							|  |  |   upgradeMessageSchema,
 | 
						
						
						
							|  |  |   maxVersion,
 | 
						
						
						
							|  |  |   BackboneMessage,
 | 
						
						
						
							|  |  |   saveMessage,
 | 
						
						
						
							|  |  | } = {}) => {
 | 
						
						
						
							|  |  |   if (!isObject(connection)) {
 | 
						
						
						
							|  |  |     throw new TypeError('_processBatch: connection must be a string');
 | 
						
						
						
							|  |  |   }
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |   if (!isFunction(upgradeMessageSchema)) {
 | 
						
						
						
							|  |  |     throw new TypeError('_processBatch: upgradeMessageSchema is required');
 | 
						
						
						
							|  |  |   }
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |   if (!isNumber(numMessagesPerBatch)) {
 | 
						
						
						
							|  |  |     throw new TypeError('_processBatch: numMessagesPerBatch is required');
 | 
						
						
						
							|  |  |   }
 | 
						
						
						
							|  |  |   if (!isNumber(maxVersion)) {
 | 
						
						
						
							|  |  |     throw new TypeError('_processBatch: maxVersion is required');
 | 
						
						
						
							|  |  |   }
 | 
						
						
						
							|  |  |   if (!isFunction(BackboneMessage)) {
 | 
						
						
						
							|  |  |     throw new TypeError('_processBatch: BackboneMessage is required');
 | 
						
						
						
							|  |  |   }
 | 
						
						
						
							|  |  |   if (!isFunction(saveMessage)) {
 | 
						
						
						
							|  |  |     throw new TypeError('_processBatch: saveMessage is required');
 | 
						
						
						
							|  |  |   }
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |   const isAttachmentMigrationComplete = await settings.isAttachmentMigrationComplete(
 | 
						
						
						
							|  |  |     connection
 | 
						
						
						
							|  |  |   );
 | 
						
						
						
							|  |  |   if (isAttachmentMigrationComplete) {
 | 
						
						
						
							|  |  |     return {
 | 
						
						
						
							|  |  |       done: true,
 | 
						
						
						
							|  |  |     };
 | 
						
						
						
							|  |  |   }
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |   const lastProcessedIndex = await settings.getAttachmentMigrationLastProcessedIndex(
 | 
						
						
						
							|  |  |     connection
 | 
						
						
						
							|  |  |   );
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |   const fetchUnprocessedMessagesStartTime = Date.now();
 | 
						
						
						
							|  |  |   let unprocessedMessages;
 | 
						
						
						
							|  |  |   try {
 | 
						
						
						
							|  |  |     unprocessedMessages = await _dangerouslyFetchMessagesRequiringSchemaUpgradeWithoutIndex(
 | 
						
						
						
							|  |  |       {
 | 
						
						
						
							|  |  |         connection,
 | 
						
						
						
							|  |  |         count: numMessagesPerBatch,
 | 
						
						
						
							|  |  |         lastIndex: lastProcessedIndex,
 | 
						
						
						
							|  |  |       }
 | 
						
						
						
							|  |  |     );
 | 
						
						
						
							|  |  |   } catch (error) {
 | 
						
						
						
							|  |  |     window.log.error(
 | 
						
						
						
							|  |  |       '_processBatch error:',
 | 
						
						
						
							|  |  |       error && error.stack ? error.stack : error
 | 
						
						
						
							|  |  |     );
 | 
						
						
						
							|  |  |     await settings.markAttachmentMigrationComplete(connection);
 | 
						
						
						
							|  |  |     await settings.deleteAttachmentMigrationLastProcessedIndex(connection);
 | 
						
						
						
							|  |  |     return {
 | 
						
						
						
							|  |  |       done: true,
 | 
						
						
						
							|  |  |     };
 | 
						
						
						
							|  |  |   }
 | 
						
						
						
							|  |  |   const fetchDuration = Date.now() - fetchUnprocessedMessagesStartTime;
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |   const upgradeStartTime = Date.now();
 | 
						
						
						
							|  |  |   const upgradedMessages = await Promise.all(
 | 
						
						
						
							|  |  |     unprocessedMessages.map(message =>
 | 
						
						
						
							|  |  |       upgradeMessageSchema(message, { maxVersion })
 | 
						
						
						
							|  |  |     )
 | 
						
						
						
							|  |  |   );
 | 
						
						
						
							|  |  |   const upgradeDuration = Date.now() - upgradeStartTime;
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |   const saveMessagesStartTime = Date.now();
 | 
						
						
						
							|  |  |   const transaction = connection.transaction(MESSAGES_STORE_NAME, 'readwrite');
 | 
						
						
						
							|  |  |   const transactionCompletion = database.completeTransaction(transaction);
 | 
						
						
						
							|  |  |   await Promise.all(
 | 
						
						
						
							|  |  |     upgradedMessages.map(message =>
 | 
						
						
						
							|  |  |       saveMessage(message, { Message: BackboneMessage })
 | 
						
						
						
							|  |  |     )
 | 
						
						
						
							|  |  |   );
 | 
						
						
						
							|  |  |   await transactionCompletion;
 | 
						
						
						
							|  |  |   const saveDuration = Date.now() - saveMessagesStartTime;
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |   const numMessagesProcessed = upgradedMessages.length;
 | 
						
						
						
							|  |  |   const done = numMessagesProcessed < numMessagesPerBatch;
 | 
						
						
						
							|  |  |   const lastMessage = last(upgradedMessages);
 | 
						
						
						
							|  |  |   const newLastProcessedIndex = lastMessage ? lastMessage.id : null;
 | 
						
						
						
							|  |  |   if (!done) {
 | 
						
						
						
							|  |  |     await settings.setAttachmentMigrationLastProcessedIndex(
 | 
						
						
						
							|  |  |       connection,
 | 
						
						
						
							|  |  |       newLastProcessedIndex
 | 
						
						
						
							|  |  |     );
 | 
						
						
						
							|  |  |   } else {
 | 
						
						
						
							|  |  |     await settings.markAttachmentMigrationComplete(connection);
 | 
						
						
						
							|  |  |     await settings.deleteAttachmentMigrationLastProcessedIndex(connection);
 | 
						
						
						
							|  |  |   }
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |   const batchTotalDuration = Date.now() - fetchUnprocessedMessagesStartTime;
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |   return {
 | 
						
						
						
							|  |  |     batchTotalDuration,
 | 
						
						
						
							|  |  |     done,
 | 
						
						
						
							|  |  |     fetchDuration,
 | 
						
						
						
							|  |  |     lastProcessedIndex,
 | 
						
						
						
							|  |  |     newLastProcessedIndex,
 | 
						
						
						
							|  |  |     numMessagesProcessed,
 | 
						
						
						
							|  |  |     saveDuration,
 | 
						
						
						
							|  |  |     targetSchemaVersion: Message.CURRENT_SCHEMA_VERSION,
 | 
						
						
						
							|  |  |     upgradeDuration,
 | 
						
						
						
							|  |  |   };
 | 
						
						
						
							|  |  | };
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | // NOTE: Named ‘dangerous’ because it is not as efficient as using our
 | 
						
						
						
							|  |  | // `messages` `schemaVersion` index:
 | 
						
						
						
							|  |  | const _dangerouslyFetchMessagesRequiringSchemaUpgradeWithoutIndex = ({
 | 
						
						
						
							|  |  |   connection,
 | 
						
						
						
							|  |  |   count,
 | 
						
						
						
							|  |  |   lastIndex,
 | 
						
						
						
							|  |  | } = {}) => {
 | 
						
						
						
							|  |  |   if (!isObject(connection)) {
 | 
						
						
						
							|  |  |     throw new TypeError("'connection' is required");
 | 
						
						
						
							|  |  |   }
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |   if (!isNumber(count)) {
 | 
						
						
						
							|  |  |     throw new TypeError("'count' is required");
 | 
						
						
						
							|  |  |   }
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |   if (lastIndex && !isString(lastIndex)) {
 | 
						
						
						
							|  |  |     throw new TypeError("'lastIndex' must be a string");
 | 
						
						
						
							|  |  |   }
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |   const hasLastIndex = Boolean(lastIndex);
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |   const transaction = connection.transaction(MESSAGES_STORE_NAME, 'readonly');
 | 
						
						
						
							|  |  |   const messagesStore = transaction.objectStore(MESSAGES_STORE_NAME);
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |   const excludeLowerBound = true;
 | 
						
						
						
							|  |  |   const range = hasLastIndex
 | 
						
						
						
							|  |  |     ? IDBKeyRange.lowerBound(lastIndex, excludeLowerBound)
 | 
						
						
						
							|  |  |     : undefined;
 | 
						
						
						
							|  |  |   return new Promise((resolve, reject) => {
 | 
						
						
						
							|  |  |     const items = [];
 | 
						
						
						
							|  |  |     const request = messagesStore.openCursor(range);
 | 
						
						
						
							|  |  |     request.onsuccess = event => {
 | 
						
						
						
							|  |  |       const cursor = event.target.result;
 | 
						
						
						
							|  |  |       const hasMoreData = Boolean(cursor);
 | 
						
						
						
							|  |  |       if (!hasMoreData || items.length === count) {
 | 
						
						
						
							|  |  |         resolve(items);
 | 
						
						
						
							|  |  |         return;
 | 
						
						
						
							|  |  |       }
 | 
						
						
						
							|  |  |       const item = cursor.value;
 | 
						
						
						
							|  |  |       items.push(item);
 | 
						
						
						
							|  |  |       cursor.continue();
 | 
						
						
						
							|  |  |     };
 | 
						
						
						
							|  |  |     request.onerror = event => reject(event.target.error);
 | 
						
						
						
							|  |  |   });
 | 
						
						
						
							|  |  | };
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | exports.getNumMessages = async ({ connection } = {}) => {
 | 
						
						
						
							|  |  |   if (!isObject(connection)) {
 | 
						
						
						
							|  |  |     throw new TypeError("'connection' is required");
 | 
						
						
						
							|  |  |   }
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |   const transaction = connection.transaction(MESSAGES_STORE_NAME, 'readonly');
 | 
						
						
						
							|  |  |   const messagesStore = transaction.objectStore(MESSAGES_STORE_NAME);
 | 
						
						
						
							|  |  |   const numTotalMessages = await database.getCount({ store: messagesStore });
 | 
						
						
						
							|  |  |   await database.completeTransaction(transaction);
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  |   return numTotalMessages;
 | 
						
						
						
							|  |  | };
 |