'use strict';

Object.defineProperty(exports, "__esModule", {
    value: true
});

var _regenerator = require('babel-runtime/regenerator');

var _regenerator2 = _interopRequireDefault(_regenerator);

var _slicedToArray2 = require('babel-runtime/helpers/slicedToArray');

var _slicedToArray3 = _interopRequireDefault(_slicedToArray2);

var _entries = require('babel-runtime/core-js/object/entries');

var _entries2 = _interopRequireDefault(_entries);

var _getIterator2 = require('babel-runtime/core-js/get-iterator');

var _getIterator3 = _interopRequireDefault(_getIterator2);

var _bluebird = require('bluebird');

var _defineProperty2 = require('babel-runtime/helpers/defineProperty');

var _defineProperty3 = _interopRequireDefault(_defineProperty2);

var _promise = require('babel-runtime/core-js/promise');

var _promise2 = _interopRequireDefault(_promise);

var _assign = require('babel-runtime/core-js/object/assign');

var _assign2 = _interopRequireDefault(_assign);

var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');

var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);

var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');

var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);

var _createClass2 = require('babel-runtime/helpers/createClass');

var _createClass3 = _interopRequireDefault(_createClass2);

var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');

var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);

var _inherits2 = require('babel-runtime/helpers/inherits');

var _inherits3 = _interopRequireDefault(_inherits2);

var _event = require('../../models/event');

var _events = require('events');

var _logger = require('../../logger');

var _logger2 = _interopRequireDefault(_logger);

var _deviceinfo = require('../deviceinfo');

var _deviceinfo2 = _interopRequireDefault(_deviceinfo);

var _Error = require('./Error');

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

var timeoutException = new Error("Verification timed out"); /*
                                                            Copyright 2018 New Vector Ltd
                                                            
                                                            Licensed under the Apache License, Version 2.0 (the "License");
                                                            you may not use this file except in compliance with the License.
                                                            You may obtain a copy of the License at
                                                            
                                                                http://www.apache.org/licenses/LICENSE-2.0
                                                            
                                                            Unless required by applicable law or agreed to in writing, software
                                                            distributed under the License is distributed on an "AS IS" BASIS,
                                                            WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                                                            See the License for the specific language governing permissions and
                                                            limitations under the License.
                                                            */

/**
 * Base class for verification methods.
 * @module crypto/verification/Base
 */

