Move data.js to data.ts
parent
7b81c4213a
commit
8ea9f02cec
@ -1,425 +0,0 @@
|
||||
import { KeyPair } from '../../libtextsecure/libsignal-protocol';
|
||||
import { MessageCollection } from '../../ts/models/message';
|
||||
import { HexKeyPair } from '../../ts/receiver/closedGroups';
|
||||
import { ECKeyPair } from '../../ts/receiver/keypairs';
|
||||
import { PubKey } from '../../ts/session/types';
|
||||
import { ConversationType } from '../../ts/state/ducks/conversations';
|
||||
import { Message } from '../../ts/types/Message';
|
||||
|
||||
export type IdentityKey = {
|
||||
id: string;
|
||||
publicKey: ArrayBuffer;
|
||||
firstUse: boolean;
|
||||
nonblockingApproval: boolean;
|
||||
secretKey?: string; // found in medium groups
|
||||
};
|
||||
|
||||
export type PreKey = {
|
||||
id: number;
|
||||
publicKey: ArrayBuffer;
|
||||
privateKey: ArrayBuffer;
|
||||
recipient: string;
|
||||
};
|
||||
|
||||
export type SignedPreKey = {
|
||||
id: number;
|
||||
publicKey: ArrayBuffer;
|
||||
privateKey: ArrayBuffer;
|
||||
created_at: number;
|
||||
confirmed: boolean;
|
||||
signature: ArrayBuffer;
|
||||
};
|
||||
|
||||
export type ContactPreKey = {
|
||||
id: number;
|
||||
identityKeyString: string;
|
||||
publicKey: ArrayBuffer;
|
||||
keyId: number;
|
||||
};
|
||||
|
||||
export type ContactSignedPreKey = {
|
||||
id: number;
|
||||
identityKeyString: string;
|
||||
publicKey: ArrayBuffer;
|
||||
keyId: number;
|
||||
signature: ArrayBuffer;
|
||||
created_at: number;
|
||||
confirmed: boolean;
|
||||
};
|
||||
|
||||
export type GuardNode = {
|
||||
ed25519PubKey: string;
|
||||
};
|
||||
|
||||
export type SwarmNode = {
|
||||
address: string;
|
||||
ip: string;
|
||||
port: string;
|
||||
pubkey_ed25519: string;
|
||||
pubkey_x25519: string;
|
||||
};
|
||||
|
||||
export type StorageItem = {
|
||||
id: string;
|
||||
value: any;
|
||||
};
|
||||
|
||||
export type SessionDataInfo = {
|
||||
id: string;
|
||||
number: string;
|
||||
deviceId: number;
|
||||
record: string;
|
||||
};
|
||||
|
||||
export type ServerToken = {
|
||||
serverUrl: string;
|
||||
token: string;
|
||||
};
|
||||
|
||||
// Basic
|
||||
export function searchMessages(query: string): Promise<Array<any>>;
|
||||
export function searchConversations(query: string): Promise<Array<any>>;
|
||||
export function shutdown(): Promise<void>;
|
||||
export function close(): Promise<void>;
|
||||
export function removeDB(): Promise<void>;
|
||||
export function removeIndexedDBFiles(): Promise<void>;
|
||||
export function getPasswordHash(): Promise<string | null>;
|
||||
|
||||
// Identity Keys
|
||||
// TODO: identity key has different shape depending on how it is called,
|
||||
// so we need to come up with a way to make TS work with all of them
|
||||
export function createOrUpdateIdentityKey(data: any): Promise<void>;
|
||||
export function getIdentityKeyById(id: string): Promise<IdentityKey | null>;
|
||||
export function bulkAddIdentityKeys(array: Array<IdentityKey>): Promise<void>;
|
||||
export function removeIdentityKeyById(id: string): Promise<void>;
|
||||
export function removeAllIdentityKeys(): Promise<void>;
|
||||
|
||||
// Pre Keys
|
||||
export function createOrUpdatePreKey(data: PreKey): Promise<void>;
|
||||
export function getPreKeyById(id: number): Promise<PreKey | null>;
|
||||
export function getPreKeyByRecipient(recipient: string): Promise<PreKey | null>;
|
||||
export function bulkAddPreKeys(data: Array<PreKey>): Promise<void>;
|
||||
export function removePreKeyById(id: number): Promise<void>;
|
||||
export function getAllPreKeys(): Promise<Array<PreKey>>;
|
||||
|
||||
// Signed Pre Keys
|
||||
export function createOrUpdateSignedPreKey(data: SignedPreKey): Promise<void>;
|
||||
export function getSignedPreKeyById(id: number): Promise<SignedPreKey | null>;
|
||||
export function getAllSignedPreKeys(): Promise<SignedPreKey | null>;
|
||||
export function bulkAddSignedPreKeys(array: Array<SignedPreKey>): Promise<void>;
|
||||
export function removeSignedPreKeyById(id: number): Promise<void>;
|
||||
export function removeAllSignedPreKeys(): Promise<void>;
|
||||
|
||||
// Contact Pre Key
|
||||
export function createOrUpdateContactPreKey(data: ContactPreKey): Promise<void>;
|
||||
export function getContactPreKeyById(id: number): Promise<ContactPreKey | null>;
|
||||
export function getContactPreKeyByIdentityKey(
|
||||
key: string
|
||||
): Promise<ContactPreKey | null>;
|
||||
export function getContactPreKeys(
|
||||
keyId: number,
|
||||
identityKeyString: string
|
||||
): Promise<Array<ContactPreKey>>;
|
||||
export function getAllContactPreKeys(): Promise<Array<ContactPreKey>>;
|
||||
export function bulkAddContactPreKeys(
|
||||
array: Array<ContactPreKey>
|
||||
): Promise<void>;
|
||||
export function removeContactPreKeyByIdentityKey(id: number): Promise<void>;
|
||||
export function removeAllContactPreKeys(): Promise<void>;
|
||||
|
||||
// Contact Signed Pre Key
|
||||
export function createOrUpdateContactSignedPreKey(
|
||||
data: ContactSignedPreKey
|
||||
): Promise<void>;
|
||||
export function getContactSignedPreKeyById(
|
||||
id: number
|
||||
): Promise<ContactSignedPreKey | null>;
|
||||
export function getContactSignedPreKeyByIdentityKey(
|
||||
key: string
|
||||
): Promise<ContactSignedPreKey | null>;
|
||||
export function getContactSignedPreKeys(
|
||||
keyId: number,
|
||||
identityKeyString: string
|
||||
): Promise<Array<ContactSignedPreKey>>;
|
||||
export function bulkAddContactSignedPreKeys(
|
||||
array: Array<ContactSignedPreKey>
|
||||
): Promise<void>;
|
||||
export function removeContactSignedPreKeyByIdentityKey(
|
||||
id: string
|
||||
): Promise<void>;
|
||||
export function removeAllContactSignedPreKeys(): Promise<void>;
|
||||
|
||||
// Guard Nodes
|
||||
export function getGuardNodes(): Promise<Array<GuardNode>>;
|
||||
export function updateGuardNodes(nodes: Array<string>): Promise<void>;
|
||||
|
||||
// Storage Items
|
||||
export function createOrUpdateItem(data: StorageItem): Promise<void>;
|
||||
export function getItemById(id: string): Promise<StorageItem | undefined>;
|
||||
export function getAlItems(): Promise<Array<StorageItem>>;
|
||||
export function bulkAddItems(array: Array<StorageItem>): Promise<void>;
|
||||
export function removeItemById(id: string): Promise<void>;
|
||||
export function removeAllItems(): Promise<void>;
|
||||
|
||||
// Sessions
|
||||
export function createOrUpdateSession(data: SessionDataInfo): Promise<void>;
|
||||
export function getAllSessions(): Promise<Array<SessionDataInfo>>;
|
||||
export function getSessionById(id: string): Promise<SessionDataInfo>;
|
||||
export function getSessionsByNumber(number: string): Promise<SessionDataInfo>;
|
||||
export function bulkAddSessions(array: Array<SessionDataInfo>): Promise<void>;
|
||||
export function removeSessionById(id: string): Promise<void>;
|
||||
export function removeSessionsByNumber(number: string): Promise<void>;
|
||||
export function removeAllSessions(): Promise<void>;
|
||||
|
||||
// Conversations
|
||||
export function getConversationCount(): Promise<number>;
|
||||
export function saveConversation(data: ConversationType): Promise<void>;
|
||||
export function saveConversations(data: Array<ConversationType>): Promise<void>;
|
||||
export function updateConversation(
|
||||
id: string,
|
||||
data: ConversationType,
|
||||
{ Conversation }
|
||||
): Promise<void>;
|
||||
export function removeConversation(id: string, { Conversation }): Promise<void>;
|
||||
|
||||
export function getAllConversations({
|
||||
ConversationCollection,
|
||||
}: {
|
||||
ConversationCollection: any;
|
||||
}): Promise<ConversationCollection>;
|
||||
|
||||
export function getAllConversationIds(): Promise<Array<string>>;
|
||||
export function getPublicConversationsByServer(
|
||||
server: string,
|
||||
{ ConversationCollection }: { ConversationCollection: any }
|
||||
): Promise<ConversationCollection>;
|
||||
export function getPubkeysInPublicConversation(
|
||||
id: string
|
||||
): Promise<Array<string>>;
|
||||
export function savePublicServerToken(data: ServerToken): Promise<void>;
|
||||
export function getPublicServerTokenByServerUrl(
|
||||
serverUrl: string
|
||||
): Promise<string>;
|
||||
export function getAllGroupsInvolvingId(
|
||||
id: string,
|
||||
{ ConversationCollection }: { ConversationCollection: any }
|
||||
): Promise<ConversationCollection>;
|
||||
|
||||
// Returns conversation row
|
||||
// TODO: Make strict return types for search
|
||||
export function searchConversations(query: string): Promise<any>;
|
||||
export function searchMessages(query: string): Promise<any>;
|
||||
export function searchMessagesInConversation(
|
||||
query: string,
|
||||
conversationId: string,
|
||||
{ limit }?: { limit: any }
|
||||
): Promise<any>;
|
||||
export function saveMessage(
|
||||
data: Mesasge,
|
||||
{ forceSave, Message }?: { forceSave?: any; Message?: any }
|
||||
): Promise<string>;
|
||||
export function cleanSeenMessages(): Promise<void>;
|
||||
export function cleanLastHashes(): Promise<void>;
|
||||
export function saveSeenMessageHash(data: {
|
||||
expiresAt: number;
|
||||
hash: string;
|
||||
}): Promise<void>;
|
||||
|
||||
export function getSwarmNodesForPubkey(pubkey: string): Promise<Array<string>>;
|
||||
export function updateSwarmNodesForPubkey(
|
||||
pubkey: string,
|
||||
snodeEdKeys: Array<string>
|
||||
): Promise<void>;
|
||||
// TODO: Strictly type the following
|
||||
export function updateLastHash(data: any): Promise<any>;
|
||||
export function saveSeenMessageHashes(data: any): Promise<any>;
|
||||
export function saveLegacyMessage(data: any): Promise<any>;
|
||||
export function saveMessages(
|
||||
arrayOfMessages: any,
|
||||
{ forceSave }?: any
|
||||
): Promise<any>;
|
||||
export function removeMessage(id: string, { Message }?: any): Promise<any>;
|
||||
export function getUnreadByConversation(
|
||||
conversationId: string,
|
||||
{ MessageCollection }?: any
|
||||
): Promise<any>;
|
||||
export function getUnreadCountByConversation(
|
||||
conversationId: string
|
||||
): Promise<any>;
|
||||
export function removeAllMessagesInConversation(
|
||||
conversationId: string,
|
||||
{ MessageCollection }?: any
|
||||
): Promise<void>;
|
||||
|
||||
export function getMessageBySender(
|
||||
{
|
||||
source,
|
||||
sourceDevice,
|
||||
sent_at,
|
||||
}: { source: any; sourceDevice: any; sent_at: any },
|
||||
{ Message }: { Message: any }
|
||||
): Promise<any>;
|
||||
export function getMessagesBySender(
|
||||
{ source, sourceDevice }: { source: any; sourceDevice: any },
|
||||
{ Message }: { Message: any }
|
||||
): Promise<MessageCollection>;
|
||||
export function getMessageIdsFromServerIds(
|
||||
serverIds: any,
|
||||
conversationId: any
|
||||
): Promise<any>;
|
||||
export function getMessageById(
|
||||
id: string,
|
||||
{ Message }: { Message: any }
|
||||
): Promise<any>;
|
||||
export function getAllMessages({
|
||||
MessageCollection,
|
||||
}: {
|
||||
MessageCollection: any;
|
||||
}): Promise<any>;
|
||||
export function getAllUnsentMessages({
|
||||
MessageCollection,
|
||||
}: {
|
||||
MessageCollection: any;
|
||||
}): Promise<any>;
|
||||
export function getAllMessageIds(): Promise<any>;
|
||||
export function getMessagesBySentAt(
|
||||
sentAt: any,
|
||||
{ MessageCollection }: { MessageCollection: any }
|
||||
): Promise<any>;
|
||||
export function getExpiredMessages({
|
||||
MessageCollection,
|
||||
}: {
|
||||
MessageCollection: any;
|
||||
}): Promise<any>;
|
||||
export function getOutgoingWithoutExpiresAt({
|
||||
MessageCollection,
|
||||
}: any): Promise<any>;
|
||||
export function getNextExpiringMessage({
|
||||
MessageCollection,
|
||||
}: {
|
||||
MessageCollection: any;
|
||||
}): Promise<any>;
|
||||
export function getNextExpiringMessage({
|
||||
MessageCollection,
|
||||
}: {
|
||||
MessageCollection: any;
|
||||
}): Promise<any>;
|
||||
export function getMessagesByConversation(
|
||||
conversationId: any,
|
||||
{
|
||||
limit,
|
||||
receivedAt,
|
||||
MessageCollection,
|
||||
type,
|
||||
}: {
|
||||
limit?: number;
|
||||
receivedAt?: number;
|
||||
MessageCollection: any;
|
||||
type?: string;
|
||||
}
|
||||
): Promise<any>;
|
||||
|
||||
export function getSeenMessagesByHashList(hashes: any): Promise<any>;
|
||||
export function getLastHashBySnode(convoId: any, snode: any): Promise<any>;
|
||||
|
||||
// Unprocessed
|
||||
export function getUnprocessedCount(): Promise<any>;
|
||||
export function getAllUnprocessed(): Promise<any>;
|
||||
export function getUnprocessedById(id: any): Promise<any>;
|
||||
export function saveUnprocessed(
|
||||
data: any,
|
||||
{
|
||||
forceSave,
|
||||
}?: {
|
||||
forceSave: any;
|
||||
}
|
||||
): Promise<any>;
|
||||
export function saveUnprocesseds(
|
||||
arrayOfUnprocessed: any,
|
||||
{
|
||||
forceSave,
|
||||
}?: {
|
||||
forceSave: any;
|
||||
}
|
||||
): Promise<void>;
|
||||
export function updateUnprocessedAttempts(
|
||||
id: any,
|
||||
attempts: any
|
||||
): Promise<void>;
|
||||
export function updateUnprocessedWithData(id: any, data: any): Promise<void>;
|
||||
export function removeUnprocessed(id: any): Promise<void>;
|
||||
export function removeAllUnprocessed(): Promise<void>;
|
||||
|
||||
// Attachment Downloads
|
||||
export function getNextAttachmentDownloadJobs(limit: any): Promise<any>;
|
||||
export function saveAttachmentDownloadJob(job: any): Promise<void>;
|
||||
export function setAttachmentDownloadJobPending(
|
||||
id: any,
|
||||
pending: any
|
||||
): Promise<void>;
|
||||
export function resetAttachmentDownloadPending(): Promise<void>;
|
||||
export function removeAttachmentDownloadJob(id: any): Promise<void>;
|
||||
export function removeAllAttachmentDownloadJobs(): Promise<void>;
|
||||
|
||||
// Other
|
||||
export function removeAll(): Promise<void>;
|
||||
export function removeAllConfiguration(): Promise<void>;
|
||||
export function removeAllConversations(): Promise<void>;
|
||||
export function removeAllPrivateConversations(): Promise<void>;
|
||||
export function removeOtherData(): Promise<void>;
|
||||
export function cleanupOrphanedAttachments(): Promise<void>;
|
||||
|
||||
// Getters
|
||||
export function getMessagesNeedingUpgrade(
|
||||
limit: any,
|
||||
{
|
||||
maxVersion,
|
||||
}: {
|
||||
maxVersion?: number;
|
||||
}
|
||||
): Promise<any>;
|
||||
export function getLegacyMessagesNeedingUpgrade(
|
||||
limit: any,
|
||||
{
|
||||
maxVersion,
|
||||
}: {
|
||||
maxVersion?: number;
|
||||
}
|
||||
): Promise<any>;
|
||||
export function getMessagesWithVisualMediaAttachments(
|
||||
conversationId: any,
|
||||
{
|
||||
limit,
|
||||
}: {
|
||||
limit: any;
|
||||
}
|
||||
): Promise<any>;
|
||||
export function getMessagesWithFileAttachments(
|
||||
conversationId: any,
|
||||
{
|
||||
limit,
|
||||
}: {
|
||||
limit: any;
|
||||
}
|
||||
): Promise<any>;
|
||||
|
||||
// Sender Keys
|
||||
export function removeAllClosedGroupRatchets(groupId: string): Promise<void>;
|
||||
|
||||
export function getAllEncryptionKeyPairsForGroup(
|
||||
groupPublicKey: string | PubKey
|
||||
): Promise<Array<HexKeyPair> | undefined>;
|
||||
export function isKeyPairAlreadySaved(
|
||||
groupPublicKey: string,
|
||||
keypair: HexKeyPair
|
||||
): Promise<boolean>;
|
||||
export function getLatestClosedGroupEncryptionKeyPair(
|
||||
groupPublicKey: string
|
||||
): Promise<HexKeyPair | undefined>;
|
||||
export function addClosedGroupEncryptionKeyPair(
|
||||
groupPublicKey: string,
|
||||
keypair: HexKeyPair
|
||||
): Promise<void>;
|
||||
export function removeAllClosedGroupEncryptionKeyPairs(
|
||||
groupPublicKey: string
|
||||
): Promise<void>;
|
File diff suppressed because it is too large
Load Diff
@ -1,60 +0,0 @@
|
||||
/* eslint-env browser */
|
||||
|
||||
const EventEmitter = require('events');
|
||||
|
||||
const POLL_INTERVAL_MS = 5 * 1000;
|
||||
const IDLE_THRESHOLD_MS = 20;
|
||||
|
||||
class IdleDetector extends EventEmitter {
|
||||
constructor() {
|
||||
super();
|
||||
this.handle = null;
|
||||
this.timeoutId = null;
|
||||
}
|
||||
|
||||
start() {
|
||||
window.log.info('Start idle detector');
|
||||
this._scheduleNextCallback();
|
||||
}
|
||||
|
||||
stop() {
|
||||
if (!this.handle) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.log.info('Stop idle detector');
|
||||
this._clearScheduledCallbacks();
|
||||
}
|
||||
|
||||
_clearScheduledCallbacks() {
|
||||
if (this.handle) {
|
||||
cancelIdleCallback(this.handle);
|
||||
this.handle = null;
|
||||
}
|
||||
|
||||
if (this.timeoutId) {
|
||||
clearTimeout(this.timeoutId);
|
||||
this.timeoutId = null;
|
||||
}
|
||||
}
|
||||
|
||||
_scheduleNextCallback() {
|
||||
this._clearScheduledCallbacks();
|
||||
this.handle = window.requestIdleCallback(deadline => {
|
||||
const { didTimeout } = deadline;
|
||||
const timeRemaining = deadline.timeRemaining();
|
||||
const isIdle = timeRemaining >= IDLE_THRESHOLD_MS;
|
||||
this.timeoutId = setTimeout(
|
||||
() => this._scheduleNextCallback(),
|
||||
POLL_INTERVAL_MS
|
||||
);
|
||||
if (isIdle || didTimeout) {
|
||||
this.emit('idle', { timestamp: Date.now(), didTimeout, timeRemaining });
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
IdleDetector,
|
||||
};
|
@ -1,168 +0,0 @@
|
||||
/* global window, Whisper, textsecure */
|
||||
|
||||
const { isFunction } = require('lodash');
|
||||
|
||||
const MessageDataMigrator = require('./messages_data_migrator');
|
||||
const {
|
||||
run,
|
||||
getLatestVersion,
|
||||
getDatabase,
|
||||
} = require('./migrations/migrations');
|
||||
|
||||
const MESSAGE_MINIMUM_VERSION = 7;
|
||||
|
||||
module.exports = {
|
||||
doesDatabaseExist,
|
||||
mandatoryMessageUpgrade,
|
||||
MESSAGE_MINIMUM_VERSION,
|
||||
migrateAllToSQLCipher,
|
||||
removeDatabase,
|
||||
runMigrations,
|
||||
};
|
||||
|
||||
async function runMigrations() {
|
||||
window.log.info('Run migrations on database with attachment data');
|
||||
await run({
|
||||
Backbone: window.Backbone,
|
||||
logger: window.log,
|
||||
});
|
||||
|
||||
Whisper.Database.migrations[0].version = getLatestVersion();
|
||||
}
|
||||
|
||||
async function mandatoryMessageUpgrade({ upgradeMessageSchema } = {}) {
|
||||
if (!isFunction(upgradeMessageSchema)) {
|
||||
throw new Error(
|
||||
'mandatoryMessageUpgrade: upgradeMessageSchema must be a function!'
|
||||
);
|
||||
}
|
||||
|
||||
const NUM_MESSAGES_PER_BATCH = 10;
|
||||
window.log.info(
|
||||
'upgradeMessages: Mandatory message schema upgrade started.',
|
||||
`Target version: ${MESSAGE_MINIMUM_VERSION}`
|
||||
);
|
||||
|
||||
let isMigrationWithoutIndexComplete = false;
|
||||
while (!isMigrationWithoutIndexComplete) {
|
||||
const database = getDatabase();
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const batchWithoutIndex = await MessageDataMigrator.processNextBatchWithoutIndex(
|
||||
{
|
||||
databaseName: database.name,
|
||||
minDatabaseVersion: database.version,
|
||||
numMessagesPerBatch: NUM_MESSAGES_PER_BATCH,
|
||||
upgradeMessageSchema,
|
||||
maxVersion: MESSAGE_MINIMUM_VERSION,
|
||||
BackboneMessage: window.models.Message.MessageModel,
|
||||
saveMessage: window.Signal.Data.saveLegacyMessage,
|
||||
}
|
||||
);
|
||||
window.log.info(
|
||||
'upgradeMessages: upgrade without index',
|
||||
batchWithoutIndex
|
||||
);
|
||||
isMigrationWithoutIndexComplete = batchWithoutIndex.done;
|
||||
}
|
||||
window.log.info('upgradeMessages: upgrade without index complete!');
|
||||
|
||||
let isMigrationWithIndexComplete = false;
|
||||
while (!isMigrationWithIndexComplete) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const batchWithIndex = await MessageDataMigrator.processNext({
|
||||
BackboneMessage: window.models.Message.MessageModel,
|
||||
BackboneMessageCollection: window.models.Message.MessageCollection,
|
||||
numMessagesPerBatch: NUM_MESSAGES_PER_BATCH,
|
||||
upgradeMessageSchema,
|
||||
getMessagesNeedingUpgrade:
|
||||
window.Signal.Data.getLegacyMessagesNeedingUpgrade,
|
||||
saveMessage: window.Signal.Data.saveLegacyMessage,
|
||||
maxVersion: MESSAGE_MINIMUM_VERSION,
|
||||
});
|
||||
window.log.info('upgradeMessages: upgrade with index', batchWithIndex);
|
||||
isMigrationWithIndexComplete = batchWithIndex.done;
|
||||
}
|
||||
window.log.info('upgradeMessages: upgrade with index complete!');
|
||||
|
||||
window.log.info('upgradeMessages: Message schema upgrade complete');
|
||||
}
|
||||
|
||||
async function migrateAllToSQLCipher({ writeNewAttachmentData, Views } = {}) {
|
||||
if (!isFunction(writeNewAttachmentData)) {
|
||||
throw new Error(
|
||||
'migrateAllToSQLCipher: writeNewAttachmentData must be a function'
|
||||
);
|
||||
}
|
||||
if (!Views) {
|
||||
throw new Error('migrateAllToSQLCipher: Views must be provided!');
|
||||
}
|
||||
|
||||
let totalMessages;
|
||||
const db = await Whisper.Database.open();
|
||||
|
||||
function showMigrationStatus(current) {
|
||||
const status = `${current}/${totalMessages}`;
|
||||
Views.Initialization.setMessage(
|
||||
window.i18n('migratingToSQLCipher', [status])
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
totalMessages = await MessageDataMigrator.getNumMessages({
|
||||
connection: db,
|
||||
});
|
||||
} catch (error) {
|
||||
window.log.error(
|
||||
'background.getNumMessages error:',
|
||||
error && error.stack ? error.stack : error
|
||||
);
|
||||
totalMessages = 0;
|
||||
}
|
||||
|
||||
if (totalMessages) {
|
||||
window.log.info(`About to migrate ${totalMessages} messages`);
|
||||
showMigrationStatus(0);
|
||||
} else {
|
||||
window.log.info('About to migrate non-messages');
|
||||
}
|
||||
|
||||
await window.Signal.migrateToSQL({
|
||||
db,
|
||||
clearStores: Whisper.Database.clearStores,
|
||||
handleDOMException: Whisper.Database.handleDOMException,
|
||||
arrayBufferToString: textsecure.MessageReceiver.arrayBufferToStringBase64,
|
||||
countCallback: count => {
|
||||
window.log.info(`Migration: ${count} messages complete`);
|
||||
showMigrationStatus(count);
|
||||
},
|
||||
writeNewAttachmentData,
|
||||
});
|
||||
|
||||
db.close();
|
||||
}
|
||||
|
||||
async function doesDatabaseExist() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const { id } = Whisper.Database;
|
||||
const req = window.indexedDB.open(id);
|
||||
|
||||
let existed = true;
|
||||
|
||||
req.onerror = reject;
|
||||
req.onsuccess = () => {
|
||||
req.result.close();
|
||||
resolve(existed);
|
||||
};
|
||||
req.onupgradeneeded = () => {
|
||||
if (req.result.version === 1) {
|
||||
existed = false;
|
||||
window.indexedDB.deleteDatabase(id);
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function removeDatabase() {
|
||||
window.log.info(`Deleting IndexedDB database '${Whisper.Database.id}'`);
|
||||
window.indexedDB.deleteDatabase(Whisper.Database.id);
|
||||
}
|
@ -1,409 +0,0 @@
|
||||
/* global window, IDBKeyRange */
|
||||
|
||||
const { includes, isFunction, isString, last, map } = require('lodash');
|
||||
const {
|
||||
bulkAddSessions,
|
||||
bulkAddIdentityKeys,
|
||||
bulkAddPreKeys,
|
||||
bulkAddSignedPreKeys,
|
||||
bulkAddItems,
|
||||
|
||||
removeSessionById,
|
||||
removeIdentityKeyById,
|
||||
removePreKeyById,
|
||||
removeSignedPreKeyById,
|
||||
removeItemById,
|
||||
|
||||
saveMessages,
|
||||
_removeMessages,
|
||||
|
||||
saveUnprocesseds,
|
||||
removeUnprocessed,
|
||||
|
||||
saveConversations,
|
||||
_removeConversations,
|
||||
} = require('./data');
|
||||
const {
|
||||
getMessageExportLastIndex,
|
||||
setMessageExportLastIndex,
|
||||
getMessageExportCount,
|
||||
setMessageExportCount,
|
||||
getUnprocessedExportLastIndex,
|
||||
setUnprocessedExportLastIndex,
|
||||
} = require('./settings');
|
||||
const { migrateConversation } = require('./types/conversation');
|
||||
|
||||
module.exports = {
|
||||
migrateToSQL,
|
||||
};
|
||||
|
||||
async function migrateToSQL({
|
||||
db,
|
||||
clearStores,
|
||||
handleDOMException,
|
||||
countCallback,
|
||||
arrayBufferToString,
|
||||
writeNewAttachmentData,
|
||||
}) {
|
||||
if (!db) {
|
||||
throw new Error('Need db for IndexedDB connection!');
|
||||
}
|
||||
if (!isFunction(clearStores)) {
|
||||
throw new Error('Need clearStores function!');
|
||||
}
|
||||
if (!isFunction(arrayBufferToString)) {
|
||||
throw new Error('Need arrayBufferToString function!');
|
||||
}
|
||||
if (!isFunction(handleDOMException)) {
|
||||
throw new Error('Need handleDOMException function!');
|
||||
}
|
||||
|
||||
window.log.info('migrateToSQL: start');
|
||||
|
||||
let [lastIndex, doneSoFar] = await Promise.all([
|
||||
getMessageExportLastIndex(db),
|
||||
getMessageExportCount(db),
|
||||
]);
|
||||
let complete = false;
|
||||
|
||||
while (!complete) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const status = await migrateStoreToSQLite({
|
||||
db,
|
||||
save: saveMessages,
|
||||
remove: _removeMessages,
|
||||
storeName: 'messages',
|
||||
handleDOMException,
|
||||
lastIndex,
|
||||
});
|
||||
|
||||
({ complete, lastIndex } = status);
|
||||
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await Promise.all([
|
||||
setMessageExportCount(db, doneSoFar),
|
||||
setMessageExportLastIndex(db, lastIndex),
|
||||
]);
|
||||
|
||||
const { count } = status;
|
||||
doneSoFar += count;
|
||||
if (countCallback) {
|
||||
countCallback(doneSoFar);
|
||||
}
|
||||
}
|
||||
window.log.info('migrateToSQL: migrate of messages complete');
|
||||
try {
|
||||
await clearStores(['messages']);
|
||||
} catch (error) {
|
||||
window.log.warn('Failed to clear messages store');
|
||||
}
|
||||
|
||||
lastIndex = await getUnprocessedExportLastIndex(db);
|
||||
complete = false;
|
||||
|
||||
while (!complete) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const status = await migrateStoreToSQLite({
|
||||
db,
|
||||
save: async array => {
|
||||
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.
|
||||
|
||||
// Need to set it to version two, since we're using Base64 strings now
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
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);
|
||||
}
|
||||
})
|
||||
);
|
||||
await saveUnprocesseds(array);
|
||||
},
|
||||
remove: removeUnprocessed,
|
||||
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');
|
||||
try {
|
||||
await clearStores(['unprocessed']);
|
||||
} catch (error) {
|
||||
window.log.warn('Failed to clear unprocessed store');
|
||||
}
|
||||
|
||||
complete = false;
|
||||
lastIndex = null;
|
||||
|
||||
while (!complete) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const status = await migrateStoreToSQLite({
|
||||
db,
|
||||
// eslint-disable-next-line no-loop-func
|
||||
save: async array => {
|
||||
const conversations = await Promise.all(
|
||||
map(array, async conversation =>
|
||||
migrateConversation(conversation, { writeNewAttachmentData })
|
||||
)
|
||||
);
|
||||
|
||||
saveConversations(conversations);
|
||||
},
|
||||
remove: _removeConversations,
|
||||
storeName: 'conversations',
|
||||
handleDOMException,
|
||||
lastIndex,
|
||||
// Because we're doing real-time moves to the filesystem, minimize parallelism
|
||||
batchSize: 5,
|
||||
});
|
||||
|
||||
({ complete, lastIndex } = status);
|
||||
}
|
||||
window.log.info('migrateToSQL: migrate of conversations complete');
|
||||
try {
|
||||
await clearStores(['conversations']);
|
||||
} catch (error) {
|
||||
window.log.warn('Failed to clear conversations store');
|
||||
}
|
||||
|
||||
complete = false;
|
||||
lastIndex = null;
|
||||
|
||||
while (!complete) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const status = await migrateStoreToSQLite({
|
||||
db,
|
||||
// eslint-disable-next-line no-loop-func
|
||||
save: bulkAddSessions,
|
||||
remove: removeSessionById,
|
||||
storeName: 'sessions',
|
||||
handleDOMException,
|
||||
lastIndex,
|
||||
batchSize: 10,
|
||||
});
|
||||
|
||||
({ complete, lastIndex } = status);
|
||||
}
|
||||
window.log.info('migrateToSQL: migrate of sessions complete');
|
||||
try {
|
||||
await clearStores(['sessions']);
|
||||
} catch (error) {
|
||||
window.log.warn('Failed to clear sessions store');
|
||||
}
|
||||
|
||||
complete = false;
|
||||
lastIndex = null;
|
||||
|
||||
while (!complete) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const status = await migrateStoreToSQLite({
|
||||
db,
|
||||
// eslint-disable-next-line no-loop-func
|
||||
save: bulkAddIdentityKeys,
|
||||
remove: removeIdentityKeyById,
|
||||
storeName: 'identityKeys',
|
||||
handleDOMException,
|
||||
lastIndex,
|
||||
batchSize: 10,
|
||||
});
|
||||
|
||||
({ complete, lastIndex } = status);
|
||||
}
|
||||
window.log.info('migrateToSQL: migrate of identityKeys complete');
|
||||
try {
|
||||
await clearStores(['identityKeys']);
|
||||
} catch (error) {
|
||||
window.log.warn('Failed to clear identityKeys store');
|
||||
}
|
||||
|
||||
complete = false;
|
||||
lastIndex = null;
|
||||
|
||||
while (!complete) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const status = await migrateStoreToSQLite({
|
||||
db,
|
||||
// eslint-disable-next-line no-loop-func
|
||||
save: bulkAddPreKeys,
|
||||
remove: removePreKeyById,
|
||||
storeName: 'preKeys',
|
||||
handleDOMException,
|
||||
lastIndex,
|
||||
batchSize: 10,
|
||||
});
|
||||
|
||||
({ complete, lastIndex } = status);
|
||||
}
|
||||
window.log.info('migrateToSQL: migrate of preKeys complete');
|
||||
try {
|
||||
await clearStores(['preKeys']);
|
||||
} catch (error) {
|
||||
window.log.warn('Failed to clear preKeys store');
|
||||
}
|
||||
|
||||
complete = false;
|
||||
lastIndex = null;
|
||||
|
||||
while (!complete) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const status = await migrateStoreToSQLite({
|
||||
db,
|
||||
// eslint-disable-next-line no-loop-func
|
||||
save: bulkAddSignedPreKeys,
|
||||
remove: removeSignedPreKeyById,
|
||||
storeName: 'signedPreKeys',
|
||||
handleDOMException,
|
||||
lastIndex,
|
||||
batchSize: 10,
|
||||
});
|
||||
|
||||
({ complete, lastIndex } = status);
|
||||
}
|
||||
window.log.info('migrateToSQL: migrate of signedPreKeys complete');
|
||||
try {
|
||||
await clearStores(['signedPreKeys']);
|
||||
} catch (error) {
|
||||
window.log.warn('Failed to clear signedPreKeys store');
|
||||
}
|
||||
|
||||
complete = false;
|
||||
lastIndex = null;
|
||||
|
||||
while (!complete) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const status = await migrateStoreToSQLite({
|
||||
db,
|
||||
// eslint-disable-next-line no-loop-func
|
||||
save: bulkAddItems,
|
||||
remove: removeItemById,
|
||||
storeName: 'items',
|
||||
handleDOMException,
|
||||
lastIndex,
|
||||
batchSize: 10,
|
||||
});
|
||||
|
||||
({ complete, lastIndex } = status);
|
||||
}
|
||||
window.log.info('migrateToSQL: migrate of items complete');
|
||||
// Note: we don't clear the items store because it contains important metadata which,
|
||||
// if this process fails, will be crucial to going through this process again.
|
||||
|
||||
window.log.info('migrateToSQL: complete');
|
||||
}
|
||||
|
||||
async function migrateStoreToSQLite({
|
||||
db,
|
||||
save,
|
||||
remove,
|
||||
storeName,
|
||||
handleDOMException,
|
||||
lastIndex = null,
|
||||
batchSize = 50,
|
||||
}) {
|
||||
if (!db) {
|
||||
throw new Error('Need db for IndexedDB connection!');
|
||||
}
|
||||
if (!isFunction(save)) {
|
||||
throw new Error('Need save function!');
|
||||
}
|
||||
if (!isFunction(remove)) {
|
||||
throw new Error('Need remove function!');
|
||||
}
|
||||
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) {
|
||||
// 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);
|
||||
|
||||
// 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,
|
||||
};
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
exports.run = ({ transaction, logger }) => {
|
||||
const messagesStore = transaction.objectStore('messages');
|
||||
|
||||
logger.info("Create message attachment metadata index: 'hasAttachments'");
|
||||
messagesStore.createIndex(
|
||||
'hasAttachments',
|
||||
['conversationId', 'hasAttachments', 'received_at'],
|
||||
{ unique: false }
|
||||
);
|
||||
|
||||
['hasVisualMediaAttachments', 'hasFileAttachments'].forEach(name => {
|
||||
logger.info(`Create message attachment metadata index: '${name}'`);
|
||||
messagesStore.createIndex(name, ['conversationId', 'received_at', name], {
|
||||
unique: false,
|
||||
});
|
||||
});
|
||||
};
|
@ -1,35 +0,0 @@
|
||||
/* global window, Whisper */
|
||||
|
||||
const Migrations = require('./migrations');
|
||||
|
||||
exports.getPlaceholderMigrations = () => {
|
||||
const version = Migrations.getLatestVersion();
|
||||
|
||||
return [
|
||||
{
|
||||
version,
|
||||
migrate() {
|
||||
throw new Error(
|
||||
'Unexpected invocation of placeholder migration!' +
|
||||
'\n\nMigrations must explicitly be run upon application startup instead' +
|
||||
' of implicitly via Backbone IndexedDB adapter at any time.'
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
exports.getCurrentVersion = () =>
|
||||
new Promise((resolve, reject) => {
|
||||
const request = window.indexedDB.open(Whisper.Database.id);
|
||||
|
||||
request.onerror = reject;
|
||||
request.onupgradeneeded = reject;
|
||||
|
||||
request.onsuccess = () => {
|
||||
const db = request.result;
|
||||
const { version } = db;
|
||||
|
||||
return resolve(version);
|
||||
};
|
||||
});
|
@ -1,245 +0,0 @@
|
||||
/* global window */
|
||||
|
||||
const { isString, last } = require('lodash');
|
||||
|
||||
const { runMigrations } = require('./run_migrations');
|
||||
const Migration18 = require('./18');
|
||||
|
||||
// IMPORTANT: The migrations below are run on a database that may be very large
|
||||
// due to attachments being directly stored inside the database. Please avoid
|
||||
// any expensive operations, e.g. modifying all messages / attachments, etc., as
|
||||
// it may cause out-of-memory errors for users with long histories:
|
||||
// https://github.com/signalapp/Signal-Desktop/issues/2163
|
||||
const migrations = [
|
||||
{
|
||||
version: '12.0',
|
||||
migrate(transaction, next) {
|
||||
window.log.info('Migration 12');
|
||||
window.log.info('creating object stores');
|
||||
const messages = transaction.db.createObjectStore('messages');
|
||||
messages.createIndex('conversation', ['conversationId', 'received_at'], {
|
||||
unique: false,
|
||||
});
|
||||
messages.createIndex('receipt', 'sent_at', { unique: false });
|
||||
messages.createIndex('unread', ['conversationId', 'unread'], {
|
||||
unique: false,
|
||||
});
|
||||
messages.createIndex('expires_at', 'expires_at', { unique: false });
|
||||
|
||||
const conversations = transaction.db.createObjectStore('conversations');
|
||||
conversations.createIndex('inbox', 'active_at', { unique: false });
|
||||
conversations.createIndex('group', 'members', {
|
||||
unique: false,
|
||||
multiEntry: true,
|
||||
});
|
||||
conversations.createIndex('type', 'type', {
|
||||
unique: false,
|
||||
});
|
||||
conversations.createIndex('search', 'tokens', {
|
||||
unique: false,
|
||||
multiEntry: true,
|
||||
});
|
||||
|
||||
transaction.db.createObjectStore('groups');
|
||||
|
||||
transaction.db.createObjectStore('sessions');
|
||||
transaction.db.createObjectStore('identityKeys');
|
||||
const preKeys = transaction.db.createObjectStore('preKeys', {
|
||||
keyPath: 'id',
|
||||
});
|
||||
preKeys.createIndex('recipient', 'recipient', { unique: true });
|
||||
|
||||
transaction.db.createObjectStore('signedPreKeys');
|
||||
transaction.db.createObjectStore('items');
|
||||
|
||||
const contactPreKeys = transaction.db.createObjectStore(
|
||||
'contactPreKeys',
|
||||
{ keyPath: 'id', autoIncrement: true }
|
||||
);
|
||||
contactPreKeys.createIndex('identityKeyString', 'identityKeyString', {
|
||||
unique: false,
|
||||
});
|
||||
contactPreKeys.createIndex('keyId', 'keyId', { unique: false });
|
||||
|
||||
const contactSignedPreKeys = transaction.db.createObjectStore(
|
||||
'contactSignedPreKeys',
|
||||
{ keyPath: 'id', autoIncrement: true }
|
||||
);
|
||||
contactSignedPreKeys.createIndex(
|
||||
'identityKeyString',
|
||||
'identityKeyString',
|
||||
{ unique: false }
|
||||
);
|
||||
contactSignedPreKeys.createIndex('keyId', 'keyId', { unique: false });
|
||||
|
||||
window.log.info('creating debug log');
|
||||
transaction.db.createObjectStore('debug');
|
||||
|
||||
next();
|
||||
},
|
||||
},
|
||||
{
|
||||
version: '13.0',
|
||||
migrate(transaction, next) {
|
||||
window.log.info('Migration 13');
|
||||
window.log.info('Adding fields to identity keys');
|
||||
const identityKeys = transaction.objectStore('identityKeys');
|
||||
const request = identityKeys.openCursor();
|
||||
const promises = [];
|
||||
request.onsuccess = event => {
|
||||
const cursor = event.target.result;
|
||||
if (cursor) {
|
||||
const attributes = cursor.value;
|
||||
attributes.timestamp = 0;
|
||||
attributes.firstUse = false;
|
||||
attributes.nonblockingApproval = false;
|
||||
attributes.verified = 0;
|
||||
promises.push(
|
||||
new Promise((resolve, reject) => {
|
||||
const putRequest = identityKeys.put(attributes, attributes.id);
|
||||
putRequest.onsuccess = resolve;
|
||||
putRequest.onerror = error => {
|
||||
window.log.error(error && error.stack ? error.stack : error);
|
||||
reject(error);
|
||||
};
|
||||
})
|
||||
);
|
||||
cursor.continue();
|
||||
} else {
|
||||
// no more results
|
||||
// eslint-disable-next-line more/no-then
|
||||
Promise.all(promises).then(() => {
|
||||
next();
|
||||
});
|
||||
}
|
||||
};
|
||||
request.onerror = event => {
|
||||
window.log.error(event);
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
version: '14.0',
|
||||
migrate(transaction, next) {
|
||||
window.log.info('Migration 14');
|
||||
window.log.info('Adding unprocessed message store');
|
||||
const unprocessed = transaction.db.createObjectStore('unprocessed');
|
||||
unprocessed.createIndex('received', 'timestamp', { unique: false });
|
||||
next();
|
||||
},
|
||||
},
|
||||
{
|
||||
version: '15.0',
|
||||
migrate(transaction, next) {
|
||||
window.log.info('Migration 15');
|
||||
window.log.info('Adding messages index for de-duplication');
|
||||
const messages = transaction.objectStore('messages');
|
||||
messages.createIndex('unique', ['source', 'sourceDevice', 'sent_at'], {
|
||||
unique: true,
|
||||
});
|
||||
next();
|
||||
},
|
||||
},
|
||||
{
|
||||
version: '16.0',
|
||||
migrate(transaction, next) {
|
||||
window.log.info('Migration 16');
|
||||
window.log.info('Dropping log table, since we now log to disk');
|
||||
transaction.db.deleteObjectStore('debug');
|
||||
next();
|
||||
},
|
||||
},
|
||||
{
|
||||
version: 17,
|
||||
async migrate(transaction, next) {
|
||||
window.log.info('Migration 17');
|
||||
|
||||
const start = Date.now();
|
||||
|
||||
const messagesStore = transaction.objectStore('messages');
|
||||
window.log.info(
|
||||
'Create index from attachment schema version to attachment'
|
||||
);
|
||||
messagesStore.createIndex('schemaVersion', 'schemaVersion', {
|
||||
unique: false,
|
||||
});
|
||||
|
||||
const duration = Date.now() - start;
|
||||
|
||||
window.log.info(
|
||||
'Complete migration to database version 17',
|
||||
`Duration: ${duration}ms`
|
||||
);
|
||||
next();
|
||||
},
|
||||
},
|
||||
{
|
||||
version: 18,
|
||||
migrate(transaction, next) {
|
||||
window.log.info('Migration 18');
|
||||
|
||||
const start = Date.now();
|
||||
Migration18.run({ transaction, logger: window.log });
|
||||
const duration = Date.now() - start;
|
||||
|
||||
window.log.info(
|
||||
'Complete migration to database version 18',
|
||||
`Duration: ${duration}ms`
|
||||
);
|
||||
next();
|
||||
},
|
||||
},
|
||||
{
|
||||
version: 19,
|
||||
migrate(transaction, next) {
|
||||
window.log.info('Migration 19');
|
||||
|
||||
// Empty because we don't want to cause incompatibility with beta users who have
|
||||
// already run migration 19 when it was object store removal.
|
||||
|
||||
next();
|
||||
},
|
||||
},
|
||||
{
|
||||
version: 20,
|
||||
migrate(transaction, next) {
|
||||
window.log.info('Migration 20');
|
||||
|
||||
// Empty because we don't want to cause incompatibility with users who have already
|
||||
// run migration 20 when it was object store removal.
|
||||
|
||||
next();
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const database = {
|
||||
id: 'loki-messenger',
|
||||
nolog: true,
|
||||
migrations,
|
||||
};
|
||||
|
||||
exports.run = ({ Backbone, databaseName, logger } = {}) =>
|
||||
runMigrations({
|
||||
Backbone,
|
||||
logger,
|
||||
database: Object.assign(
|
||||
{},
|
||||
database,
|
||||
isString(databaseName) ? { id: databaseName } : {}
|
||||
),
|
||||
});
|
||||
|
||||
exports.getDatabase = () => ({
|
||||
name: database.id,
|
||||
version: exports.getLatestVersion(),
|
||||
});
|
||||
|
||||
exports.getLatestVersion = () => {
|
||||
const lastMigration = last(migrations);
|
||||
if (!lastMigration) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return lastMigration.version;
|
||||
};
|
@ -1,79 +0,0 @@
|
||||
/* eslint-env browser */
|
||||
|
||||
const { head, isFunction, isObject, isString, last } = require('lodash');
|
||||
|
||||
const db = require('../database');
|
||||
const { deferredToPromise } = require('../deferred_to_promise');
|
||||
|
||||
const closeDatabaseConnection = ({ Backbone } = {}) =>
|
||||
deferredToPromise(Backbone.sync('closeall'));
|
||||
|
||||
exports.runMigrations = async ({ Backbone, database, logger } = {}) => {
|
||||
if (
|
||||
!isObject(Backbone) ||
|
||||
!isObject(Backbone.Collection) ||
|
||||
!isFunction(Backbone.Collection.extend)
|
||||
) {
|
||||
throw new TypeError('runMigrations: Backbone is required');
|
||||
}
|
||||
|
||||
if (
|
||||
!isObject(database) ||
|
||||
!isString(database.id) ||
|
||||
!Array.isArray(database.migrations)
|
||||
) {
|
||||
throw new TypeError('runMigrations: database is required');
|
||||
}
|
||||
if (!isObject(logger)) {
|
||||
throw new TypeError('runMigrations: logger is required');
|
||||
}
|
||||
|
||||
const {
|
||||
firstVersion: firstMigrationVersion,
|
||||
lastVersion: lastMigrationVersion,
|
||||
} = getMigrationVersions(database);
|
||||
|
||||
const databaseVersion = await db.getVersion(database.id);
|
||||
const isAlreadyUpgraded = databaseVersion >= lastMigrationVersion;
|
||||
|
||||
logger.info('Database status', {
|
||||
firstMigrationVersion,
|
||||
lastMigrationVersion,
|
||||
databaseVersion,
|
||||
isAlreadyUpgraded,
|
||||
});
|
||||
|
||||
if (isAlreadyUpgraded) {
|
||||
return;
|
||||
}
|
||||
|
||||
const migrationCollection = new (Backbone.Collection.extend({
|
||||
database,
|
||||
storeName: 'items',
|
||||
}))();
|
||||
|
||||
// Note: this legacy migration technique is required to bring old clients with
|
||||
// data in IndexedDB forward into the new world of SQLCipher only.
|
||||
await deferredToPromise(migrationCollection.fetch({ limit: 1 }));
|
||||
|
||||
logger.info('Close database connection');
|
||||
await closeDatabaseConnection({ Backbone });
|
||||
};
|
||||
|
||||
const getMigrationVersions = database => {
|
||||
if (!isObject(database) || !Array.isArray(database.migrations)) {
|
||||
throw new TypeError("'database' is required");
|
||||
}
|
||||
|
||||
const firstMigration = head(database.migrations);
|
||||
const lastMigration = last(database.migrations);
|
||||
|
||||
const firstVersion = firstMigration
|
||||
? parseInt(firstMigration.version, 10)
|
||||
: null;
|
||||
const lastVersion = lastMigration
|
||||
? parseInt(lastMigration.version, 10)
|
||||
: null;
|
||||
|
||||
return { firstVersion, lastVersion };
|
||||
};
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue