UNPKG

bitcore-wallet-service

Version:
1,398 lines 45.4 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Storage = void 0; const async = __importStar(require("async")); const lodash_1 = __importDefault(require("lodash")); const mongodb = __importStar(require("mongodb")); const bchaddresstranslator_1 = require("./bchaddresstranslator"); const common_1 = require("./common"); const logger_1 = __importDefault(require("./logger")); const model_1 = require("./model"); const $ = require('preconditions').singleton(); const collections = { WALLETS: 'wallets', TXS: 'txs', ADDRESSES: 'addresses', ADVERTISEMENTS: 'advertisements', NOTIFICATIONS: 'notifications', COPAYERS_LOOKUP: 'copayers_lookup', PREFERENCES: 'preferences', EMAIL_QUEUE: 'email_queue', CACHE: 'cache', FIAT_RATES2: 'fiat_rates2', TX_NOTES: 'tx_notes', SESSIONS: 'sessions', PUSH_NOTIFICATION_SUBS: 'push_notification_subs', TX_CONFIRMATION_SUBS: 'tx_confirmation_subs', LOCKS: 'locks' }; const Defaults = common_1.Common.Defaults; const Utils = common_1.Common.Utils; const ObjectID = mongodb.ObjectID; var objectIdDate = function (date) { return Math.floor(date / 1000).toString(16) + '0000000000000000'; }; class Storage { constructor(opts = {}) { this.walletCheck = async (params) => { const { walletId } = params; return new Promise(resolve => { const addressStream = this.db.collection(collections.ADDRESSES).find({ walletId }); let sum = 0; let lastAddress; addressStream.on('data', walletAddress => { if (walletAddress.address) { lastAddress = walletAddress.address.replace(/:.*$/, ''); const addressSum = Buffer.from(lastAddress).reduce((tot, cur) => (tot + cur) % Number.MAX_SAFE_INTEGER); sum = (sum + addressSum) % Number.MAX_SAFE_INTEGER; } }); addressStream.on('end', () => { resolve({ lastAddress, sum }); }); }); }; opts = opts || {}; this.db = opts.db; } static createIndexes(db) { logger_1.default.info('Creating DB indexes'); if (!db.collection) { logger_1.default.error('DB not ready: [storage.ts] no db.collection'); return; } db.collection(collections.WALLETS).createIndex({ id: 1 }); db.collection(collections.COPAYERS_LOOKUP).createIndex({ copayerId: 1 }); db.collection(collections.COPAYERS_LOOKUP).createIndex({ walletId: 1 }); db.collection(collections.TXS).createIndex({ walletId: 1, id: 1 }); db.collection(collections.TXS).createIndex({ walletId: 1, isPending: 1, txid: 1 }); db.collection(collections.TXS).createIndex({ walletId: 1, createdOn: -1 }); db.collection(collections.TXS).createIndex({ txid: 1 }); db.collection(collections.NOTIFICATIONS).createIndex({ walletId: 1, id: 1 }); db.collection(collections.ADVERTISEMENTS).createIndex({ advertisementId: 1, title: 1 }, { unique: true }); db.collection(collections.ADDRESSES).createIndex({ walletId: 1, createdOn: 1 }); db.collection(collections.ADDRESSES).createIndex({ address: 1, coin: 1 }, { unique: true }); db.collection(collections.ADDRESSES).createIndex({ address: 1, beRegistered: 1 }); db.collection(collections.ADDRESSES).createIndex({ walletId: 1, address: 1 }); db.collection(collections.EMAIL_QUEUE).createIndex({ id: 1 }); db.collection(collections.EMAIL_QUEUE).createIndex({ notificationId: 1 }); db.collection(collections.CACHE).createIndex({ walletId: 1, type: 1, key: 1 }); db.collection(collections.TX_NOTES).createIndex({ walletId: 1, txid: 1 }); db.collection(collections.PREFERENCES).createIndex({ walletId: 1 }); db.collection(collections.FIAT_RATES2).createIndex({ coin: 1, code: 1, ts: 1 }); db.collection(collections.PUSH_NOTIFICATION_SUBS).createIndex({ copayerId: 1 }); db.collection(collections.TX_CONFIRMATION_SUBS).createIndex({ copayerId: 1, txid: 1 }); db.collection(collections.TX_CONFIRMATION_SUBS).createIndex({ isActive: 1, txid: 1, copayerId: 1 }); db.collection(collections.SESSIONS).createIndex({ copayerId: 1 }); } connect(opts, cb) { opts = opts || {}; if (this.db) return cb(); const config = opts.mongoDb || {}; if (opts.secondaryPreferred) { if (config.uri.indexOf('?') > 0) { config.uri = config.uri + '&'; } else { config.uri = config.uri + '?'; } config.uri = config.uri + 'readPreference=secondaryPreferred'; logger_1.default.info('Read operations set to secondaryPreferred'); } if (!config.dbname) { logger_1.default.error('No dbname at config.'); return cb(new Error('No dbname at config.')); } mongodb.MongoClient.connect(config.uri, { useUnifiedTopology: true }, (err, client) => { if (err) { logger_1.default.error('Unable to connect to the mongoDB. Check the credentials.'); return cb(err); } this.db = client.db(config.dbname); this.client = client; logger_1.default.info(`Connection established to db: ${config.uri}`); Storage.createIndexes(this.db); return cb(); }); } disconnect(cb) { if (this.client) { this.client.close(err => { if (err) return cb(err); this.db = null; this.client = null; return cb(); }); } else { return cb(); } } fetchWallet(id, cb) { if (!this.db) return cb('not ready'); this.db.collection(collections.WALLETS).findOne({ id }, (err, result) => { if (err) return cb(err); if (!result) return cb(); return cb(null, model_1.Wallet.fromObj(result)); }); } storeWallet(wallet, cb) { this.db.collection(collections.WALLETS).replaceOne({ id: wallet.id }, wallet.toObject(), { w: 1, upsert: true }, cb); } storeWalletAndUpdateCopayersLookup(wallet, cb) { const copayerLookups = lodash_1.default.map(wallet.copayers, copayer => { try { $.checkState(copayer.requestPubKeys, 'Failed state: copayer.requestPubkeys undefined at <storeWalletAndUpdateCopayersLookup()>'); } catch (e) { return cb(e); } return { copayerId: copayer.id, walletId: wallet.id, requestPubKeys: copayer.requestPubKeys }; }); this.db.collection(collections.COPAYERS_LOOKUP).deleteMany({ walletId: wallet.id }, { w: 1 }, err => { if (err) return cb(err); this.db.collection(collections.COPAYERS_LOOKUP).insertMany(copayerLookups, { w: 1 }, err => { if (err) return cb(err); return this.storeWallet(wallet, cb); }); }); } fetchCopayerLookup(copayerId, cb) { this.db.collection(collections.COPAYERS_LOOKUP).findOne({ copayerId }, (err, result) => { if (err) return cb(err); if (!result) return cb(); if (!result.requestPubKeys) { result.requestPubKeys = [ { key: result.requestPubKey, signature: result.signature } ]; } return cb(null, result); }); } _completeTxData(walletId, txs, cb) { this.fetchWallet(walletId, (err, wallet) => { if (err) return cb(err); lodash_1.default.each([].concat(txs), tx => { tx.derivationStrategy = wallet.derivationStrategy || 'BIP45'; tx.creatorName = wallet.getCopayer(tx.creatorId).name; lodash_1.default.each(tx.actions, action => { action.copayerName = wallet.getCopayer(action.copayerId).name; }); if (tx.status == 'accepted') tx.raw = tx.getRawTx(); }); return cb(null, txs); }); } fetchTx(walletId, txProposalId, cb) { if (!this.db) return cb(); this.db.collection(collections.TXS).findOne({ id: txProposalId, walletId }, (err, result) => { if (err) return cb(err); if (!result) return cb(); return this._completeTxData(walletId, model_1.TxProposal.fromObj(result), cb); }); } fetchTxByHash(hash, cb) { if (!this.db) return cb(); this.db.collection(collections.TXS).findOne({ txid: hash }, (err, result) => { if (err) return cb(err); if (!result) return cb(); return this._completeTxData(result.walletId, model_1.TxProposal.fromObj(result), cb); }); } fetchLastTxs(walletId, creatorId, limit, cb) { this.db .collection(collections.TXS) .find({ walletId, creatorId }, { limit: limit || 5 }) .sort({ createdOn: -1 }) .toArray((err, result) => { if (err) return cb(err); if (!result) return cb(); const txs = lodash_1.default.map(result, tx => { return model_1.TxProposal.fromObj(tx); }); return cb(null, txs); }); } fetchEthPendingTxs(multisigTxpsInfo) { return new Promise((resolve, reject) => { this.db .collection(collections.TXS) .find({ txid: { $in: multisigTxpsInfo.map(txpInfo => txpInfo.transactionHash) } }) .sort({ createdOn: -1 }) .toArray(async (err, result) => { if (err) return reject(err); if (!result) return reject(); const multisigTxpsInfoByTransactionHash = lodash_1.default.groupBy(multisigTxpsInfo, 'transactionHash'); const actionsById = {}; const txs = lodash_1.default.compact(lodash_1.default.map(result, tx => { if (!tx.multisigContractAddress) { return undefined; } tx.status = 'pending'; tx.multisigTxId = multisigTxpsInfoByTransactionHash[tx.txid][0].transactionId; tx.actions.forEach(action => { if (lodash_1.default.some(multisigTxpsInfoByTransactionHash[tx.txid], { event: 'ExecutionFailure' })) { action.type = 'failed'; } }); if (tx.amount === 0) { actionsById[tx.multisigTxId] = [...tx.actions, ...(actionsById[tx.multisigTxId] || [])]; return undefined; } return model_1.TxProposal.fromObj(tx); })); txs.forEach((tx) => { if (actionsById[tx.multisigTxId]) { tx.actions = [...tx.actions, ...(actionsById[tx.multisigTxId] || [])]; } }); return resolve(txs); }); }); } fetchPendingTxs(walletId, cb) { this.db .collection(collections.TXS) .find({ walletId, isPending: true }) .sort({ createdOn: -1 }) .toArray((err, result) => { if (err) return cb(err); if (!result) return cb(); const txs = lodash_1.default.map(result, tx => { return model_1.TxProposal.fromObj(tx); }); return this._completeTxData(walletId, txs, cb); }); } fetchTxs(walletId, opts, cb) { opts = opts || {}; const tsFilter = {}; if (lodash_1.default.isNumber(opts.minTs)) tsFilter.$gte = opts.minTs; if (lodash_1.default.isNumber(opts.maxTs)) tsFilter.$lte = opts.maxTs; const filter = { walletId }; if (!lodash_1.default.isEmpty(tsFilter)) filter.createdOn = tsFilter; const mods = {}; if (lodash_1.default.isNumber(opts.limit)) mods.limit = opts.limit; this.db .collection(collections.TXS) .find(filter, mods) .sort({ createdOn: -1 }) .toArray((err, result) => { if (err) return cb(err); if (!result) return cb(); const txs = lodash_1.default.map(result, tx => { return model_1.TxProposal.fromObj(tx); }); return this._completeTxData(walletId, txs, cb); }); } fetchBroadcastedTxs(walletId, opts, cb) { opts = opts || {}; const tsFilter = {}; if (lodash_1.default.isNumber(opts.minTs)) tsFilter.$gte = opts.minTs; if (lodash_1.default.isNumber(opts.maxTs)) tsFilter.$lte = opts.maxTs; const filter = { walletId, status: 'broadcasted' }; if (!lodash_1.default.isEmpty(tsFilter)) filter.broadcastedOn = tsFilter; const mods = {}; if (lodash_1.default.isNumber(opts.limit)) mods.limit = opts.limit; this.db .collection(collections.TXS) .find(filter, mods) .sort({ createdOn: -1 }) .toArray((err, result) => { if (err) return cb(err); if (!result) return cb(); const txs = lodash_1.default.map(result, tx => { return model_1.TxProposal.fromObj(tx); }); return this._completeTxData(walletId, txs, cb); }); } fetchNotifications(walletId, notificationId, minTs, cb) { function makeId(timestamp) { return lodash_1.default.padStart(timestamp, 14, '0') + lodash_1.default.repeat('0', 4); } let minId = makeId(minTs); if (notificationId) { minId = notificationId > minId ? notificationId : minId; } this.db .collection(collections.NOTIFICATIONS) .find({ walletId, id: { $gt: minId } }) .sort({ id: 1 }) .toArray((err, result) => { if (err) return cb(err); if (!result) return cb(); const notifications = lodash_1.default.map(result, notification => { return model_1.Notification.fromObj(notification); }); return cb(null, notifications); }); } storeNotification(walletId, notification, cb) { if (!this.db) { logger_1.default.warn('Trying to store a notification with close DB %o', notification); return; } this.db.collection(collections.NOTIFICATIONS).insertOne(notification, { w: 1 }, cb); } storeTx(walletId, txp, cb) { this.db.collection(collections.TXS).replaceOne({ id: txp.id, walletId }, txp.toObject(), { w: 1, upsert: true }, cb); } removeTx(walletId, txProposalId, cb) { this.db.collection(collections.TXS).deleteOne({ id: txProposalId, walletId }, { w: 1 }, cb); } removeWallet(walletId, cb) { async.parallel([ next => { this.db.collection(collections.WALLETS).deleteOne({ id: walletId }, next); }, next => { const otherCollections = lodash_1.default.without(lodash_1.default.values(collections), collections.WALLETS); async.each(otherCollections, (col, next) => { this.db.collection(col).deleteMany({ walletId }, next); }, next); } ], cb); } fetchAddresses(walletId, cb) { this.db .collection(collections.ADDRESSES) .find({ walletId }) .sort({ createdOn: 1 }) .toArray((err, result) => { if (err) return cb(err); if (!result) return cb(); return cb(null, Utils.sortAsc(result.map(model_1.Address.fromObj), 'createdOn', 'path')); }); } migrateToCashAddr(walletId, cb) { const cursor = this.db.collection(collections.ADDRESSES).find({ walletId }); cursor.on('end', () => { console.log(`Migration to cash address of ${walletId} Finished`); return this.clearWalletCache(walletId, cb); }); cursor.on('err', err => { return cb(err); }); cursor.on('data', doc => { cursor.pause(); let x; try { x = bchaddresstranslator_1.BCHAddressTranslator.translate(doc.address, 'cashaddr'); } catch (e) { return cb(e); } this.db.collection(collections.ADDRESSES).updateMany({ _id: doc._id }, { $set: { address: x } }); cursor.resume(); }); } fetchUnsyncAddresses(walletId, cb) { this.db .collection(collections.ADDRESSES) .find({ walletId, beRegistered: null }) .toArray((err, result) => { if (err) return cb(err); if (!result) return cb(); return cb(null, result); }); } fetchNewAddresses(walletId, fromTs, cb) { this.db .collection(collections.ADDRESSES) .find({ walletId, createdOn: { $gte: fromTs } }) .sort({ createdOn: 1 }) .toArray((err, result) => { if (err) return cb(err); if (!result) return cb(); return cb(null, result.map(model_1.Address.fromObj)); }); } storeAddress(address, cb) { this.db.collection(collections.ADDRESSES).replaceOne({ walletId: address.walletId, address: address.address }, address, { w: 1, upsert: false }, cb); } markSyncedAddresses(addresses, cb) { this.db.collection(collections.ADDRESSES).updateMany({ address: { $in: addresses } }, { $set: { beRegistered: true } }, { w: 1, upsert: false }, cb); } deregisterWallet(walletId, cb) { this.db.collection(collections.WALLETS).updateOne({ id: walletId }, { $set: { beRegistered: null } }, { w: 1, upsert: false }, () => { this.db.collection(collections.ADDRESSES).updateMany({ walletId }, { $set: { beRegistered: null } }, { w: 1, upsert: false }, () => { this.clearWalletCache(walletId, cb); }); }); } storeAddressAndWallet(wallet, addresses, cb) { const clonedAddresses = [].concat(addresses); if (lodash_1.default.isEmpty(addresses)) return cb(); let duplicate; this.db.collection(collections.ADDRESSES).insertMany(clonedAddresses, { w: 1 }, err => { if (err) { if (!err.toString().match(/E11000/)) { return cb(err); } else { duplicate = true; logger_1.default.warn('Found duplicate address: ' + clonedAddresses.map(a => a.address).join(',')); } } this.storeWallet(wallet, err => { return cb(err, duplicate); }); }); } fetchAddressByWalletId(walletId, address, cb) { this.db.collection(collections.ADDRESSES).findOne({ walletId, address }, (err, result) => { if (err) return cb(err); if (!result) return cb(); return cb(null, model_1.Address.fromObj(result)); }); } fetchAddressesByWalletId(walletId, addresses, cb) { this.db .collection(collections.ADDRESSES) .find({ walletId, address: { $in: addresses } }, {}) .toArray((err, result) => { if (err) return cb(err); if (!result) return cb(); return cb(null, result); }); } fetchAddressByChain(chain, address, cb) { if (!this.db) return cb(); this.db .collection(collections.ADDRESSES) .find({ address }) .toArray((err, result) => { if (err) return cb(err); if (!result || lodash_1.default.isEmpty(result)) return cb(); if (result.length > 1) { result = lodash_1.default.find(result, address => { return chain == (address.chain || address.coin || 'btc'); }); } else { result = lodash_1.default.head(result); } if (!result) return cb(); return cb(null, model_1.Address.fromObj(result)); }); } fetchPreferences(walletId, copayerId, cb) { this.db .collection(collections.PREFERENCES) .find({ walletId }) .toArray((err, result) => { if (err) return cb(err); if (copayerId) { result = lodash_1.default.find(result, { copayerId }); } if (!result) return cb(); const preferences = lodash_1.default.map([].concat(result), r => { return model_1.Preferences.fromObj(r); }); if (copayerId) { return cb(null, preferences[0]); } else { return cb(null, preferences); } }); } storePreferences(preferences, cb) { this.db.collection(collections.PREFERENCES).replaceOne({ walletId: preferences.walletId, copayerId: preferences.copayerId }, preferences, { w: 1, upsert: true }, cb); } storeEmail(email, cb) { this.db.collection(collections.EMAIL_QUEUE).replaceOne({ id: email.id }, email, { w: 1, upsert: true }, cb); } fetchUnsentEmails(cb) { this.db .collection(collections.EMAIL_QUEUE) .find({ status: 'fail' }) .toArray((err, result) => { if (err) return cb(err); if (!result || lodash_1.default.isEmpty(result)) return cb(null, []); const emails = lodash_1.default.map(result, x => { return model_1.Email.fromObj(x); }); return cb(null, emails); }); } fetchEmailByNotification(notificationId, cb) { this.db.collection(collections.EMAIL_QUEUE).findOne({ notificationId }, (err, result) => { if (err) return cb(err); if (!result) return cb(); return cb(null, model_1.Email.fromObj(result)); }); } getTxHistoryCacheStatusV8(walletId, cb) { this.db.collection(collections.CACHE).findOne({ walletId, type: 'historyCacheStatusV8', key: null }, (err, result) => { if (err) return cb(err); if (!result) return cb(null, { tipId: null, tipIndex: null }); return cb(null, { updatedOn: result.updatedOn, updatedHeight: result.updatedHeight, tipIndex: result.tipIndex, tipTxId: result.tipTxId, tipHeight: result.tipHeight }); }); } getWalletAddressChecked(walletId, cb) { this.db.collection(collections.CACHE).findOne({ walletId, type: 'addressChecked', key: null }, (err, result) => { if (err || !result) return cb(err); return cb(null, result.totalAddresses); }); } setWalletAddressChecked(walletId, totalAddresses, cb) { this.db.collection(collections.CACHE).replaceOne({ walletId, type: 'addressChecked', key: null }, { walletId, type: 'addressChecked', key: null, totalAddresses }, { w: 1, upsert: true }, cb); } getTxHistoryCacheV8(walletId, skip, limit, cb) { $.checkArgument(skip >= 0); $.checkArgument(limit >= 0); this.getTxHistoryCacheStatusV8(walletId, (err, cacheStatus) => { if (err) return cb(err); if (lodash_1.default.isNull(cacheStatus.tipId)) return cb(null, []); let firstPosition = cacheStatus.tipIndex - skip - limit + 1; const lastPosition = cacheStatus.tipIndex - skip + 1; if (firstPosition < 0) firstPosition = 0; if (lastPosition <= 0) return cb(null, []); this.db .collection(collections.CACHE) .find({ walletId, type: 'historyCacheV8', key: { $gte: firstPosition, $lt: lastPosition } }) .sort({ key: -1 }) .toArray((err, result) => { if (err) return cb(err); if (!result) return cb(); const txs = lodash_1.default.map(result, 'tx'); return cb(null, txs); }); }); } clearWalletCache(walletId, cb) { this.db.collection(collections.CACHE).deleteMany({ walletId }, {}, cb); } storeTxHistoryStreamV8(walletId, streamKey, items, cb) { this.db.collection(collections.CACHE).replaceOne({ walletId, type: 'historyStream', key: null }, { walletId, type: 'historyStream', key: null, streamKey, items }, { w: 1, upsert: true }, cb); } clearTxHistoryStreamV8(walletId, cb) { this.db.collection(collections.CACHE).deleteMany({ walletId, type: 'historyStream', key: null }, {}, cb); } getTxHistoryStreamV8(walletId, cb) { this.db.collection(collections.CACHE).findOne({ walletId, type: 'historyStream', key: null }, (err, result) => { if (err || !result) return cb(err); return cb(null, result); }); } storeTxHistoryCacheV8(walletId, tipIndex, items, updateHeight, cb) { let index = lodash_1.default.isNull(tipIndex) ? 0 : tipIndex + 1; let pos; lodash_1.default.each(items.reverse(), item => { item.position = index++; }); async.each(items, (item, next) => { pos = item.position; delete item.position; this.db.collection(collections.CACHE).insertOne({ walletId, type: 'historyCacheV8', key: pos, tx: item }, next); }, err => { if (err) return cb(err); const first = lodash_1.default.first(items); const last = lodash_1.default.last(items); try { $.checkState(last.txid, 'Failed state: missing txid in tx to be cached at <storeHistoryCacheV8()>'); $.checkState(last.blockheight, 'Failed state: missing blockheight in tx to be cached at <storeHistoryCacheV8()>'); $.checkState(first.blockheight, 'Failed state: missing blockheight in tx to be cached at <storeHistoryCacheV8()>'); $.checkState(last.blockheight >= 0, 'Failed state: blockheight <=0 om tx to be cached at <storeHistoryCacheV8()>'); $.checkState(first.blockheight <= last.blockheight, 'Failed state: tx to be cached are in wrong order (lastest should be first)'); } catch (e) { return cb(e); } logger_1.default.debug(`Cache Last Item: ${last.txid} blockh: ${last.blockheight} updatedh: ${updateHeight}`); this.db.collection(collections.CACHE).replaceOne({ walletId, type: 'historyCacheStatusV8', key: null }, { walletId, type: 'historyCacheStatusV8', key: null, updatedOn: Date.now(), updatedHeight: updateHeight, tipIndex: pos, tipTxId: last.txid, tipHeight: last.blockheight }, { w: 1, upsert: true }, cb); }); } storeFiatRate(coin, rates, cb) { const now = Date.now(); async.each(rates, (rate, next) => { let i = { ts: now, coin, code: rate.code, value: rate.value }; this.db.collection(collections.FIAT_RATES2).insertOne(i, { w: 1 }, next); }, cb); } fetchFiatRate(coin, code, ts, cb) { this.db .collection(collections.FIAT_RATES2) .find({ coin, code, ts: { $lte: ts } }) .sort({ ts: -1 }) .limit(1) .toArray((err, result) => { if (err || lodash_1.default.isEmpty(result)) return cb(err); return cb(null, result[0]); }); } fetchHistoricalRates(coin, code, ts, cb) { this.db .collection(collections.FIAT_RATES2) .find({ coin, code, ts: { $gte: ts } }) .sort({ ts: -1 }) .toArray((err, result) => { if (err || lodash_1.default.isEmpty(result)) return cb(err); return cb(null, result); }); } fetchTxNote(walletId, txid, cb) { this.db.collection(collections.TX_NOTES).findOne({ walletId, txid }, (err, result) => { if (err) return cb(err); if (!result) return cb(); return this._completeTxNotesData(walletId, model_1.TxNote.fromObj(result), cb); }); } _completeTxNotesData(walletId, notes, cb) { this.fetchWallet(walletId, (err, wallet) => { if (err) return cb(err); lodash_1.default.each([].concat(notes), note => { note.editedByName = wallet.getCopayer(note.editedBy).name; }); return cb(null, notes); }); } fetchTxNotes(walletId, opts, cb) { const filter = { walletId }; if (lodash_1.default.isNumber(opts.minTs)) filter.editedOn = { $gte: opts.minTs }; this.db .collection(collections.TX_NOTES) .find(filter) .toArray((err, result) => { if (err) return cb(err); const notes = lodash_1.default.compact(lodash_1.default.map(result, note => { return model_1.TxNote.fromObj(note); })); return this._completeTxNotesData(walletId, notes, cb); }); } storeTxNote(txNote, cb) { this.db.collection(collections.TX_NOTES).replaceOne({ txid: txNote.txid, walletId: txNote.walletId }, txNote.toObject(), { w: 1, upsert: true }, cb); } getSession(copayerId, cb) { this.db.collection(collections.SESSIONS).findOne({ copayerId }, (err, result) => { if (err || !result) return cb(err); return cb(null, model_1.Session.fromObj(result)); }); } storeSession(session, cb) { this.db.collection(collections.SESSIONS).replaceOne({ copayerId: session.copayerId }, session.toObject(), { w: 1, upsert: true }, cb); } fetchPushNotificationSubs(copayerId, cb) { this.db .collection(collections.PUSH_NOTIFICATION_SUBS) .find({ copayerId }) .toArray((err, result) => { if (err) return cb(err); if (!result) return cb(); const tokens = lodash_1.default.map([].concat(result), r => { return model_1.PushNotificationSub.fromObj(r); }); return cb(null, tokens); }); } fetchLatestPushNotificationSubs(cb) { const fromDate = new Date().getTime() - Defaults.PUSH_NOTIFICATION_SUBS_TIME; this.db .collection(collections.PUSH_NOTIFICATION_SUBS) .find({ _id: { $gte: new ObjectID(objectIdDate(fromDate)) } }) .sort({ _id: -1 }) .toArray((err, result) => { if (err) return cb(err); if (!result) return cb(); const tokens = lodash_1.default.map([].concat(result), r => { return model_1.PushNotificationSub.fromObj(r); }); return cb(null, tokens); }); } storePushNotificationSub(pushNotificationSub, cb) { this.db.collection(collections.PUSH_NOTIFICATION_SUBS).replaceOne({ copayerId: pushNotificationSub.copayerId, token: pushNotificationSub.token }, pushNotificationSub, { w: 1, upsert: true }, cb); } removePushNotificationSub(copayerId, token, cb) { this.db.collection(collections.PUSH_NOTIFICATION_SUBS).deleteMany({ copayerId, token }, { w: 1 }, cb); } storePushNotificationBrazeSub(pushNotificationSub, cb) { this.db.collection(collections.PUSH_NOTIFICATION_SUBS).replaceOne({ copayerId: pushNotificationSub.copayerId, externalUserId: pushNotificationSub.externalUserId }, pushNotificationSub, { w: 1, upsert: true }, cb); } removePushNotificationBrazeSub(copayerId, externalUserId, cb) { this.db.collection(collections.PUSH_NOTIFICATION_SUBS).deleteMany({ copayerId, externalUserId }, { w: 1 }, cb); } streamActiveTxConfirmationSubs(copayerId, txids) { if (!this.db) { logger_1.default.warn('Trying to fetch notifications with closed DB'); return; } const filter = { isActive: true, txid: { $in: txids } }; if (copayerId) filter.copayerId = copayerId; return this.db .collection(collections.TX_CONFIRMATION_SUBS) .find(filter) .addCursorFlag('noCursorTimeout', true); } storeTxConfirmationSub(txConfirmationSub, cb) { this.db.collection(collections.TX_CONFIRMATION_SUBS).replaceOne({ copayerId: txConfirmationSub.copayerId, txid: txConfirmationSub.txid }, txConfirmationSub, { w: 1, upsert: true }, cb); } removeTxConfirmationSub(copayerId, txid, cb) { this.db.collection(collections.TX_CONFIRMATION_SUBS).deleteMany({ copayerId, txid }, { w: 1 }, cb); } _dump(cb, fn) { fn = fn || console.log; cb = cb || function () { }; this.db.collections((err, collections) => { if (err) return cb(err); async.eachSeries(collections, (col, next) => { col.find().toArray((err, items) => { fn('--------', col.s.name); fn(items); fn('------------------------------------------------------------------\n\n'); next(err); }); }, cb); }); } checkAndUseGlobalCache(key, duration, cb) { const now = Date.now(); this.db.collection(collections.CACHE).findOne({ key, walletId: null, type: null }, (err, ret) => { if (err) return cb(err); if (!ret) return cb(); const validFor = ret.ts + duration - now; return cb(null, validFor > 0 ? ret.result : null, ret.result); }); } storeGlobalCache(key, values, cb) { const now = Date.now(); this.db.collection(collections.CACHE).updateOne({ key, walletId: null, type: null }, { $set: { ts: now, result: values } }, { w: 1, upsert: true }, cb); } clearGlobalCache(key, cb) { this.db.collection(collections.CACHE).deleteMany({ key, walletId: null, type: null }, { w: 1 }, cb); } acquireLock(key, expireTs, cb) { this.db.collection(collections.LOCKS).insertOne({ _id: key, expireOn: expireTs }, {}, cb); } releaseLock(key, cb) { this.db.collection(collections.LOCKS).deleteMany({ _id: key }, {}, cb); } clearExpiredLock(key, cb) { this.db.collection(collections.LOCKS).findOne({ _id: key }, (err, ret) => { if (err || !ret) return; if (ret.expireOn < Date.now()) { logger_1.default.info('Releasing expired lock : ' + key); return this.releaseLock(key, cb); } return cb(); }); } fetchTestingAdverts(cb) { this.db .collection(collections.ADVERTISEMENTS) .find({ isTesting: true }) .toArray((err, result) => { if (err) return cb(err); if (!result) return cb(); return cb(null, result.map(model_1.Advertisement.fromObj)); }); } fetchActiveAdverts(cb) { this.db .collection(collections.ADVERTISEMENTS) .find({ isAdActive: true }) .toArray((err, result) => { if (err) return cb(err); if (!result) return cb(); return cb(null, result.map(model_1.Advertisement.fromObj)); }); } fetchAdvertsByCountry(country, cb) { this.db .collection(collections.ADVERTISEMENTS) .find({ country }) .toArray((err, result) => { if (err) return cb(err); if (!result) return cb(); return cb(null, result.map(model_1.Advertisement.fromObj)); }); } fetchAllAdverts(cb) { this.db.collection(collections.ADVERTISEMENTS).find({}); } removeAdvert(adId, cb) { this.db.collection(collections.ADVERTISEMENTS).deleteOne({ advertisementId: adId }, { w: 1 }, cb); } storeAdvert(advert, cb) { this.db.collection(collections.ADVERTISEMENTS).updateOne({ advertisementId: advert.advertisementId }, { $set: advert }, { upsert: true }, cb); } fetchAdvert(adId, cb) { this.db.collection(collections.ADVERTISEMENTS).findOne({ advertisementId: adId }, (err, result) => { if (err) return cb(err); if (!result) return cb(); return cb(null, model_1.Advertisement.fromObj(result)); }); } activateAdvert(adId, cb) { this.db.collection(collections.ADVERTISEMENTS).updateOne({ advertisementId: adId }, { $set: { isAdActive: true, isTesting: false } }, { upsert: true }, cb); } deactivateAdvert(adId, cb) { this.db.collection(collections.ADVERTISEMENTS).updateOne({ advertisementId: adId }, { $set: { isAdActive: false, isTesting: true } }, { upsert: true }, cb); } } exports.Storage = Storage; Storage.BCHEIGHT_KEY = 'bcheight'; Storage.collections = collections; //# sourceMappingURL=storage.js.map