var VerificationBase = function (_EventEmitter) {
    (0, _inherits3.default)(VerificationBase, _EventEmitter);

    /**
     * Base class for verification methods.
     *
     * <p>Once a verifier object is created, the verification can be started by
     * calling the verify() method, which will return a promise that will
     * resolve when the verification is completed, or reject if it could not
     * complete.</p>
     *
     * <p>Subclasses must have a NAME class property.</p>
     *
     * @class
     *
     * @param {module:base-apis~MatrixBaseApis} baseApis base matrix api interface
     *
     * @param {string} userId the user ID that is being verified
     *
     * @param {string} deviceId the device ID that is being verified
     *
     * @param {string} transactionId the transaction ID to be used when sending events
     *
     * @param {string} [roomId] the room to use for verification
     *
     * @param {object} [startEvent] the m.key.verification.start event that
     * initiated this verification, if any
     *
     * @param {object} [request] the key verification request object related to
     * this verification, if any
     */
    function VerificationBase(baseApis, userId, deviceId, transactionId, roomId, startEvent, request) {
        (0, _classCallCheck3.default)(this, VerificationBase);

        var _this = (0, _possibleConstructorReturn3.default)(this, (VerificationBase.__proto__ || (0, _getPrototypeOf2.default)(VerificationBase)).call(this));

        _this._baseApis = baseApis;
        _this.userId = userId;
        _this.deviceId = deviceId;
        _this.transactionId = transactionId;
        if (typeof roomId === "string" || roomId instanceof String) {
            _this.roomId = roomId;
            _this.startEvent = startEvent;
            _this.request = request;
        } else {
            // if room ID was omitted, but start event and request were not
            _this.startEvent = roomId;
            _this.request = startEvent;
        }
        _this.cancelled = false;
        _this._done = false;
        _this._promise = null;
        _this._transactionTimeoutTimer = null;
        _this._eventsSubscription = null;

        // At this point, the verification request was received so start the timeout timer.
        _this._resetTimer();

        if (_this.roomId) {
            _this._sendWithTxnId = _this._sendMessage;
        } else {
            _this._sendWithTxnId = _this._sendToDevice;
        }
        return _this;
    }

    (0, _createClass3.default)(VerificationBase, [{
        key: '_resetTimer',
        value: function _resetTimer() {
            var _this2 = this;

            _logger2.default.info("Refreshing/starting the verification transaction timeout timer");
            if (this._transactionTimeoutTimer !== null) {
                clearTimeout(this._transactionTimeoutTimer);
            }
            this._transactionTimeoutTimer = setTimeout(function () {
                if (!_this2._done && !_this2.cancelled) {
                    _logger2.default.info("Triggering verification timeout");
                    _this2.cancel(timeoutException);
                }
            }, 10 * 60 * 1000); // 10 minutes
        }
    }, {
        key: '_endTimer',
        value: function _endTimer() {
            if (this._transactionTimeoutTimer !== null) {
                clearTimeout(this._transactionTimeoutTimer);
                this._transactionTimeoutTimer = null;
            }
        }
    }, {
        key: '_contentFromEventWithTxnId',
        value: function _contentFromEventWithTxnId(event) {
            if (this.roomId) {
                // verification as timeline event
                // ensure m.related_to is included in e2ee rooms
                // as the field is excluded from encryption
                var content = (0, _assign2.default)({}, event.getContent());
                content["m.relates_to"] = event.getRelation();
                return content;
            } else {
                // verification as to_device event
                return event.getContent();
            }
        }

        /* creates a content object with the transaction id added to it */

    }, {
        key: '_contentWithTxnId',
        value: function _contentWithTxnId(content) {
            var copy = (0, _assign2.default)({}, content);
            if (this.roomId) {
                // verification as timeline event
                copy["m.relates_to"] = {
                    rel_type: "m.reference",
                    event_id: this.transactionId
                };
            } else {
                // verification as to_device event
                copy.transaction_id = this.transactionId;
            }
            return copy;
        }
    }, {
        key: '_send',
        value: function _send(type, contentWithoutTxnId) {
            var content = this._contentWithTxnId(contentWithoutTxnId);
            return this._sendWithTxnId(type, content);
        }

        /* send a message to the other participant, using to-device messages
         */

    }, {
        key: '_sendToDevice',
        value: function _sendToDevice(type, content) {
            if (this._done) {
                return _promise2.default.reject(new Error("Verification is already done"));
            }
            return this._baseApis.sendToDevice(type, (0, _defineProperty3.default)({}, this.userId, (0, _defineProperty3.default)({}, this.deviceId, content)));
        }

        /* send a message to the other participant, using in-roomm messages
         */

    }, {
        key: '_sendMessage',
        value: function _sendMessage(type, content) {
            if (this._done) {
                return _promise2.default.reject(new Error("Verification is already done"));
            }
            return this._baseApis.sendEvent(this.roomId, type, content);
        }
    }, {
        key: '_waitForEvent',
        value: function _waitForEvent(type) {
            var _this3 = this;

            if (this._done) {
                return _promise2.default.reject(new Error("Verification is already done"));
            }
            this._expectedEvent = type;
            return new _promise2.default(function (resolve, reject) {
                _this3._resolveEvent = resolve;
                _this3._rejectEvent = reject;
            });
        }
    }, {
        key: 'handleEvent',
        value: function handleEvent(e) {
            if (this._done) {
                return;
            } else if (e.getType() === this._expectedEvent) {
                this._expectedEvent = undefined;
                this._rejectEvent = undefined;
                this._resetTimer();
                this._resolveEvent(e);
            } else if (e.getType() === "m.key.verification.cancel") {
                var reject = this._reject;
                this._reject = undefined;
                reject(new Error("Other side cancelled verification"));
            } else {
                var exception = new Error("Unexpected message: expecting " + this._expectedEvent + " but got " + e.getType());
                this._expectedEvent = undefined;
                if (this._rejectEvent) {
                    var _reject = this._rejectEvent;
                    this._rejectEvent = undefined;
                    _reject(exception);
                }
                this.cancel(exception);
            }
        }
    }, {
        key: 'done',
        value: function done() {
            this._endTimer(); // always kill the activity timer
            if (!this._done) {
                if (this.roomId) {
                    // verification in DM requires a done message
                    this._send("m.key.verification.done", {});
                }
                this._resolve();
            }
        }
    }, {
        key: 'cancel',
        value: function cancel(e) {
            this._endTimer(); // always kill the activity timer
            if (!this._done) {
                this.cancelled = true;
                if (this.userId && this.deviceId && this.transactionId) {
                    // send a cancellation to the other user (if it wasn't
                    // cancelled by the other user)
                    if (e === timeoutException) {
                        var timeoutEvent = (0, _Error.newTimeoutError)();
                        this._send(timeoutEvent.getType(), timeoutEvent.getContent());
                    } else if (e instanceof _event.MatrixEvent) {
                        var sender = e.getSender();
                        if (sender !== this.userId) {
                            var content = e.getContent();
                            if (e.getType() === "m.key.verification.cancel") {
                                content.code = content.code || "m.unknown";
                                content.reason = content.reason || content.body || "Unknown reason";
                                content.transaction_id = this.transactionId;
                                this._send("m.key.verification.cancel", content);
                            } else {
                                this._send("m.key.verification.cancel", {
                                    code: "m.unknown",
                                    reason: content.body || "Unknown reason",
                                    transaction_id: this.transactionId
                                });
                            }
                        }
                    } else {
                        this._send("m.key.verification.cancel", {
                            code: "m.unknown",
                            reason: e.toString(),
                            transaction_id: this.transactionId
                        });
                    }
                }
                if (this._promise !== null) {
                    // when we cancel without a promise, we end up with a promise
                    // but no reject function. If cancel is called again, we'd error.
                    if (this._reject) this._reject(e);
                } else {
                    // unsubscribe from events, this happens in _reject usually but we don't have one here
                    if (this._eventsSubscription) {
                        this._eventsSubscription = this._eventsSubscription();
                    }
                    // FIXME: this causes an "Uncaught promise" console message
                    // if nothing ends up chaining this promise.
                    this._promise = _promise2.default.reject(e);
                }
                // Also emit a 'cancel' event that the app can listen for to detect cancellation
                // before calling verify()
                this.emit('cancel', e);
            }
        }

        /**
         * Begin the key verification
         *
         * @returns {Promise} Promise which resolves when the verification has
         *     completed.
         */

    }, {
        key: 'verify',
        value: function verify() {
            var _this4 = this;

            if (this._promise) return this._promise;

            this._promise = new _promise2.default(function (resolve, reject) {
                _this4._resolve = function () {
                    _this4._done = true;
                    _this4._endTimer();
                    if (_this4.handler) {
                        // these listeners are attached in Crypto.acceptVerificationDM
                        if (_this4._eventsSubscription) {
                            _this4._eventsSubscription = _this4._eventsSubscription();
                        }
                    }
                    resolve.apply(undefined, arguments);
                };
                _this4._reject = function () {
                    _this4._done = true;
                    _this4._endTimer();
                    if (_this4.handler) {
                        // these listeners are attached in Crypto.acceptVerificationDM
                        if (_this4._eventsSubscription) {
                            _this4._eventsSubscription = _this4._eventsSubscription();
                        }
                    }
                    reject.apply(undefined, arguments);
                };
            });
            if (this._doVerification && !this._started) {
                this._started = true;
                this._resetTimer(); // restart the timeout
                _promise2.default.resolve(this._doVerification()).then(this.done.bind(this), this.cancel.bind(this));
            }
            return this._promise;
        }
    }, {
        key: '_verifyKeys',
        value: function () {
            var _ref = (0, _bluebird.coroutine)( /*#__PURE__*/_regenerator2.default.mark(function _callee(userId, keys, verifier) {
                var verifiedDevices, _iteratorNormalCompletion, _didIteratorError, _iteratorError, _iterator, _step, _step$value, keyId, keyInfo, deviceId, device, crossSigningInfo, _iteratorNormalCompletion2, _didIteratorError2, _iteratorError2, _iterator2, _step2, _deviceId;

                return _regenerator2.default.wrap(function _callee$(_context) {
                    while (1) {
                        switch (_context.prev = _context.next) {
                            case 0:
                                // we try to verify all the keys that we're told about, but we might
                                // not know about all of them, so keep track of the keys that we know
                                // about, and ignore the rest
                                verifiedDevices = [];
                                _iteratorNormalCompletion = true;
                                _didIteratorError = false;
                                _iteratorError = undefined;
                                _context.prev = 4;
                                _iterator = (0, _getIterator3.default)((0, _entries2.default)(keys));

                            case 6:
                                if (_iteratorNormalCompletion = (_step = _iterator.next()).done) {
                                    _context.next = 29;
                                    break;
                                }

                                _step$value = (0, _slicedToArray3.default)(_step.value, 2), keyId = _step$value[0], keyInfo = _step$value[1];
                                deviceId = keyId.split(':', 2)[1];
                                _context.next = 11;
                                return (0, _bluebird.resolve)(this._baseApis.getStoredDevice(userId, deviceId));

                            case 11:
                                device = _context.sent;

                                if (!device) {
                                    _context.next = 18;
                                    break;
                                }

                                _context.next = 15;
                                return (0, _bluebird.resolve)(verifier(keyId, device, keyInfo));

                            case 15:
                                verifiedDevices.push(deviceId);
                                _context.next = 26;
                                break;

                            case 18:
                                crossSigningInfo = this._baseApis._crypto._deviceList.getStoredCrossSigningForUser(userId);

                                if (!(crossSigningInfo && crossSigningInfo.getId() === deviceId)) {
                                    _context.next = 25;
                                    break;
                                }

                                _context.next = 22;
                                return (0, _bluebird.resolve)(verifier(keyId, _deviceinfo2.default.fromStorage({
                                    keys: (0, _defineProperty3.default)({}, keyId, deviceId)
                                }, deviceId), keyInfo));

                            case 22:
                                verifiedDevices.push(deviceId);
                                _context.next = 26;
                                break;

                            case 25:
                                _logger2.default.warn('verification: Could not find device ' + deviceId + ' to verify');

                            case 26:
                                _iteratorNormalCompletion = true;
                                _context.next = 6;
                                break;

                            case 29:
                                _context.next = 35;
                                break;

                            case 31:
                                _context.prev = 31;
                                _context.t0 = _context['catch'](4);
                                _didIteratorError = true;
                                _iteratorError = _context.t0;

                            case 35:
                                _context.prev = 35;
                                _context.prev = 36;

                                if (!_iteratorNormalCompletion && _iterator.return) {
                                    _iterator.return();
                                }

                            case 38:
                                _context.prev = 38;

                                if (!_didIteratorError) {
                                    _context.next = 41;
                                    break;
                                }

                                throw _iteratorError;

                            case 41:
                                return _context.finish(38);

                            case 42:
                                return _context.finish(35);

                            case 43:
                                if (verifiedDevices.length) {
                                    _context.next = 45;
                                    break;
                                }

                                throw new Error("No devices could be verified");

                            case 45:
                                _iteratorNormalCompletion2 = true;
                                _didIteratorError2 = false;
                                _iteratorError2 = undefined;
                                _context.prev = 48;
                                _iterator2 = (0, _getIterator3.default)(verifiedDevices);

                            case 50:
                                if (_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done) {
                                    _context.next = 57;
                                    break;
                                }

                                _deviceId = _step2.value;
                                _context.next = 54;
                                return (0, _bluebird.resolve)(this._baseApis.setDeviceVerified(userId, _deviceId));

                            case 54:
                                _iteratorNormalCompletion2 = true;
                                _context.next = 50;
                                break;

                            case 57:
                                _context.next = 63;
                                break;

                            case 59:
                                _context.prev = 59;
                                _context.t1 = _context['catch'](48);
                                _didIteratorError2 = true;
                                _iteratorError2 = _context.t1;

                            case 63:
                                _context.prev = 63;
                                _context.prev = 64;

                                if (!_iteratorNormalCompletion2 && _iterator2.return) {
                                    _iterator2.return();
                                }

                            case 66:
                                _context.prev = 66;

                                if (!_didIteratorError2) {
                                    _context.next = 69;
                                    break;
                                }

                                throw _iteratorError2;

                            case 69:
                                return _context.finish(66);

                            case 70:
                                return _context.finish(63);

                            case 71:
                            case 'end':
                                return _context.stop();
                        }
                    }
                }, _callee, this, [[4, 31, 35, 43], [36,, 38, 42], [48, 59, 63, 71], [64,, 66, 70]]);
            }));

            function _verifyKeys(_x, _x2, _x3) {
                return _ref.apply(this, arguments);
            }

            return _verifyKeys;
        }()
    }, {
        key: 'setEventsSubscription',
        value: function setEventsSubscription(subscription) {
            this._eventsSubscription = subscription;
        }
    }]);
    return VerificationBase;
}(_events.EventEmitter);

exports.default = VerificationBase;
//# sourceMappingURL=Base.js.map